diff --git a/components/lox-compile/src/compile.rs b/components/lox-compile/src/compile.rs index 9939883..91e0e52 100644 --- a/components/lox-compile/src/compile.rs +++ b/components/lox-compile/src/compile.rs @@ -80,5 +80,6 @@ fn compile_expr(db: &dyn crate::Db, expr: &syntax::Expr, chunk: &mut Chunk) { let word_str = word.as_str(db); chunk.emit_byte(Code::Variable(word_str.to_string())) } + syntax::Expr::Assign { name, value } => todo!(), } } diff --git a/components/lox-ir/src/syntax.rs b/components/lox-ir/src/syntax.rs index a302ace..693ea3e 100644 --- a/components/lox-ir/src/syntax.rs +++ b/components/lox-ir/src/syntax.rs @@ -28,6 +28,9 @@ pub enum Expr { // `foo` Variable(Word), + + // assignment expression, like `foo = 1 + 2` + Assign { name: Word, value: Box }, } impl<'db> salsa::DebugWithDb for Expr { @@ -57,6 +60,11 @@ impl<'db> salsa::DebugWithDb for Expr { Expr::BooleanLiteral(value) => write!(f, "BooleanLiteral({})", value), Expr::StringLiteral(word) => write!(f, "StringLiteral({})", word.as_str(db)), Expr::Variable(word) => write!(f, "Variable({})", word.as_str(db)), + Expr::Assign { name, value } => f + .debug_struct("Assign") + .field("name", &name.as_str(db)) + .field("value", &value.debug(db)) + .finish(), _ => todo!(), } } diff --git a/components/lox-parse/src/parser.rs b/components/lox-parse/src/parser.rs index 38d2197..0899e8c 100644 --- a/components/lox-parse/src/parser.rs +++ b/components/lox-parse/src/parser.rs @@ -89,17 +89,38 @@ impl<'me> Parser<'me> { Some(Stmt::Expr(expr)) } - // expression → equality ; - // equality → comparison ( ( "!=" | "==" ) comparison )* ; - // comparison → term ( ( ">" | ">=" | "<" | "<=" ) term )* ; - // term → factor ( ( "-" | "+" ) factor )* ; - // factor → unary ( ( "/" | "*" ) unary )* ; - // unary → ( "!" | "-" ) unary + // expression -> assignment ; + // assignment -> IDENTIFIER "=" assignment | equality ; + // equality -> comparison ( ( "!=" | "==" ) comparison )* ; + // comparison -> term ( ( ">" | ">=" | "<" | "<=" ) term )* ; + // term -> factor ( ( "-" | "+" ) factor )* ; + // factor -> unary ( ( "/" | "*" ) unary )* ; + // unary -> ( "!" | "-" ) unary // | primary ; // primary → NUMBER | STRING | "true" | "false" | "nil" // | "(" expression ")" ; fn parse_expr(&mut self) -> Option { - self.equality() + self.assignment() + } + + // assignment -> IDENTIFIER "=" assignment | equality ; + // assignment is not a statement, it is an expression + fn assignment(&mut self) -> Option { + let expr = self.equality()?; + if self.eat_op(Op::Equal).is_some() { + let value = self.assignment()?; + if let Expr::Variable(name) = expr { + return Some(Expr::Assign { + name, + value: Box::new(value), + }); + } else { + // FIXME: we should use the span of the `expr` here + let span = self.tokens.peek_span(); + self.error(span, "invalid assignment target").emit(self.db); + } + } + Some(expr) } fn equality(&mut self) -> Option { diff --git a/lox_tests/assignment.lox b/lox_tests/assignment.lox new file mode 100644 index 0000000..09b000e --- /dev/null +++ b/lox_tests/assignment.lox @@ -0,0 +1,2 @@ +var a = 1; +a = 2; \ No newline at end of file diff --git a/lox_tests/assignment/bytecode b/lox_tests/assignment/bytecode new file mode 100644 index 0000000..2076a9f --- /dev/null +++ b/lox_tests/assignment/bytecode @@ -0,0 +1,15 @@ +Chunk { + code: [ + Constant( + F64( + 1.0, + ), + ), + VarDeclaration( + "a", + ), + Variable( + "a", + ), + ], +} \ No newline at end of file diff --git a/lox_tests/assignment/execute b/lox_tests/assignment/execute new file mode 100644 index 0000000..ee4d08e --- /dev/null +++ b/lox_tests/assignment/execute @@ -0,0 +1,25 @@ +execute: Constant( + F64( + 1.0, + ), +) +stack: [ + Number( + 1.0, + ), +] + +execute: VarDeclaration( + "a", +) +stack: [] + +execute: Variable( + "a", +) +stack: [ + Number( + 1.0, + ), +] + diff --git a/lox_tests/assignment/syntax b/lox_tests/assignment/syntax new file mode 100644 index 0000000..9becf31 --- /dev/null +++ b/lox_tests/assignment/syntax @@ -0,0 +1,12 @@ +Var { + name: "a", + initializer: Some( + NumberLiteral(1), + ), +} +Expr { + expr: Assign { + name: "a", + value: NumberLiteral(2), + }, +} diff --git a/lox_tests/assignment/token b/lox_tests/assignment/token new file mode 100644 index 0000000..0742858 --- /dev/null +++ b/lox_tests/assignment/token @@ -0,0 +1,20 @@ +TokenTree { + source text: "var a = 1;\na = 2;", + tokens: [ + Alphabetic(var), + Whitespace(' '), + Alphabetic(a), + Whitespace(' '), + Op(=), + Whitespace(' '), + Number(1), + Semicolon, + Whitespace('\n'), + Alphabetic(a), + Whitespace(' '), + Op(=), + Whitespace(' '), + Number(2), + Semicolon, + ], +} \ No newline at end of file