diff --git a/README.md b/README.md index 280bffd..d6ee4dc 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A general-purpose interpreted programming language written in Rust. Guaranteed t # Why the name? -This was inspired by the Lox programming language from [Crafting Interpreters](https://craftinginterpreters.com) by Robert Nystrom. +This is loosely based on the Lox programming language from [Crafting Interpreters](https://craftinginterpreters.com) by Robert Nystrom. For obvious reasons, I cannot name it Lust, hence Lox + Rust = Lost :) @@ -21,14 +21,18 @@ The syntax is fairly straightforward, and is mostly borrowed from existing langu ```rust fn foo(n) { if (n == 0) { - print("Zero"); + print("zero"); while (n <= 1) { print(n); n = n + 1; } return true; } else { - print("Not zero"); + print("not zero"); + // Post-increment (i++) tends to be ambiguous, both in + // expected behaviour and actual outcome. We're better + // off without using it - the only type of increment + // available is pre-increment (++i). for (let i = 0; i < n; ++i) { print(i); } @@ -36,10 +40,9 @@ fn foo(n) { } } -let n = 5; // All numbers are handled as floating point values -let isZero = foo(n); -print(isZero); // false -print(foo(0)); // true +let n = 5; // All numbers are floating point values +print(foo(n)); // false +print(foo(0)); // true, prints "Zero" let a; // Uninitialised variables are null by default print(a); // null @@ -48,12 +51,24 @@ print(a); // null // There are no fields in the class declaration, only methods. // Self-referencing works with the `this` keyword. class Vehicle { + // The constructor is just a function + // with the same name as the class + fn Vehicle() { + this.wheels = 0; + } + fn countWheels() { print(this.wheels); } } -let car = Vehicle(); -car.wheels = 4; +// Classes can inherit from a parent class with the `<-` operator +class Car <- Vehicle { + fn Car() { + this.wheels = 4; + } +} + +let car = Car(); car.countWheels(); // 4 ``` diff --git a/lost_compile/src/environment.rs b/lost_compile/src/environment.rs index 8eba248..c483b80 100644 --- a/lost_compile/src/environment.rs +++ b/lost_compile/src/environment.rs @@ -30,7 +30,7 @@ impl Env { pub fn set(&mut self, name: String, value: Type) { debug!("Set {name} -> {value:?}"); - self.values.insert(name, value.clone()); + self.values.insert(name, value); } pub fn get(&self, name: String) -> Result { diff --git a/lost_compile/src/error.rs b/lost_compile/src/error.rs index 9308c97..30b445a 100644 --- a/lost_compile/src/error.rs +++ b/lost_compile/src/error.rs @@ -23,9 +23,10 @@ pub enum ErrorMsg { ExpectedNumber, ExpectedNumOrStr, ExpectedIdent, + ExpectedObject, + ExpectedClass, + InvalidCall, InvalidStrOp, - InvalidCallExpr, - InvalidObject, TooManyArgs, TooFewArgs, GetConstructor, @@ -34,9 +35,13 @@ pub enum ErrorMsg { UndefinedVar, MisresolvedVar, UndefinedMember, + UndefinedMethod, SelfIntialiser, + SelfInherit, ReturnOutsideFunction, ThisOutsideMethod, + SuperOutsideMethod, + SuperWithoutParent, } impl Display for ErrorMsg { @@ -45,19 +50,24 @@ impl Display for ErrorMsg { Self::ExpectedNumber => "expected numeric operand, found", Self::ExpectedNumOrStr => "expected both operands to be numeric or string, found", Self::ExpectedIdent => "expected identifier, found", + Self::ExpectedObject => "expected object, found", + Self::ExpectedClass => "expected class, found", + Self::InvalidCall => "cannot call non-callable value", Self::InvalidStrOp => "invalid string operation", - Self::InvalidCallExpr => "cannot call non-function value", - Self::InvalidObject => "invalid object, found", Self::TooManyArgs => "too many arguments in function call", Self::TooFewArgs => "too few arguments in function call", Self::GetConstructor => "illegal to get constructor of class", Self::NoScope => "no scope present to resolve", Self::UndefinedVar => "undefined variable", + Self::UndefinedMethod => "undefined class method", Self::MisresolvedVar => "misresolved variable", Self::UndefinedMember => "undefined object member", Self::SelfIntialiser => "cannot use self to initialise", + Self::SelfInherit => "cannot inherit self in class", Self::ReturnOutsideFunction => "cannot return from outside a function", - Self::ThisOutsideMethod => "cannot use `this` variable outside class methods", + Self::ThisOutsideMethod => "cannot use `this` outside class methods", + Self::SuperOutsideMethod => "cannot use `super` outside class methods", + Self::SuperWithoutParent => "cannot use `super` without parent class", }) } } diff --git a/lost_compile/src/interpret.rs b/lost_compile/src/interpret.rs index 323d839..967bdf0 100644 --- a/lost_compile/src/interpret.rs +++ b/lost_compile/src/interpret.rs @@ -27,13 +27,13 @@ impl Interpreter { depths: HashMap, ) -> Result<(), Exception> { self.depths.extend(depths); - self.interpret_all(source.items) + self.interpret_all(&source.items) } - pub fn interpret_all(&mut self, items: Vec) -> Result<(), Exception> { + pub fn interpret_all(&mut self, items: &[Item]) -> Result<(), Exception> { items - .into_iter() - .try_for_each(|item| self.interpret_item(item)) + .iter() + .try_for_each(|item| self.interpret_item(item.clone())) } fn interpret_item(&mut self, item: Item) -> Result<(), Exception> { @@ -48,10 +48,14 @@ impl Interpreter { Item::WhileStmt { condition, body } => self.interpret_while_stmt(condition, *body), Item::ReturnStmt(expr) => self.interpret_return_stmt(expr), Item::Block(items) => self - .interpret_block(items, Env::with_parent(Rc::clone(&self.env))) + .interpret_block(&items, Env::with_parent(Rc::clone(&self.env))) .map(|_| ()), Item::Function { ident, args, body } => self.interpret_function(ident, args, body), - Item::Class { ident, methods } => self.interpret_class(ident, methods), + Item::Class { + ident, + parent, + methods, + } => self.interpret_class(ident, parent, methods), } } @@ -92,11 +96,7 @@ impl Interpreter { Ok(()) } - fn interpret_block( - &mut self, - items: Vec, - env: Rc>, - ) -> Result<(), Exception> { + fn interpret_block(&mut self, items: &[Item], env: Rc>) -> Result<(), Exception> { let old_env = Rc::clone(&self.env); self.env = env; let ret = self.interpret_all(items); @@ -125,7 +125,24 @@ impl Interpreter { } } - fn interpret_class(&mut self, ident: Ident, methods: Vec) -> Result<(), Exception> { + fn interpret_class( + &mut self, + ident: Ident, + parent: Option, + methods: Vec, + ) -> Result<(), Exception> { + let (old_env, parent_class) = if let Some(p) = parent { + let t @ Type::Class(c) = &self.interpret_ident(p.clone())? else { + return Err(runtime_error(ErrorMsg::ExpectedClass, p.name)); + }; + // Create a new parent env with the parent class + let old_env = Rc::clone(&self.env); + self.env = Env::with_parent(Rc::clone(&self.env)); + self.env.borrow_mut().set("super".to_string(), t.clone()); + (Some(old_env), Some(Box::new(c.clone()))) + } else { + (None, None) + }; let mut method_map: HashMap = HashMap::default(); for method in methods { if let Item::Function { ident, args, body } = method { @@ -135,10 +152,16 @@ impl Interpreter { unreachable!("non-functions cannot be passed as methods"); } } + if let Some(env) = old_env { + // Go back to the old env without `super` + self.env = env; + } + self.env.borrow_mut().set( ident.name.clone(), Type::Class(Class { name: ident.name, + parent: parent_class, methods: method_map, }), ); @@ -165,6 +188,7 @@ impl Interpreter { field, value, } => self.interpret_field_set(*object, field, *value), + Expr::Super(super_, method) => self.interpret_super(super_, method), } } @@ -316,7 +340,7 @@ impl Interpreter { Type::Func(f) => Box::new(f), Type::NativeFunc(f) => Box::new(f), Type::Class(c) => Box::new(c), - _ => return Err(runtime_error(ErrorMsg::InvalidCallExpr, ty.to_string())), + _ => return Err(runtime_error(ErrorMsg::InvalidCall, ty.to_string())), }; // Ensure the number of arguments matches the function definition match func.arity().cmp(&arg_exprs.len()) { @@ -342,14 +366,14 @@ impl Interpreter { func.call(self, args) } - pub(crate) fn call(&mut self, func: Func, args: Vec) -> Result { - let env = Env::with_parent(func.env); + pub(crate) fn call(&mut self, func: &Func, args: Vec) -> Result { + let env = Env::with_parent(Rc::clone(&func.env)); // Add the arguments to the function env - for (ident, value) in func.args.into_iter().zip(args) { - env.borrow_mut().set(ident, value); + for (ident, value) in func.args.iter().zip(args) { + env.borrow_mut().set(ident.clone(), value); } // If there is no error or return value, return null - let Err(exception) = self.interpret_block(func.body, env) else { + let Err(exception) = self.interpret_block(&func.body, env) else { return Ok(Type::Null); }; // If the exception is a return value, propagate @@ -364,7 +388,7 @@ impl Interpreter { fn interpret_field_get(&mut self, object: Expr, field: Ident) -> Result { let ty = self.interpret_expr(object)?; let Type::Instance(instance) = ty else { - return Err(runtime_error(ErrorMsg::InvalidObject, ty.to_string())); + return Err(runtime_error(ErrorMsg::ExpectedObject, ty.to_string())); }; instance.get(field.name) } @@ -380,18 +404,49 @@ impl Interpreter { }; let ty = self.interpret_expr(object.clone())?; let Type::Instance(mut instance) = ty else { - return Err(runtime_error(ErrorMsg::InvalidObject, ty.to_string())); + return Err(runtime_error(ErrorMsg::ExpectedObject, ty.to_string())); }; let value = self.interpret_expr(expr)?; instance.set(field.name, value)?; self.env.borrow_mut().assign_at_depth( - instance.to_string(), + ident.name.clone(), Type::Instance(instance), *self.depths.get(ident).expect("unresolved object"), )?; Ok(Type::Null) } + fn interpret_super(&self, super_: Ident, method: Ident) -> Result { + let depth = self + .depths + .get(&super_) + .expect("no `super` variable present"); + let Type::Class(parent) = self + .env + .borrow() + .get_at_depth("super".to_string(), *depth)? + else { + unreachable!("`super` cannot be a non-class type") + }; + let Type::Instance(this) = self + .env + .borrow() + .get_at_depth("this".to_string(), *depth - 1)? + else { + unreachable!("`this` cannot be a non-instance type") + }; + let Some(mut method) = parent.get_method(&method.name) else { + return Err(runtime_error(ErrorMsg::UndefinedMethod, method.name)); + }; + // Add `this` into a parent env for the function to access + method.env = Env::with_parent(method.env); + method + .env + .borrow_mut() + .set("this".to_string(), Type::Instance(this)); + return Ok(Type::Func(method)); + } + fn is_eq(&self, left: &Type, right: &Type) -> bool { match (left, right) { (Type::Number(m), Type::Number(n)) => m == n, @@ -468,7 +523,7 @@ mod tests { 1, ); assert!(interpreter - .interpret_block(items, Env::with_parent(Rc::clone(&interpreter.env))) + .interpret_block(&items, Env::with_parent(Rc::clone(&interpreter.env))) .is_ok()); } diff --git a/lost_compile/src/resolve.rs b/lost_compile/src/resolve.rs index 4eb82a5..5eb85dd 100644 --- a/lost_compile/src/resolve.rs +++ b/lost_compile/src/resolve.rs @@ -11,7 +11,6 @@ use crate::{ enum Node { Function, Method, - Class, } #[derive(Debug, Default)] @@ -19,6 +18,7 @@ pub struct Resolver { scopes: Vec>, depths: HashMap, node: Option, + is_child_class: bool, } impl Resolver { @@ -54,15 +54,12 @@ impl Resolver { Item::Block(items) => self.resolve_block(items), Item::WhileStmt { condition, body } => self.resolve_while_stmt(condition, *body), Item::ReturnStmt(expr) => self.resolve_return_stmt(expr), - Item::Function { - ident: name, - args, - body, - } => self.resolve_func_decl(name, args, body), + Item::Function { ident, args, body } => self.resolve_func_decl(ident, args, body), Item::Class { - ident: name, + ident, + parent, methods, - } => self.resolve_class(name, methods), + } => self.resolve_class(ident, parent, methods), } } @@ -145,13 +142,26 @@ impl Resolver { Ok(()) } - fn resolve_class(&mut self, ident: Ident, methods: Vec) -> Result<(), Exception> { - let old = self.node; - self.node = Some(Node::Class); + fn resolve_class( + &mut self, + ident: Ident, + parent: Option, + methods: Vec, + ) -> Result<(), Exception> { self.declare(ident.name.clone())?; - self.define(ident.name)?; - self.init_scope(); + self.define(ident.name.clone())?; + if let Some(p) = &parent { + if p.name == ident.name { + return Err(resolution_error(ErrorMsg::SelfInherit, p.name.clone())); + } + self.is_child_class = true; + self.resolve_ident(p.clone())?; + self.init_scope(); + self.define("super".to_string())?; + } + + self.init_scope(); self.define("this".to_string())?; for method in methods { if let Item::Function { args, body, .. } = method { @@ -160,9 +170,11 @@ impl Resolver { unreachable!("non-functions cannot be passed as methods"); } } - self.end_scope(); - self.node = old; + if parent.is_some() { + self.end_scope(); + } + Ok(()) } @@ -181,16 +193,36 @@ impl Resolver { Expr::FieldSet { object, value, .. } => self .resolve_expr(*object) .and_then(|_| self.resolve_expr(*value)), + Expr::Super(super_, _) => self.resolve_ident(super_), } } fn resolve_ident(&mut self, ident: Ident) -> Result<(), Exception> { // The `this` variable cannot be used outside class methods - if ident.name == "this" && self.node != Some(Node::Method) { - return Err(resolution_error( - ErrorMsg::ThisOutsideMethod, - String::default(), - )); + match ident.name.as_str() { + "this" => { + if self.node != Some(Node::Method) { + return Err(resolution_error( + ErrorMsg::ThisOutsideMethod, + String::default(), + )); + } + } + "super" => { + if self.node != Some(Node::Method) { + return Err(resolution_error( + ErrorMsg::SuperOutsideMethod, + String::default(), + )); + } + if !self.is_child_class { + return Err(resolution_error( + ErrorMsg::SuperWithoutParent, + String::default(), + )); + } + } + _ => (), } if let Some(&value) = self.scopes.last().and_then(|s| s.get(&ident.name)) { diff --git a/lost_compile/src/types.rs b/lost_compile/src/types.rs index 0fe6fed..fdfccec 100644 --- a/lost_compile/src/types.rs +++ b/lost_compile/src/types.rs @@ -64,7 +64,7 @@ impl Callable for Func { self.args.len() } fn call(&self, interpreter: &mut Interpreter, args: Vec) -> Result { - interpreter.call(self.clone(), args) + interpreter.call(self, args) } } @@ -112,6 +112,7 @@ impl Callable for NativeFunc { #[derive(Clone, Debug, PartialEq)] pub struct Class { pub name: String, + pub parent: Option>, pub methods: HashMap, } @@ -136,14 +137,23 @@ impl Callable for Class { }); // The constructor has the same name as the class. // If it is in the list of defined methods, run it. - if let Some(mut init) = self.methods.get(&self.name).cloned() { + if let Some(mut init) = self.get_method(&self.name) { init.env = Env::with_parent(init.env); - init.env - .borrow_mut() - .set("this".to_string(), instance.clone()); + init.env.borrow_mut().set("this".to_string(), instance); init.call(interpreter, args)?; + Ok(init.env.borrow().get("this".to_string())?) + } else { + Ok(instance) } - Ok(instance) + } +} + +impl Class { + pub fn get_method(&self, method: &str) -> Option { + self.methods + .get(method) + .cloned() + .or_else(|| self.parent.as_ref().and_then(|p| p.get_method(method))) } } @@ -160,19 +170,20 @@ impl Display for Instance { } impl Instance { - pub fn get(&self, member: String) -> Result { + pub fn get(self, member: String) -> Result { if let Some(value) = self.fields.get(&member) { return Ok(value.clone()); } - // The constructor must be fetched or explicitly called + // The constructor must not be fetched or explicitly called if self.class.name == member { return Err(runtime_error(ErrorMsg::GetConstructor, member)); } - if let Some(mut func) = self.class.methods.get(&member).cloned() { + if let Some(mut func) = self.class.get_method(&member) { + // Add `this` into a parent env for the function to access func.env = Env::with_parent(func.env); func.env .borrow_mut() - .set("this".to_string(), Type::Instance(self.clone())); + .set("this".to_string(), Type::Instance(self)); return Ok(Type::Func(func)); } Err(runtime_error(ErrorMsg::UndefinedMember, member)) diff --git a/lost_syntax/src/ast.rs b/lost_syntax/src/ast.rs index 60aedec..1b737d7 100644 --- a/lost_syntax/src/ast.rs +++ b/lost_syntax/src/ast.rs @@ -11,6 +11,7 @@ pub struct Source { pub enum Item { Class { ident: Ident, + parent: Option, methods: Vec, }, Function { @@ -192,4 +193,5 @@ pub enum Expr { field: Ident, value: Box, }, + Super(Ident, Ident), } diff --git a/lost_syntax/src/error.rs b/lost_syntax/src/error.rs index f3ee39b..b5d82cb 100644 --- a/lost_syntax/src/error.rs +++ b/lost_syntax/src/error.rs @@ -14,8 +14,10 @@ pub enum ErrorMsg { MissingOpeningBrace, MissingClosingBrace, MissingSemicolon, - InvalidIdent, - InvalidField, + ExpectedParentClass, + ExpectedMethod, + ExpectedIdent, + ExpectedField, InvalidAssignment, // EOF EndOfStream, @@ -32,8 +34,10 @@ impl Display for ErrorMsg { Self::MissingOpeningBrace => "missing opening brace at", Self::MissingClosingBrace => "missing closing brace at", Self::MissingSemicolon => "missing semicolon at", - Self::InvalidIdent => "invalid identifier", - Self::InvalidField => "invalid field", + Self::ExpectedParentClass => "expected parent class at", + Self::ExpectedMethod => "expected method at", + Self::ExpectedIdent => "expected identifier, found", + Self::ExpectedField => "expected field, found", Self::InvalidAssignment => "invalid assignment target", Self::EndOfStream => "end of stream", }) diff --git a/lost_syntax/src/lex.rs b/lost_syntax/src/lex.rs index 61600f1..4db1397 100644 --- a/lost_syntax/src/lex.rs +++ b/lost_syntax/src/lex.rs @@ -59,24 +59,18 @@ impl<'a> Lexer<'a> { match self.advance() { Some(c) => { match c { - '!' => { - Ok(self.lookahead_for_token('=', TokenKind::BANG_EQUAL, TokenKind::BANG)) - } - '=' => { - Ok(self.lookahead_for_token('=', TokenKind::EQUAL_EQUAL, TokenKind::EQUAL)) - } - '>' => Ok(self.lookahead_for_token( - '=', - TokenKind::GREATER_EQUAL, - TokenKind::GREATER, - )), - '<' => { - Ok(self.lookahead_for_token('=', TokenKind::LESS_EQUAL, TokenKind::LESS)) - } - '+' => Ok(self.lookahead_for_token('+', TokenKind::INCREMENT, TokenKind::PLUS)), - '-' => { - Ok(self.lookahead_for_token('-', TokenKind::DECREMENT, TokenKind::MINUS)) + '!' => Ok(self.advance_if_or('=', TokenKind::BANG_EQUAL, TokenKind::BANG)), + '=' => Ok(self.advance_if_or('=', TokenKind::EQUAL_EQUAL, TokenKind::EQUAL)), + '>' => { + Ok(self.advance_if_or('=', TokenKind::GREATER_EQUAL, TokenKind::GREATER)) } + '<' => Ok(if self.advance_if(|t| t == '=').is_some() { + self.make_token(TokenKind::LESS_EQUAL) + } else { + self.advance_if_or('-', TokenKind::INHERIT, TokenKind::LESS) + }), + '+' => Ok(self.advance_if_or('+', TokenKind::INCREMENT, TokenKind::PLUS)), + '-' => Ok(self.advance_if_or('-', TokenKind::DECREMENT, TokenKind::MINUS)), '/' => Ok(self.lex_slash_or_comment()), '"' => self.lex_string(), _ => { @@ -204,7 +198,7 @@ impl<'a> Lexer<'a> { count.ne(&0).then_some(count) } - fn lookahead_for_token( + fn advance_if_or( &mut self, match_char: char, if_match: TokenKind, diff --git a/lost_syntax/src/parse.rs b/lost_syntax/src/parse.rs index cff47b1..54da846 100644 --- a/lost_syntax/src/parse.rs +++ b/lost_syntax/src/parse.rs @@ -58,7 +58,7 @@ impl<'a> Parser<'a> { fn parse_let_stmt(&mut self) -> Result { // Consume the `let` keyword self.advance(); - let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::InvalidIdent)?; + let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::ExpectedIdent)?; let init = if self.advance_if(|t| t.kind == TokenKind::EQUAL).is_some() { Some(self.parse_expr()?) } else { @@ -169,11 +169,21 @@ impl<'a> Parser<'a> { fn parse_class(&mut self) -> Result { // Consume the `class` keyword self.advance(); - let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::InvalidIdent)?; + let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::ExpectedIdent)?; let ident = Ident { name: name.lexeme.clone(), range: name.range, }; + // Consume the parent class, if any + let parent = if self.advance_if(|t| t.kind == TokenKind::INHERIT).is_some() { + let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::ExpectedParentClass)?; + Some(Ident { + name: name.lexeme.clone(), + range: name.range, + }) + } else { + None + }; self.advance_or_err(TokenKind::LBRACE, ErrorMsg::MissingOpeningBrace)?; let mut methods = vec![]; while self @@ -186,13 +196,17 @@ impl<'a> Parser<'a> { } self.advance_or_err(TokenKind::RBRACE, ErrorMsg::MissingClosingBrace)?; - Ok(Item::Class { ident, methods }) + Ok(Item::Class { + ident, + parent, + methods, + }) } fn parse_function(&mut self) -> Result { // Consume the `fn` keyword self.advance(); - let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::InvalidIdent)?; + let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::ExpectedIdent)?; let ident = Ident { name: name.lexeme.clone(), range: name.range, @@ -205,7 +219,7 @@ impl<'a> Parser<'a> { .filter(|t| t.kind == TokenKind::RPAREN) .is_none() { - let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::InvalidIdent)?; + let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::ExpectedIdent)?; args.push(Ident { name: name.lexeme.clone(), range: name.range, @@ -477,7 +491,7 @@ impl<'a> Parser<'a> { TokenKind::DOT => { // Consume the dot self.advance(); - let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::InvalidIdent)?; + let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::ExpectedIdent)?; expr = Expr::FieldGet { object: Box::new(expr), field: Ident { @@ -510,6 +524,26 @@ impl<'a> Parser<'a> { range: t.range, })) } + TokenKind::SUPER => { + let super_ = t.clone(); + return self + .advance_or_err(TokenKind::DOT, ErrorMsg::ExpectedMethod) + .and_then(|_| { + self.advance_or_err(TokenKind::IDENT, ErrorMsg::ExpectedMethod) + }) + .map(|t| { + Expr::Super( + Ident { + name: super_.lexeme.clone(), + range: super_.range, + }, + Ident { + name: t.lexeme.clone(), + range: t.range, + }, + ) + }); + } _ => return Err(Self::error(t, ErrorMsg::UnexpectedToken)), }, None => return Err(format!("Parse error: {}", ErrorMsg::EndOfStream)), @@ -624,7 +658,7 @@ mod tests { items: vec![Item::LetStmt { ident: Ident { name: "x".to_owned(), - range: TextRange { start: 0, end: 0 }, + range: TextRange { start: 4, end: 5 }, }, init: Some(Expr::Literal(Literal::Number(42.0))), }], @@ -685,7 +719,7 @@ mod tests { Item::LetStmt { ident: Ident { name: var.clone(), - range: TextRange { start: 0, end: 0 }, + range: TextRange { start: 9, end: 10 }, }, init: Some(Expr::Literal(Literal::Number(0.0))), }, @@ -693,7 +727,7 @@ mod tests { condition: Expr::Binary { lhs: Box::new(Expr::Ident(Ident { name: var.clone(), - range: TextRange { start: 0, end: 0 }, + range: TextRange { start: 16, end: 17 }, })), op: ast::BinOp::Less, rhs: Box::new(Expr::Literal(Literal::Number(5.0))), @@ -701,17 +735,17 @@ mod tests { body: Box::new(Item::Block(vec![ Item::Block(vec![Item::ExprStmt(Expr::Ident(Ident { name: var.clone(), - range: TextRange { start: 0, end: 0 }, + range: TextRange { start: 36, end: 37 }, }))]), Item::ExprStmt(Expr::Assignment { name: Ident { name: var.clone(), - range: TextRange { start: 0, end: 0 }, + range: TextRange { start: 23, end: 24 }, }, value: Box::new(Expr::Binary { lhs: Box::new(Expr::Ident(Ident { name: var.clone(), - range: TextRange { start: 0, end: 0 }, + range: TextRange { start: 27, end: 28 }, })), op: ast::BinOp::Plus, rhs: Box::new(Expr::Literal(Literal::Number(1.0))), @@ -733,14 +767,14 @@ mod tests { Item::LetStmt { ident: Ident { name: "x".to_owned(), - range: TextRange { start: 0, end: 0 }, + range: TextRange { start: 6, end: 7 }, }, init: Some(Expr::Literal(Literal::Number(1.0))), }, Item::LetStmt { ident: Ident { name: "y".to_owned(), - range: TextRange { start: 0, end: 0 }, + range: TextRange { start: 17, end: 18 }, }, init: Some(Expr::Literal(Literal::Number(2.0))), }, diff --git a/lost_syntax/src/token.rs b/lost_syntax/src/token.rs index 1729075..7a9e2b8 100644 --- a/lost_syntax/src/token.rs +++ b/lost_syntax/src/token.rs @@ -38,7 +38,6 @@ pub enum TokenKind { DECREMENT, // Comparisons BANG_EQUAL, - EQUAL, EQUAL_EQUAL, GREATER, GREATER_EQUAL, @@ -65,6 +64,8 @@ pub enum TokenKind { LET, WHILE, // Miscellaneous tokens + EQUAL, + INHERIT, COMMENT, WHITESPACE, EOF,