refactor(tazjin/rlox): Thread lifetimes through interpreter

In order to store a function in the interpreter's representation of a
callable, the lifetimes used throughout rlox need to be threaded
through properly.

This is currently not optimal, for two reasons:

* following the design of the book's scanner, the source code slice
  needs to still be available at runtime. Rust makes this explicit,
  but it seems unnecessary.

* the interpreter's lifetime is now bounded to be smaller than the
  source's, which means that the REPL no longer persists state between
  evaluations

Both of these can be fixed eventually by diverging the scanner from
the book slightly, but right now that's not my priority.

Change-Id: Id0bf694541ff59795cfdea3c64a965384a49bfe2
Reviewed-on: https://cl.tvl.fyi/c/depot/+/2391
Reviewed-by: tazjin <mail@tazj.in>
Tested-by: BuildkiteCI
This commit is contained in:
Vincent Ambo 2021-01-14 17:44:53 +03:00 committed by tazjin
parent 1ed34443d8
commit fe97398fd9
3 changed files with 59 additions and 48 deletions

View file

@ -16,32 +16,35 @@ mod tests;
// Representation of all callables, including builtins & user-defined // Representation of all callables, including builtins & user-defined
// functions. // functions.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Callable { pub enum Callable<'a> {
Builtin(&'static dyn builtins::Builtin), Builtin(&'static dyn builtins::Builtin),
Function(Rc<parser::Function<'a>>),
} }
impl Callable { impl<'a> Callable<'a> {
fn arity(&self) -> usize { fn arity(&self) -> usize {
match self { match self {
Callable::Builtin(builtin) => builtin.arity(), Callable::Builtin(builtin) => builtin.arity(),
_ => unimplemented!(),
} }
} }
fn call(&self, args: Vec<Value>) -> Result<Value, Error> { fn call(&self, args: Vec<Value>) -> Result<Value<'a>, Error> {
match self { match self {
Callable::Builtin(builtin) => builtin.call(args), Callable::Builtin(builtin) => builtin.call(args),
_ => unimplemented!(),
} }
} }
} }
// Representation of an in-language value. // Representation of an in-language value.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Value { pub enum Value<'a> {
Literal(Literal), Literal(Literal),
Callable(Callable), Callable(Callable<'a>),
} }
impl PartialEq for Value { impl<'a> PartialEq for Value<'a> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
match (self, other) { match (self, other) {
(Value::Literal(lhs), Value::Literal(rhs)) => lhs == rhs, (Value::Literal(lhs), Value::Literal(rhs)) => lhs == rhs,
@ -51,13 +54,13 @@ impl PartialEq for Value {
} }
} }
impl From<Literal> for Value { impl<'a> From<Literal> for Value<'a> {
fn from(lit: Literal) -> Value { fn from(lit: Literal) -> Value<'a> {
Value::Literal(lit) Value::Literal(lit)
} }
} }
impl Value { impl<'a> Value<'a> {
fn expect_literal(self) -> Result<Literal, Error> { fn expect_literal(self) -> Result<Literal, Error> {
match self { match self {
Value::Literal(lit) => Ok(lit), Value::Literal(lit) => Ok(lit),
@ -67,19 +70,19 @@ impl Value {
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
struct Environment { struct Environment<'a> {
enclosing: Option<Rc<RwLock<Environment>>>, enclosing: Option<Rc<RwLock<Environment<'a>>>>,
values: HashMap<String, Value>, values: HashMap<String, Value<'a>>,
} }
impl Environment { impl<'a> Environment<'a> {
fn define(&mut self, name: &scanner::Token, value: Value) -> Result<(), Error> { fn define(&mut self, name: &scanner::Token, value: Value<'a>) -> Result<(), Error> {
let ident = identifier_str(name)?; let ident = identifier_str(name)?;
self.values.insert(ident.into(), value); self.values.insert(ident.into(), value);
Ok(()) Ok(())
} }
fn get(&self, name: &parser::Variable) -> Result<Value, Error> { fn get(&self, name: &parser::Variable) -> Result<Value<'a>, Error> {
let ident = identifier_str(&name.0)?; let ident = identifier_str(&name.0)?;
self.values self.values
@ -98,7 +101,7 @@ impl Environment {
}) })
} }
fn assign(&mut self, name: &scanner::Token, value: Value) -> Result<(), Error> { fn assign(&mut self, name: &scanner::Token, value: Value<'a>) -> Result<(), Error> {
let ident = identifier_str(name)?; let ident = identifier_str(name)?;
match self.values.get_mut(ident) { match self.values.get_mut(ident) {
@ -132,11 +135,11 @@ fn identifier_str<'a>(name: &'a scanner::Token) -> Result<&'a str, Error> {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Interpreter { pub struct Interpreter<'a> {
env: Rc<RwLock<Environment>>, env: Rc<RwLock<Environment<'a>>>,
} }
impl Interpreter { impl<'a> Interpreter<'a> {
/// Create a new interpreter and configure the initial global /// Create a new interpreter and configure the initial global
/// variable set. /// variable set.
pub fn create() -> Self { pub fn create() -> Self {
@ -156,28 +159,28 @@ impl Interpreter {
} }
// Environment modification helpers // Environment modification helpers
fn define_var(&mut self, name: &scanner::Token, value: Value) -> Result<(), Error> { fn define_var(&mut self, name: &scanner::Token, value: Value<'a>) -> Result<(), Error> {
self.env self.env
.write() .write()
.expect("environment lock is poisoned") .expect("environment lock is poisoned")
.define(name, value) .define(name, value)
} }
fn assign_var(&mut self, name: &scanner::Token, value: Value) -> Result<(), Error> { fn assign_var(&mut self, name: &scanner::Token, value: Value<'a>) -> Result<(), Error> {
self.env self.env
.write() .write()
.expect("environment lock is poisoned") .expect("environment lock is poisoned")
.assign(name, value) .assign(name, value)
} }
fn get_var(&mut self, var: &parser::Variable) -> Result<Value, Error> { fn get_var(&mut self, var: &parser::Variable) -> Result<Value<'a>, Error> {
self.env self.env
.read() .read()
.expect("environment lock is poisoned") .expect("environment lock is poisoned")
.get(var) .get(var)
} }
fn set_enclosing(&mut self, parent: Rc<RwLock<Environment>>) { fn set_enclosing(&mut self, parent: Rc<RwLock<Environment<'a>>>) {
self.env self.env
.write() .write()
.expect("environment lock is poisoned") .expect("environment lock is poisoned")
@ -185,7 +188,7 @@ impl Interpreter {
} }
// Interpreter itself // Interpreter itself
pub fn interpret<'a>(&mut self, program: &Block<'a>) -> Result<Value, Error> { pub fn interpret(&mut self, program: &Block<'a>) -> Result<Value<'a>, Error> {
let mut value = Value::Literal(Literal::Nil); let mut value = Value::Literal(Literal::Nil);
for stmt in program { for stmt in program {
@ -195,7 +198,7 @@ impl Interpreter {
Ok(value) Ok(value)
} }
fn interpret_stmt<'a>(&mut self, stmt: &Statement<'a>) -> Result<Value, Error> { fn interpret_stmt(&mut self, stmt: &Statement<'a>) -> Result<Value<'a>, Error> {
let value = match stmt { let value = match stmt {
Statement::Expr(expr) => self.eval(expr)?, Statement::Expr(expr) => self.eval(expr)?,
Statement::Print(expr) => { Statement::Print(expr) => {
@ -214,7 +217,7 @@ impl Interpreter {
Ok(value) Ok(value)
} }
fn interpret_var<'a>(&mut self, var: &parser::Var<'a>) -> Result<Value, Error> { fn interpret_var(&mut self, var: &parser::Var<'a>) -> Result<Value<'a>, Error> {
let init = var.initialiser.as_ref().ok_or_else(|| Error { let init = var.initialiser.as_ref().ok_or_else(|| Error {
line: var.name.line, line: var.name.line,
kind: ErrorKind::InternalError("missing variable initialiser".into()), kind: ErrorKind::InternalError("missing variable initialiser".into()),
@ -224,7 +227,7 @@ impl Interpreter {
Ok(value) Ok(value)
} }
fn interpret_block<'a>(&mut self, block: &parser::Block<'a>) -> Result<Value, Error> { fn interpret_block(&mut self, block: &parser::Block<'a>) -> Result<Value<'a>, Error> {
// Initialise a new environment and point it at the parent // Initialise a new environment and point it at the parent
// (this is a bit tedious because we need to wrap it in and // (this is a bit tedious because we need to wrap it in and
// out of the Rc). // out of the Rc).
@ -241,7 +244,7 @@ impl Interpreter {
return result; return result;
} }
fn interpret_if<'a>(&mut self, if_stmt: &parser::If<'a>) -> Result<Value, Error> { fn interpret_if(&mut self, if_stmt: &parser::If<'a>) -> Result<Value<'a>, Error> {
let condition = self.eval(&if_stmt.condition)?; let condition = self.eval(&if_stmt.condition)?;
if eval_truthy(&condition) { if eval_truthy(&condition) {
@ -253,7 +256,7 @@ impl Interpreter {
} }
} }
fn interpret_while<'a>(&mut self, stmt: &parser::While<'a>) -> Result<Value, Error> { fn interpret_while(&mut self, stmt: &parser::While<'a>) -> Result<Value<'a>, Error> {
let mut value = Value::Literal(Literal::Nil); let mut value = Value::Literal(Literal::Nil);
while eval_truthy(&self.eval(&stmt.condition)?) { while eval_truthy(&self.eval(&stmt.condition)?) {
value = self.interpret_stmt(&stmt.body)?; value = self.interpret_stmt(&stmt.body)?;
@ -262,7 +265,7 @@ impl Interpreter {
Ok(value) Ok(value)
} }
fn eval<'a>(&mut self, expr: &Expr<'a>) -> Result<Value, Error> { fn eval(&mut self, expr: &Expr<'a>) -> Result<Value<'a>, Error> {
match expr { match expr {
Expr::Assign(assign) => self.eval_assign(assign), Expr::Assign(assign) => self.eval_assign(assign),
Expr::Literal(lit) => Ok(lit.clone().into()), Expr::Literal(lit) => Ok(lit.clone().into()),
@ -275,7 +278,7 @@ impl Interpreter {
} }
} }
fn eval_unary<'a>(&mut self, expr: &parser::Unary<'a>) -> Result<Value, Error> { fn eval_unary(&mut self, expr: &parser::Unary<'a>) -> Result<Value<'a>, Error> {
let right = self.eval(&*expr.right)?; let right = self.eval(&*expr.right)?;
match (&expr.operator.kind, right) { match (&expr.operator.kind, right) {
@ -294,7 +297,7 @@ impl Interpreter {
} }
} }
fn eval_binary<'a>(&mut self, expr: &parser::Binary<'a>) -> Result<Value, Error> { fn eval_binary(&mut self, expr: &parser::Binary<'a>) -> Result<Value<'a>, Error> {
let left = self.eval(&*expr.left)?.expect_literal()?; let left = self.eval(&*expr.left)?.expect_literal()?;
let right = self.eval(&*expr.right)?.expect_literal()?; let right = self.eval(&*expr.right)?.expect_literal()?;
@ -338,13 +341,13 @@ impl Interpreter {
Ok(result.into()) Ok(result.into())
} }
fn eval_assign<'a>(&mut self, assign: &parser::Assign<'a>) -> Result<Value, Error> { fn eval_assign(&mut self, assign: &parser::Assign<'a>) -> Result<Value<'a>, Error> {
let value = self.eval(&assign.value)?; let value = self.eval(&assign.value)?;
self.assign_var(&assign.name, value.clone())?; self.assign_var(&assign.name, value.clone())?;
Ok(value) Ok(value)
} }
fn eval_logical<'a>(&mut self, logical: &parser::Logical<'a>) -> Result<Value, Error> { fn eval_logical(&mut self, logical: &parser::Logical<'a>) -> Result<Value<'a>, Error> {
let left = eval_truthy(&self.eval(&logical.left)?); let left = eval_truthy(&self.eval(&logical.left)?);
let right = eval_truthy(&self.eval(&logical.right)?); let right = eval_truthy(&self.eval(&logical.right)?);
@ -358,7 +361,7 @@ impl Interpreter {
} }
} }
fn eval_call<'a>(&mut self, call: &parser::Call<'a>) -> Result<Value, Error> { fn eval_call(&mut self, call: &parser::Call<'a>) -> Result<Value<'a>, Error> {
let callable = match self.eval(&call.callee)? { let callable = match self.eval(&call.callee)? {
Value::Callable(c) => c, Value::Callable(c) => c,
Value::Literal(v) => { Value::Literal(v) => {

View file

@ -1,8 +1,11 @@
use super::*; use super::*;
fn code(code: &str) -> Vec<char> {
code.chars().collect()
}
/// Evaluate a code snippet, returning a value. /// Evaluate a code snippet, returning a value.
fn parse_eval(code: &str) -> Value { fn parse_eval<'a>(chars: &'a [char]) -> Value<'a> {
let chars: Vec<char> = code.chars().collect();
let tokens = scanner::scan(&chars).expect("could not scan code"); let tokens = scanner::scan(&chars).expect("could not scan code");
let program = parser::parse(tokens).expect("could not parse code"); let program = parser::parse(tokens).expect("could not parse code");
Interpreter::create() Interpreter::create()
@ -12,7 +15,7 @@ fn parse_eval(code: &str) -> Value {
#[test] #[test]
fn test_if() { fn test_if() {
let result = parse_eval( let code = code(
r#" r#"
if (42 > 23) if (42 > 23)
"pass"; "pass";
@ -21,12 +24,15 @@ else
"#, "#,
); );
assert_eq!(Value::Literal(Literal::String("pass".into())), result); assert_eq!(
Value::Literal(Literal::String("pass".into())),
parse_eval(&code)
);
} }
#[test] #[test]
fn test_scope() { fn test_scope() {
let result = parse_eval( let code = code(
r#" r#"
var result = ""; var result = "";
@ -48,16 +54,19 @@ var c = "global c";
assert_eq!( assert_eq!(
Value::Literal(Literal::String("inner a, outer b, global c".into())), Value::Literal(Literal::String("inner a, outer b, global c".into())),
result parse_eval(&code),
); );
} }
#[test] #[test]
fn test_binary_operators() { fn test_binary_operators() {
assert_eq!(Value::Literal(Literal::Number(42.0)), parse_eval("40 + 2;")); assert_eq!(
Value::Literal(Literal::Number(42.0)),
parse_eval(&code("40 + 2;"))
);
assert_eq!( assert_eq!(
Value::Literal(Literal::String("foobar".into())), Value::Literal(Literal::String("foobar".into())),
parse_eval("\"foo\" + \"bar\";") parse_eval(&code("\"foo\" + \"bar\";"))
); );
} }

View file

@ -25,14 +25,12 @@ fn main() {
// Run Lox code from a file and print results to stdout // Run Lox code from a file and print results to stdout
fn run_file(file: &str) { fn run_file(file: &str) {
let contents = fs::read_to_string(file).expect("failed to read the input file"); let contents = fs::read_to_string(file).expect("failed to read the input file");
let mut lox = interpreter::Interpreter::create(); run(&contents);
run(&mut lox, &contents);
} }
// Evaluate Lox code interactively in a shitty REPL. // Evaluate Lox code interactively in a shitty REPL.
fn run_prompt() { fn run_prompt() {
let mut line = String::new(); let mut line = String::new();
let mut lox = interpreter::Interpreter::create();
loop { loop {
print!("> "); print!("> ");
@ -40,13 +38,14 @@ fn run_prompt() {
io::stdin() io::stdin()
.read_line(&mut line) .read_line(&mut line)
.expect("failed to read user input"); .expect("failed to read user input");
run(&mut lox, &line); run(&line);
line.clear(); line.clear();
} }
} }
fn run(lox: &mut interpreter::Interpreter, code: &str) { fn run(code: &str) {
let chars: Vec<char> = code.chars().collect(); let chars: Vec<char> = code.chars().collect();
let mut lox = interpreter::Interpreter::create();
match scanner::scan(&chars) { match scanner::scan(&chars) {
Ok(tokens) => match parser::parse(tokens) { Ok(tokens) => match parser::parse(tokens) {