diff --git a/lost_compile/src/environment.rs b/lost_compile/src/environment.rs index 31d8a25..8eba248 100644 --- a/lost_compile/src/environment.rs +++ b/lost_compile/src/environment.rs @@ -1,10 +1,9 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; use log::debug; -use lost_syntax::ast::Literal; use crate::{ - error::{make, ErrorMsg, Exception}, + error::{runtime_error, ErrorMsg, Exception}, stdlib, types::Type, }; @@ -18,7 +17,7 @@ pub struct Env { impl Env { pub fn new() -> Rc> { let mut env = Self::default(); - stdlib::init_io(&mut env); + stdlib::init(&mut env); Rc::new(RefCell::new(env)) } @@ -29,10 +28,9 @@ impl Env { })) } - pub fn set(&mut self, name: String, value: Type) -> Type { + pub fn set(&mut self, name: String, value: Type) { debug!("Set {name} -> {value:?}"); self.values.insert(name, value.clone()); - value } pub fn get(&self, name: String) -> Result { @@ -44,10 +42,10 @@ impl Env { debug!("Get {name} from parent"); return parent.borrow().get(name); } - Err(make(ErrorMsg::UndefinedVar, name)) + Err(runtime_error(ErrorMsg::UndefinedVar, name)) } - pub fn assign(&mut self, name: String, value: Type) -> Result { + pub fn assign(&mut self, name: String, value: Type) -> Result<(), Exception> { debug!("Assign {name} -> {value:?})"); if self.values.contains_key(&name) { return Ok(self.set(name, value)); @@ -56,16 +54,41 @@ impl Env { debug!("Assign {name} in parent"); return parent.borrow_mut().assign(name, value); } - Err(make(ErrorMsg::UndefinedVar, name)) + Err(runtime_error(ErrorMsg::UndefinedVar, name)) } - pub fn from_literal(&self, value: Literal) -> Result { - Ok(match value { - Literal::Str(s) => Type::Str(s), - Literal::Number(n) => Type::Number(n), - Literal::Boolean(b) => Type::Boolean(b), - Literal::Ident(name) => return self.get(name), - Literal::Null => Type::Null, - }) + pub fn get_at_depth(&self, name: String, depth: usize) -> Result { + debug!("Get {name} at depth {depth}"); + if depth == 0 { + if let Some(value) = self.values.get(&name) { + return Ok(value.clone()); + } + return Err(runtime_error(ErrorMsg::MisresolvedVar, name)); + } + self.parent + .as_ref() + .expect("depth exceeds maximum environment depth") + .borrow() + .get_at_depth(name, depth - 1) + } + + pub fn assign_at_depth( + &mut self, + name: String, + value: Type, + depth: usize, + ) -> Result<(), Exception> { + debug!("Set {name} -> {value} at depth {depth}"); + if depth == 0 { + if self.values.contains_key(&name) { + return Ok(self.set(name, value)); + } + return Err(runtime_error(ErrorMsg::MisresolvedVar, name)); + } + self.parent + .as_ref() + .expect("depth exceeds maximum environment depth") + .borrow_mut() + .assign_at_depth(name, value, depth - 1) } } diff --git a/lost_compile/src/error.rs b/lost_compile/src/error.rs index e96af26..9308c97 100644 --- a/lost_compile/src/error.rs +++ b/lost_compile/src/error.rs @@ -29,9 +29,14 @@ pub enum ErrorMsg { TooManyArgs, TooFewArgs, GetConstructor, - // Memory errors + // Resolution errors + NoScope, UndefinedVar, + MisresolvedVar, UndefinedMember, + SelfIntialiser, + ReturnOutsideFunction, + ThisOutsideMethod, } impl Display for ErrorMsg { @@ -46,12 +51,25 @@ impl Display for ErrorMsg { 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::MisresolvedVar => "misresolved variable", Self::UndefinedMember => "undefined object member", + Self::SelfIntialiser => "cannot use self to initialise", + Self::ReturnOutsideFunction => "cannot return from outside a function", + Self::ThisOutsideMethod => "cannot use `this` variable outside class methods", }) } } -pub fn make(msg: ErrorMsg, val: String) -> Exception { +pub fn runtime_error(msg: ErrorMsg, val: String) -> Exception { Exception::Error(format!("Runtime error: {} {}", msg, val)) } + +pub fn resolution_error(msg: ErrorMsg, val: String) -> Exception { + Exception::Error( + format!("Resolution error: {} {}", msg, val) + .trim() + .to_string(), + ) +} diff --git a/lost_compile/src/interpret.rs b/lost_compile/src/interpret.rs index cdcfa5d..323d839 100644 --- a/lost_compile/src/interpret.rs +++ b/lost_compile/src/interpret.rs @@ -2,74 +2,65 @@ use std::{cell::RefCell, cmp::Ordering, collections::HashMap, rc::Rc}; use crate::{ environment::Env, - error::{make, ErrorMsg, Exception}, + error::{runtime_error, ErrorMsg, Exception}, types::{Callable, Class, Func, Type}, }; -use lost_syntax::ast::{BinOp, Expr, Item, Literal, LogicalOp, UnaryOp}; +use lost_syntax::ast::{BinOp, Expr, Ident, Item, Literal, LogicalOp, Source, UnaryOp}; #[derive(Default, Debug)] pub struct Interpreter { pub env: Rc>, + depths: HashMap, } impl Interpreter { pub fn new() -> Self { - Self { env: Env::new() } - } - - pub fn interpret_all(&mut self, items: Vec) -> Result<(), Exception> { - let mut item_iter = items.into_iter(); - while let Some(item) = item_iter.next() { - self.interpret(item)?; + Self { + env: Env::new(), + depths: HashMap::default(), } - Ok(()) } - pub fn interpret(&mut self, item: Item) -> Result<(), Exception> { - self.interpret_item(item) + pub fn interpret( + &mut self, + source: Source, + depths: HashMap, + ) -> Result<(), Exception> { + self.depths.extend(depths); + self.interpret_all(source.items) } - fn interpret_item(&mut self, item: Item) -> Result<(), Exception> { - self.interpret_stmt(item) + pub fn interpret_all(&mut self, items: Vec) -> Result<(), Exception> { + items + .into_iter() + .try_for_each(|item| self.interpret_item(item)) } - fn interpret_stmt(&mut self, stmt: Item) -> Result<(), Exception> { - match stmt { - Item::ExprStmt(expr) => { - self.interpret_expr(expr)?; - } - Item::LetStmt { name, init } => return self.interpret_let_stmt(name, init), + fn interpret_item(&mut self, item: Item) -> Result<(), Exception> { + match item { + Item::ExprStmt(expr) => self.interpret_expr(expr).map(|_| ()), + Item::LetStmt { ident, init } => self.interpret_let_stmt(ident, init), Item::IfStmt { condition, if_item, else_item, - } => return self.interpret_if_stmt(condition, *if_item, else_item.map(|item| *item)), - Item::WhileStmt { condition, body } => { - return self.interpret_while_stmt(condition, *body) - } - Item::ReturnStmt(expr) => return self.interpret_return_stmt(expr), - Item::Block(items) => { - return self - .interpret_block(items, Env::with_parent(Rc::clone(&self.env))) - .map(|_| ()) - } - Item::Function { name, args, body } => { - return self.interpret_function(name, args, body) - } - Item::Class { name, methods } => return self.interpret_class(name, methods), - }; - Ok(()) + } => self.interpret_if_stmt(condition, *if_item, else_item.map(|item| *item)), + 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))) + .map(|_| ()), + Item::Function { ident, args, body } => self.interpret_function(ident, args, body), + Item::Class { ident, methods } => self.interpret_class(ident, methods), + } } - fn interpret_let_stmt(&mut self, name: Literal, init: Option) -> Result<(), Exception> { - let Literal::Ident(ident) = name else { - unreachable!("non-identifiers cannot be passed to this function") - }; + fn interpret_let_stmt(&mut self, ident: Ident, init: Option) -> Result<(), Exception> { let value = match init { Some(expr) => self.interpret_expr(expr)?, None => Type::Null, }; - self.env.borrow_mut().set(ident, value); + self.env.borrow_mut().set(ident.name, value); Ok(()) } @@ -115,56 +106,39 @@ impl Interpreter { fn interpret_function( &mut self, - name: Literal, - args: Vec, + ident: Ident, + args: Vec, body: Vec, ) -> Result<(), Exception> { - let func = self.create_function(name, args, body); - self.env - .borrow_mut() - .set(func.name.clone(), Type::Func(func)); + let func = self.create_function(ident.clone(), args, body); + self.env.borrow_mut().set(ident.name, Type::Func(func)); Ok(()) } - fn create_function(&self, name: Literal, args: Vec, body: Vec) -> Func { - let Literal::Ident(ident) = name else { - unreachable!("non-identifiers cannot be passed to this function") - }; - let arg_idents = args - .into_iter() - .map(|arg| { - if let Literal::Ident(arg_ident) = arg { - arg_ident - } else { - unreachable!("non-identifiers cannot be passed to this function"); - } - }) - .collect(); + fn create_function(&self, ident: Ident, args: Vec, body: Vec) -> Func { + let arg_idents = args.into_iter().map(|arg| arg.name).collect(); Func { - name: ident, + name: ident.name, args: arg_idents, body, env: Rc::clone(&self.env), } } - fn interpret_class(&mut self, name: Literal, methods: Vec) -> Result<(), Exception> { - let Literal::Ident(ident) = name else { - unreachable!("non-identifiers cannot be passed to this function") - }; + fn interpret_class(&mut self, ident: Ident, methods: Vec) -> Result<(), Exception> { let mut method_map: HashMap = HashMap::default(); for method in methods { - if let Item::Function { name, args, body } = method { - let func = self.create_function(name, args, body); + if let Item::Function { ident, args, body } = method { + let func = self.create_function(ident, args, body); method_map.insert(func.name.clone(), func); } else { unreachable!("non-functions cannot be passed as methods"); } } self.env.borrow_mut().set( - ident.clone(), + ident.name.clone(), Type::Class(Class { - name: ident, + name: ident.name, methods: method_map, }), ); @@ -178,12 +152,13 @@ impl Interpreter { fn interpret_expr(&mut self, expr: Expr) -> Result { match expr { Expr::Assignment { name, value } => self.interpret_assignment(name, *value), - Expr::Literal(l) => Ok(self.env.borrow().from_literal(l)?), + Expr::Literal(lit) => self.interpret_literal(lit), + Expr::Ident(ident) => self.interpret_ident(ident), Expr::Unary { op, expr } => self.interpret_unary(op, *expr), Expr::Binary { lhs, op, rhs } => self.interpret_binary(*lhs, op, *rhs), Expr::Logical { lhs, op, rhs } => self.interpret_logical(*lhs, op, *rhs), Expr::Group(e) => self.interpret_expr(*e), - Expr::Call { func, args } => self.interpret_func_call(*func, args), + Expr::Call { func, args } => self.interpret_call(*func, args), Expr::FieldGet { object, field } => self.interpret_field_get(*object, field), Expr::FieldSet { object, @@ -193,9 +168,33 @@ impl Interpreter { } } - fn interpret_assignment(&mut self, name: Literal, expr: Expr) -> Result { + fn interpret_assignment(&mut self, ident: Ident, expr: Expr) -> Result { let value = self.interpret_expr(expr)?; - self.env.borrow_mut().assign(name.to_string(), value) + self.env.borrow_mut().assign_at_depth( + ident.name.clone(), + value, + *self.depths.get(&ident).expect("unresolved variable"), + )?; + Ok(Type::Null) + } + + fn interpret_literal(&mut self, lit: Literal) -> Result { + Ok(match lit { + Literal::Str(s) => Type::Str(s), + Literal::Number(n) => Type::Number(n), + Literal::Boolean(b) => Type::Boolean(b), + Literal::Null => Type::Null, + }) + } + + fn interpret_ident(&mut self, ident: Ident) -> Result { + self.env.borrow().get_at_depth( + ident.name.clone(), + *self + .depths + .get(&ident) + .expect(&format!("unresolved variable {}", ident.name)), + ) } fn interpret_unary(&mut self, op: UnaryOp, expr: Expr) -> Result { @@ -205,30 +204,38 @@ impl Interpreter { if let Type::Number(n) = lit { Ok(Type::Number(-n)) } else { - Err(make(ErrorMsg::ExpectedNumber, lit.to_string())) + Err(runtime_error(ErrorMsg::ExpectedNumber, lit.to_string())) } } - UnaryOp::Bang => return Ok(Type::Boolean(!self.to_bool(&lit))), + UnaryOp::Bang => Ok(Type::Boolean(!self.to_bool(&lit))), UnaryOp::Increment => { - let Expr::Literal(Literal::Ident(ident)) = expr else { - return Err(make(ErrorMsg::ExpectedIdent, lit.to_string())); + let Expr::Ident(ident) = expr else { + return Err(runtime_error(ErrorMsg::ExpectedIdent, lit.to_string())); }; if let Type::Number(n) = lit { - self.env.borrow_mut().assign(ident, Type::Number(n + 1.0))?; + self.env.borrow_mut().assign_at_depth( + ident.name.clone(), + Type::Number(n + 1.0), + *self.depths.get(&ident).expect("unresolved variable"), + )?; Ok(Type::Number(n + 1.0)) } else { - Err(make(ErrorMsg::ExpectedNumber, lit.to_string())) + Err(runtime_error(ErrorMsg::ExpectedNumber, lit.to_string())) } } UnaryOp::Decrement => { - let Expr::Literal(Literal::Ident(ident)) = expr else { - return Err(make(ErrorMsg::ExpectedIdent, lit.to_string())); + let Expr::Ident(ident) = expr else { + return Err(runtime_error(ErrorMsg::ExpectedIdent, lit.to_string())); }; if let Type::Number(n) = lit { - self.env.borrow_mut().assign(ident, Type::Number(n - 1.0))?; + self.env.borrow_mut().assign_at_depth( + ident.name.clone(), + Type::Number(n - 1.0), + *self.depths.get(&ident).expect("unresolved variable"), + )?; Ok(Type::Number(n - 1.0)) } else { - Err(make(ErrorMsg::ExpectedNumber, lit.to_string())) + Err(runtime_error(ErrorMsg::ExpectedNumber, lit.to_string())) } } } @@ -249,10 +256,10 @@ impl Interpreter { rhs: Expr, ) -> Result { let left = self.interpret_expr(lhs)?; - return match (op, self.to_bool(&left)) { + match (op, self.to_bool(&left)) { (LogicalOp::Or, true) | (LogicalOp::And, false) => Ok(left), _ => self.interpret_expr(rhs), - }; + } } fn interpret_binary(&mut self, lhs: Expr, op: BinOp, rhs: Expr) -> Result { @@ -265,9 +272,9 @@ impl Interpreter { if let Type::Str(right_str) = right { return Ok(Type::Str(left_str + &right_str)); } - return Err(make(ErrorMsg::ExpectedNumOrStr, right.to_string())); + return Err(runtime_error(ErrorMsg::ExpectedNumOrStr, right.to_string())); } - return Err(make(ErrorMsg::InvalidStrOp, op.to_string())); + return Err(runtime_error(ErrorMsg::InvalidStrOp, op.to_string())); }; // Handle == and != @@ -283,10 +290,10 @@ impl Interpreter { // that gives the user a gun to shoot their foot with, and // is hence not implemented. No numero, no bueno ;) let Type::Number(left_num) = left else { - return Err(make(ErrorMsg::ExpectedNumber, left.to_string())); + return Err(runtime_error(ErrorMsg::ExpectedNumber, left.to_string())); }; let Type::Number(right_num) = right else { - return Err(make(ErrorMsg::ExpectedNumber, right.to_string())); + return Err(runtime_error(ErrorMsg::ExpectedNumber, right.to_string())); }; Ok(match op { @@ -303,28 +310,24 @@ impl Interpreter { }) } - fn interpret_func_call( - &mut self, - fn_expr: Expr, - arg_exprs: Vec, - ) -> Result { + fn interpret_call(&mut self, fn_expr: Expr, arg_exprs: Vec) -> Result { let ty = self.interpret_expr(fn_expr)?; let func: Box = match ty { Type::Func(f) => Box::new(f), Type::NativeFunc(f) => Box::new(f), Type::Class(c) => Box::new(c), - _ => return Err(make(ErrorMsg::InvalidCallExpr, ty.to_string())), + _ => return Err(runtime_error(ErrorMsg::InvalidCallExpr, ty.to_string())), }; // Ensure the number of arguments matches the function definition match func.arity().cmp(&arg_exprs.len()) { Ordering::Greater => { - return Err(make( + return Err(runtime_error( ErrorMsg::TooFewArgs, format!("(expected {}, got {})", func.arity(), arg_exprs.len()), )) } Ordering::Less => { - return Err(make( + return Err(runtime_error( ErrorMsg::TooManyArgs, format!("(expected {}, got {})", func.arity(), arg_exprs.len()), )) @@ -339,7 +342,7 @@ impl Interpreter { func.call(self, args) } - pub(crate) fn call_func(&mut self, func: Func, args: Vec) -> Result { + pub(crate) fn call(&mut self, func: Func, args: Vec) -> Result { let env = Env::with_parent(func.env); // Add the arguments to the function env for (ident, value) in func.args.into_iter().zip(args) { @@ -358,38 +361,35 @@ impl Interpreter { } } - fn interpret_field_get(&mut self, object: Expr, field: Literal) -> Result { + 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(make(ErrorMsg::InvalidObject, ty.to_string())); + return Err(runtime_error(ErrorMsg::InvalidObject, ty.to_string())); }; - let Literal::Ident(name) = field else { - unreachable!("object fields cannot be non-identifiers") - }; - instance.get(name) + instance.get(field.name) } fn interpret_field_set( &mut self, object: Expr, - field: Literal, + field: Ident, expr: Expr, ) -> Result { + let Expr::Ident(ident) = &object else { + unreachable!("non-identifiers cannot be passed to this function"); + }; let ty = self.interpret_expr(object.clone())?; let Type::Instance(mut instance) = ty else { - return Err(make(ErrorMsg::InvalidObject, ty.to_string())); - }; - let Literal::Ident(field_name) = field else { - unreachable!("object fields cannot be non-identifiers") + return Err(runtime_error(ErrorMsg::InvalidObject, ty.to_string())); }; let value = self.interpret_expr(expr)?; - instance.set(field_name, value)?; - let Expr::Literal(Literal::Ident(instance_name)) = object else { - unreachable!("assignees cannot be non-identifiers") - }; - self.env - .borrow_mut() - .assign(instance_name, Type::Instance(instance)) + instance.set(field.name, value)?; + self.env.borrow_mut().assign_at_depth( + instance.to_string(), + Type::Instance(instance), + *self.depths.get(ident).expect("unresolved object"), + )?; + Ok(Type::Null) } fn is_eq(&self, left: &Type, right: &Type) -> bool { @@ -403,17 +403,22 @@ impl Interpreter { #[cfg(test)] mod tests { + use lost_syntax::{ast::Ident, token::TextRange}; + use super::*; #[test] fn let_stmt() { let mut interpreter = Interpreter::new(); let var = "x".to_string(); - let name = Literal::Ident(var.clone()); + let name = Ident { + name: var.clone(), + range: TextRange { start: 0, end: 0 }, + }; let init = Some(Expr::Literal(Literal::Number(5.0))); assert!(interpreter.interpret_let_stmt(name, init).is_ok()); assert_eq!( - interpreter.env.borrow().get(var).unwrap(), + interpreter.env.borrow().get_at_depth(var, 0).unwrap(), Type::Number(5.0) ); } @@ -444,13 +449,24 @@ mod tests { let mut interpreter = Interpreter::new(); let items = vec![ Item::LetStmt { - name: Literal::Ident("x".to_string()), + ident: Ident { + name: "x".to_string(), + range: TextRange { start: 0, end: 0 }, + }, init: Some(Expr::Literal(Literal::Number(5.0))), }, - Item::Block(vec![Item::ExprStmt(Expr::Literal(Literal::Ident( - "x".to_string(), - )))]), + Item::Block(vec![Item::ExprStmt(Expr::Ident(Ident { + name: "x".to_string(), + range: TextRange { start: 0, end: 0 }, + }))]), ]; + interpreter.depths.insert( + Ident { + name: "x".to_string(), + range: TextRange { start: 0, end: 0 }, + }, + 1, + ); assert!(interpreter .interpret_block(items, Env::with_parent(Rc::clone(&interpreter.env))) .is_ok()); @@ -477,7 +493,17 @@ mod tests { .env .borrow_mut() .set("x".to_string(), Type::Number(5.0)); - let result = interpreter.interpret_expr(Expr::Literal(Literal::Ident("x".to_string()))); + interpreter.depths.insert( + Ident { + name: "x".to_string(), + range: TextRange { start: 0, end: 0 }, + }, + 0, + ); + let result = interpreter.interpret_expr(Expr::Ident(Ident { + name: "x".to_string(), + range: TextRange { start: 0, end: 0 }, + })); assert_eq!(result.unwrap(), Type::Number(5.0)); // Unary minus diff --git a/lost_compile/src/lib.rs b/lost_compile/src/lib.rs index 7ec10e6..6011777 100644 --- a/lost_compile/src/lib.rs +++ b/lost_compile/src/lib.rs @@ -1,15 +1,20 @@ pub mod environment; pub mod error; pub mod interpret; +pub mod resolve; pub mod stdlib; pub mod types; -use crate::error::Exception; +use crate::{error::Exception, resolve::Resolver}; use interpret::Interpreter; use log::trace; use lost_syntax::{lex::Lexer, parse::Parser}; -pub fn run(source: &str, interpreter: &mut Interpreter) -> Result<(), Vec> { +pub fn run( + source: &str, + resolver: &mut Resolver, + interpreter: &mut Interpreter, +) -> Result<(), Vec> { let lexer = Lexer::new(source); trace!("Lexing {source}"); let tokens = lexer.lex_all_sanitised().map_err(|e| { @@ -19,14 +24,13 @@ pub fn run(source: &str, interpreter: &mut Interpreter) -> Result<(), Vec>() })?; - trace!("Interpreting {root:#?}"); - match interpreter.interpret_all(root.items) { - Ok(()) => Ok(()), - Err(e) => Err(vec![e]), - } + trace!("Resolving {root:#?}"); + let depths = resolver.resolve(root.clone()).map_err(|e| vec![e])?; + trace!("Interpreting with depths {depths:?}"); + interpreter.interpret(root, depths).map_err(|e| vec![e]) } diff --git a/lost_compile/src/main.rs b/lost_compile/src/main.rs index 1342b14..234a831 100644 --- a/lost_compile/src/main.rs +++ b/lost_compile/src/main.rs @@ -1,7 +1,7 @@ #[macro_use] extern crate log; -use lost_compile::{interpret::Interpreter, run}; +use lost_compile::{interpret::Interpreter, resolve::Resolver, run}; use std::{ env, fs, io::{self, Write}, @@ -12,7 +12,7 @@ fn main() { debug!("Logging enabled"); let args: Vec = env::args().skip(1).collect(); - debug!("Arguments: {args:#?}"); + trace!("Arguments: {args:#?}"); match args.len() { 0 => run_repl(), @@ -28,8 +28,9 @@ fn main() { fn run_file(file_path: &str) { let source = fs::read_to_string(file_path).expect("failed to read file"); + let mut resolver = Resolver::new(); let mut interpreter = Interpreter::new(); - if let Err(errs) = run(&source, &mut interpreter) { + if let Err(errs) = run(&source, &mut resolver, &mut interpreter) { errs.iter().for_each(|e| eprintln!("{e}")); } } @@ -46,6 +47,7 @@ fn run_repl() { AUTHORS ); let (stdin, mut stdout) = (io::stdin(), io::stdout()); + let mut resolver = Resolver::new(); let mut interpreter = Interpreter::new(); loop { let mut line = String::default(); @@ -56,7 +58,7 @@ fn run_repl() { if n == 0 { break; } - if let Err(errs) = run(&line, &mut interpreter) { + if let Err(errs) = run(&line, &mut resolver, &mut interpreter) { errs.iter().for_each(|e| eprintln!("{e}")); } } diff --git a/lost_compile/src/resolve.rs b/lost_compile/src/resolve.rs new file mode 100644 index 0000000..4eb82a5 --- /dev/null +++ b/lost_compile/src/resolve.rs @@ -0,0 +1,260 @@ +use std::collections::HashMap; + +use lost_syntax::ast::{Expr, Ident, Item, Source}; + +use crate::{ + error::{resolution_error, ErrorMsg, Exception}, + stdlib, +}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Node { + Function, + Method, + Class, +} + +#[derive(Debug, Default)] +pub struct Resolver { + scopes: Vec>, + depths: HashMap, + node: Option, +} + +impl Resolver { + pub fn new() -> Self { + let mut scope = HashMap::default(); + stdlib::init_symbols(&mut scope); + Self { + scopes: vec![scope], + ..Default::default() + } + } + + pub fn resolve(&mut self, source: Source) -> Result, Exception> { + self.resolve_all(source.items) + .and_then(|_| Ok(self.depths.clone())) + } + + pub fn resolve_all(&mut self, items: Vec) -> Result<(), Exception> { + items + .into_iter() + .try_for_each(|item| self.resolve_item(item)) + } + + fn resolve_item(&mut self, item: Item) -> Result<(), Exception> { + match item { + Item::ExprStmt(expr) => self.resolve_expr(expr), + Item::LetStmt { ident, init } => self.resolve_let_stmt(ident, init), + Item::IfStmt { + condition, + if_item, + else_item, + } => self.resolve_if_stmt(condition, *if_item, else_item.map(|item| *item)), + 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::Class { + ident: name, + methods, + } => self.resolve_class(name, methods), + } + } + + fn resolve_block(&mut self, items: Vec) -> Result<(), Exception> { + self.init_scope(); + self.resolve_all(items)?; + self.end_scope(); + Ok(()) + } + + fn resolve_let_stmt(&mut self, ident: Ident, init: Option) -> Result<(), Exception> { + self.declare(ident.name.clone())?; + if let Some(expr) = init { + self.resolve_expr(expr)?; + } + self.define(ident.name)?; + Ok(()) + } + + fn resolve_if_stmt( + &mut self, + condition: Expr, + if_item: Item, + else_item: Option, + ) -> Result<(), Exception> { + self.resolve_expr(condition)?; + self.resolve_item(if_item)?; + if let Some(item) = else_item { + self.resolve_item(item)?; + } + Ok(()) + } + + fn resolve_while_stmt(&mut self, condition: Expr, body: Item) -> Result<(), Exception> { + self.resolve_expr(condition)?; + self.resolve_item(body)?; + Ok(()) + } + + fn resolve_return_stmt(&mut self, expr: Expr) -> Result<(), Exception> { + if self.node.is_none() { + return Err(resolution_error( + ErrorMsg::ReturnOutsideFunction, + String::default(), + )); + } + self.resolve_expr(expr) + } + + fn resolve_func_decl( + &mut self, + ident: Ident, + args: Vec, + body: Vec, + ) -> Result<(), Exception> { + self.declare(ident.name.clone())?; + self.define(ident.name)?; + self.resolve_function(args, body, Node::Function)?; + Ok(()) + } + + fn resolve_function( + &mut self, + args: Vec, + body: Vec, + object: Node, + ) -> Result<(), Exception> { + let old = self.node; + self.node = Some(object); + self.init_scope(); + + for arg in args { + self.declare(arg.name.clone())?; + self.define(arg.name)?; + } + self.resolve_all(body)?; + + self.end_scope(); + self.node = old; + Ok(()) + } + + fn resolve_class(&mut self, ident: Ident, methods: Vec) -> Result<(), Exception> { + let old = self.node; + self.node = Some(Node::Class); + self.declare(ident.name.clone())?; + self.define(ident.name)?; + self.init_scope(); + + self.define("this".to_string())?; + for method in methods { + if let Item::Function { args, body, .. } = method { + self.resolve_function(args, body, Node::Method)?; + } else { + unreachable!("non-functions cannot be passed as methods"); + } + } + + self.end_scope(); + self.node = old; + Ok(()) + } + + fn resolve_expr(&mut self, expr: Expr) -> Result<(), Exception> { + match expr { + Expr::Assignment { name, value } => self.resolve_assignment(name, *value), + Expr::Literal(_) => Ok(()), + Expr::Ident(ident) => self.resolve_ident(ident), + Expr::Unary { expr, .. } => self.resolve_expr(*expr), + Expr::Binary { lhs, rhs, .. } | Expr::Logical { lhs, rhs, .. } => self + .resolve_expr(*lhs) + .and_then(|_| self.resolve_expr(*rhs)), + Expr::Group(e) => self.resolve_expr(*e), + Expr::Call { func, args } => self.resolve_func_call(*func, args), + Expr::FieldGet { object, .. } => self.resolve_expr(*object), + Expr::FieldSet { object, value, .. } => self + .resolve_expr(*object) + .and_then(|_| self.resolve_expr(*value)), + } + } + + 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(), + )); + } + + if let Some(&value) = self.scopes.last().and_then(|s| s.get(&ident.name)) { + if !value { + return Err(resolution_error(ErrorMsg::SelfIntialiser, ident.name)); + } + }; + + self.resolve_variable(ident)?; + + Ok(()) + } + + fn resolve_assignment(&mut self, ident: Ident, expr: Expr) -> Result<(), Exception> { + self.resolve_expr(expr) + .and_then(|_| self.resolve_variable(ident)) + } + + fn resolve_func_call(&mut self, fn_expr: Expr, arg_exprs: Vec) -> Result<(), Exception> { + self.resolve_expr(fn_expr)?; + for arg in arg_exprs { + self.resolve_expr(arg)?; + } + Ok(()) + } + + fn resolve_variable(&mut self, lit: Ident) -> Result<(), Exception> { + if let Some((i, _)) = self + .scopes + .iter() + .rev() + .enumerate() + .find(|(_, s)| s.contains_key(&lit.name)) + { + self.depths.insert(lit, i); + Ok(()) + } else { + Err(resolution_error(ErrorMsg::UndefinedVar, lit.name)) + } + } + + fn init_scope(&mut self) { + self.scopes.push(HashMap::default()); + } + + fn end_scope(&mut self) { + self.scopes.pop(); + } + + fn declare(&mut self, name: String) -> Result<(), Exception> { + self.scopes + .last_mut() + .ok_or(resolution_error(ErrorMsg::NoScope, name.clone())) + .map(|s| { + s.insert(name, false); + }) + } + + fn define(&mut self, name: String) -> Result<(), Exception> { + self.scopes + .last_mut() + .ok_or(resolution_error(ErrorMsg::NoScope, name.clone())) + .map(|s| { + s.insert(name, true); + }) + } +} diff --git a/lost_compile/src/stdlib.rs b/lost_compile/src/stdlib.rs index 9a2c103..a87ea19 100644 --- a/lost_compile/src/stdlib.rs +++ b/lost_compile/src/stdlib.rs @@ -1,3 +1,8 @@ +use std::{ + collections::HashMap, + time::{self, UNIX_EPOCH}, +}; + use crate::{ environment::Env, types::{ @@ -6,17 +11,48 @@ use crate::{ }, }; -pub fn init_io(env: &mut Env) { - // print(args) - env.set( - "print".to_string(), - NativeFunc(types::NativeFunc { - name: "print".to_string(), - args: vec!["arg".to_string()], - body: |_, args| { - println!("{}", args[0]); - Ok(Type::Null) - }, - }), - ); +/// Initialises the environment with stdlib functions +/// at the global level for the interpreter. +pub fn init(env: &mut Env) { + let fns: [(&str, Type); 2] = [ + ( + "print", + NativeFunc(types::NativeFunc { + name: "print".to_string(), + args: vec!["arg".to_string()], + body: |_, args| { + println!("{}", args[0]); + Ok(Type::Null) + }, + }), + ), + ( + "clock", + NativeFunc(types::NativeFunc { + name: "clock".to_string(), + args: vec![], + body: |_, _| { + Ok(Type::Number( + time::SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("failed to calculate time") + .as_secs_f64(), + )) + }, + }), + ), + ]; + + fns.into_iter().for_each(|(name, f)| { + env.set(name.to_string(), f); + }); +} + +/// Initialises the scope with stdlib functions for the resolver +pub fn init_symbols(map: &mut HashMap) { + const SYMBOLS: [&str; 2] = ["print", "clock"]; + + SYMBOLS.into_iter().for_each(|s| { + map.insert(s.to_string(), true); + }); } diff --git a/lost_compile/src/types.rs b/lost_compile/src/types.rs index 5d0ec24..0fe6fed 100644 --- a/lost_compile/src/types.rs +++ b/lost_compile/src/types.rs @@ -9,7 +9,7 @@ use lost_syntax::ast::Item; use crate::{ environment::Env, - error::{make, ErrorMsg, Exception}, + error::{runtime_error, ErrorMsg, Exception}, interpret::Interpreter, }; @@ -64,7 +64,7 @@ impl Callable for Func { self.args.len() } fn call(&self, interpreter: &mut Interpreter, args: Vec) -> Result { - interpreter.call_func(self.clone(), args) + interpreter.call(self.clone(), args) } } @@ -166,7 +166,7 @@ impl Instance { } // The constructor must be fetched or explicitly called if self.class.name == member { - return Err(make(ErrorMsg::GetConstructor, member)); + return Err(runtime_error(ErrorMsg::GetConstructor, member)); } if let Some(mut func) = self.class.methods.get(&member).cloned() { func.env = Env::with_parent(func.env); @@ -175,7 +175,7 @@ impl Instance { .set("this".to_string(), Type::Instance(self.clone())); return Ok(Type::Func(func)); } - Err(make(ErrorMsg::UndefinedMember, member)) + Err(runtime_error(ErrorMsg::UndefinedMember, member)) } pub fn set(&mut self, field: String, value: Type) -> Result { diff --git a/lost_playground/src/lib.rs b/lost_playground/src/lib.rs index 9785841..f4c4d1b 100644 --- a/lost_playground/src/lib.rs +++ b/lost_playground/src/lib.rs @@ -1,12 +1,7 @@ -use lost_compile::{ - environment::Env, - interpret::Interpreter, - run, - types::{ - self, - Type::{self, NativeFunc}, - }, -}; +mod stdlib; + +use crate::stdlib::init; +use lost_compile::{environment::Env, interpret::Interpreter, resolve::Resolver, run, types::Type}; use std::env; use wasm_bindgen::prelude::*; @@ -15,7 +10,7 @@ use wasm_bindgen::prelude::*; static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; #[wasm_bindgen] -pub fn init() -> String { +pub fn init_repl() -> String { #[cfg(feature = "console_error_panic_hook")] console_error_panic_hook::set_once(); @@ -34,30 +29,36 @@ pub fn init() -> String { #[wasm_bindgen] #[derive(Default)] pub struct World { + resolver: Resolver, interpreter: Interpreter, } -const REPL_OUTPUT_VAR: &str = "REPL_OUTPUT"; +pub(crate) const REPL_OUTPUT_VAR: &str = "REPL_OUTPUT"; #[wasm_bindgen] impl World { #[wasm_bindgen(constructor)] pub fn new() -> Self { - let world = Self::default(); let mut env = Env::default(); // Initialise output variable. This is a hack // to print to JS since stdout itself can't be // captured and piped into a JS value. env.set(REPL_OUTPUT_VAR.to_string(), Type::Str(String::default())); // Add stdlib functions - init_io(&mut env); - world.interpreter.env.replace(env); - world + init(&mut env); + + let interpreter = Interpreter::default(); + interpreter.env.replace(env); + + World { + resolver: Resolver::new(), + interpreter, + } } pub fn run(&mut self, src: &str) -> Result { clear_output(&self.interpreter); - match run(src, &mut self.interpreter) { + match run(src, &mut self.resolver, &mut self.interpreter) { Ok(()) => Ok(self .interpreter .env @@ -78,41 +79,6 @@ fn clear_output(interpreter: &Interpreter) { interpreter .env .borrow_mut() - .assign(REPL_OUTPUT_VAR.to_string(), Type::Str(String::default())) + .assign_at_depth(REPL_OUTPUT_VAR.to_string(), Type::Str(String::default()), 0) .expect("no output variable present"); } - -/// Override the default init_io from stdlib -/// since `println!` cannot be used with wasm -fn init_io(env: &mut Env) { - // print(args) - env.set( - "print".to_string(), - NativeFunc(types::NativeFunc { - name: "print".to_string(), - args: vec!["arg".to_string()], - body: |interpreter, args| { - let Type::Str(output) = - interpreter.env.borrow().get(REPL_OUTPUT_VAR.to_string())? - else { - unreachable!("The output value cannot be a non-string type"); - }; - interpreter - .env - .borrow_mut() - .assign( - REPL_OUTPUT_VAR.to_string(), - // The string must be appended to any - // output that has not been written yet - Type::Str(if output.is_empty() { - args[0].to_string() - } else { - output + "\n" + &args[0].to_string() - }), - ) - .expect("no output variable present"); - Ok(Type::Null) - }, - }), - ); -} diff --git a/lost_playground/src/stdlib.rs b/lost_playground/src/stdlib.rs new file mode 100644 index 0000000..f35d35c --- /dev/null +++ b/lost_playground/src/stdlib.rs @@ -0,0 +1,44 @@ +use lost_compile::{ + environment::Env, + types::{ + self, + Type::{self, NativeFunc}, + }, +}; + +use crate::REPL_OUTPUT_VAR; + +/// Override the default init_io from stdlib +/// since `println!` cannot be used with wasm +pub fn init(env: &mut Env) { + // print(args) + env.set( + "print".to_string(), + NativeFunc(types::NativeFunc { + name: "print".to_string(), + args: vec!["arg".to_string()], + body: |interpreter, args| { + let Type::Str(output) = + interpreter.env.borrow().get(REPL_OUTPUT_VAR.to_string())? + else { + unreachable!("The output value cannot be a non-string type"); + }; + interpreter + .env + .borrow_mut() + .assign( + REPL_OUTPUT_VAR.to_string(), + // The string must be appended to any + // output that has not been written yet + Type::Str(if output.is_empty() { + args[0].to_string() + } else { + output + "\n" + &args[0].to_string() + }), + ) + .expect("no output variable present"); + Ok(Type::Null) + }, + }), + ); +} diff --git a/lost_syntax/src/ast.rs b/lost_syntax/src/ast.rs index 626edbd..60aedec 100644 --- a/lost_syntax/src/ast.rs +++ b/lost_syntax/src/ast.rs @@ -1,8 +1,8 @@ -use std::fmt::Display; +use std::{fmt::Display, hash::Hash}; -use crate::token::TokenKind; +use crate::token::{TextRange, TokenKind}; -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub struct Source { pub items: Vec, } @@ -10,16 +10,16 @@ pub struct Source { #[derive(Clone, Debug, PartialEq)] pub enum Item { Class { - name: Literal, + ident: Ident, methods: Vec, }, Function { - name: Literal, - args: Vec, + ident: Ident, + args: Vec, body: Vec, }, LetStmt { - name: Literal, + ident: Ident, init: Option, }, ExprStmt(Expr), @@ -127,9 +127,18 @@ impl LogicalOp { } } +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Ident { + pub name: String, + // Identifiers in different scopes may have the same name, + // but are not identical since they refer to different variables. + // The text range ensures that there is no collision between + // these identfiers during resolution. + pub range: TextRange, +} + #[derive(Clone, Debug, PartialEq)] pub enum Literal { - Ident(String), Number(f64), Str(String), Boolean(bool), @@ -139,7 +148,6 @@ pub enum Literal { impl Display for Literal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&match self { - Self::Ident(name) => name.to_owned(), Self::Number(n) => n.to_string(), Self::Str(s) => s.to_owned(), Self::Boolean(b) => b.to_string(), @@ -151,9 +159,10 @@ impl Display for Literal { #[derive(Clone, Debug, PartialEq)] pub enum Expr { Assignment { - name: Literal, + name: Ident, value: Box, }, + Ident(Ident), Literal(Literal), Unary { op: UnaryOp, @@ -176,11 +185,11 @@ pub enum Expr { }, FieldGet { object: Box, - field: Literal, + field: Ident, }, FieldSet { object: Box, - field: Literal, + field: Ident, value: Box, }, } diff --git a/lost_syntax/src/parse.rs b/lost_syntax/src/parse.rs index 0d8fe8c..cff47b1 100644 --- a/lost_syntax/src/parse.rs +++ b/lost_syntax/src/parse.rs @@ -1,7 +1,7 @@ use std::{iter::Peekable, slice::Iter}; use crate::{ - ast::{self, Expr, Item, Literal, Source}, + ast::{self, Expr, Ident, Item, Literal, Source}, error::{Error, ErrorMsg}, token::{Token, TokenKind}, }; @@ -18,11 +18,11 @@ impl<'a> Parser<'a> { } } - pub fn parse_all(mut self) -> Result> { + pub fn parse(mut self) -> Result> { let mut items: Vec = Vec::default(); let mut errors: Vec = Vec::default(); while self.stream.peek().is_some() { - match self.parse() { + match self.parse_item() { Ok(stmt) => items.push(stmt), Err(e) => { errors.push(e); @@ -34,10 +34,6 @@ impl<'a> Parser<'a> { errors.is_empty().then(|| Source { items }).ok_or(errors) } - pub fn parse(&mut self) -> Result { - self.parse_item() - } - pub fn parse_item(&mut self) -> Result { match self.stream.peek() { Some(&t) => { @@ -70,7 +66,10 @@ impl<'a> Parser<'a> { }; Ok(Item::LetStmt { - name: Literal::Ident(name.lexeme.clone()), + ident: Ident { + name: name.lexeme.clone(), + range: name.range, + }, init, }) } @@ -170,10 +169,11 @@ impl<'a> Parser<'a> { fn parse_class(&mut self) -> Result { // Consume the `class` keyword self.advance(); - let name = Literal::Ident( - self.advance_or_err(TokenKind::IDENT, ErrorMsg::InvalidIdent)? - .lexeme, - ); + let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::InvalidIdent)?; + let ident = Ident { + name: name.lexeme.clone(), + range: name.range, + }; self.advance_or_err(TokenKind::LBRACE, ErrorMsg::MissingOpeningBrace)?; let mut methods = vec![]; while self @@ -186,16 +186,17 @@ impl<'a> Parser<'a> { } self.advance_or_err(TokenKind::RBRACE, ErrorMsg::MissingClosingBrace)?; - Ok(Item::Class { name, methods }) + Ok(Item::Class { ident, methods }) } fn parse_function(&mut self) -> Result { // Consume the `fn` keyword self.advance(); - let name = Literal::Ident( - self.advance_or_err(TokenKind::IDENT, ErrorMsg::InvalidIdent)? - .lexeme, - ); + let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::InvalidIdent)?; + let ident = Ident { + name: name.lexeme.clone(), + range: name.range, + }; self.advance_or_err(TokenKind::LPAREN, ErrorMsg::MissingOpeningParen)?; let mut args = vec![]; while self @@ -204,10 +205,11 @@ impl<'a> Parser<'a> { .filter(|t| t.kind == TokenKind::RPAREN) .is_none() { - args.push(Literal::Ident( - self.advance_or_err(TokenKind::IDENT, ErrorMsg::InvalidIdent)? - .lexeme, - )); + let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::InvalidIdent)?; + args.push(Ident { + name: name.lexeme.clone(), + range: name.range, + }); if self.advance_if(|t| t.kind == TokenKind::COMMA).is_none() { break; } @@ -222,7 +224,7 @@ impl<'a> Parser<'a> { unreachable!("parsing a block must return a body") }; - Ok(Item::Function { name, args, body }) + Ok(Item::Function { ident, args, body }) } fn parse_return(&mut self) -> Result { @@ -289,26 +291,25 @@ impl<'a> Parser<'a> { }; let rhs = self.parse_assignment()?; match lhs { - Expr::Literal(lit @ Literal::Ident(_)) => Ok(Expr::Assignment { - name: lit, + Expr::Ident(ident) => Ok(Expr::Assignment { + name: ident, value: Box::new(rhs), }), Expr::FieldGet { object, field } => { - if matches!(field, Literal::Ident(_)) { - Ok(Expr::FieldSet { - object, - field, - value: Box::new(rhs), - }) - } else { - // The error is manually generated as there is - // no single token for the lvalue identifier - Err(format!( - "Parse error at line {}: {}", - eq.line + 1, - ErrorMsg::InvalidField, - )) - } + Ok(Expr::FieldSet { + object, + field, + value: Box::new(rhs), + }) + // } else { + // // The error is manually generated as there is + // // no single token for the lvalue identifier + // Err(format!( + // "Parse error at line {}: {}", + // eq.line + 1, + // ErrorMsg::InvalidField, + // )) + // } } _ => { // The error is manually generated as there is @@ -476,12 +477,13 @@ impl<'a> Parser<'a> { TokenKind::DOT => { // Consume the dot self.advance(); + let name = self.advance_or_err(TokenKind::IDENT, ErrorMsg::InvalidIdent)?; expr = Expr::FieldGet { object: Box::new(expr), - field: Literal::Ident( - self.advance_or_err(TokenKind::IDENT, ErrorMsg::InvalidIdent)? - .lexeme, - ), + field: Ident { + name: name.lexeme.clone(), + range: name.range, + }, } } _ => break, @@ -502,7 +504,12 @@ impl<'a> Parser<'a> { } TokenKind::STRING => Literal::Str(t.lexeme.clone()), TokenKind::LPAREN => return self.parse_group(), - TokenKind::IDENT | TokenKind::THIS => Literal::Ident(t.lexeme.clone()), + TokenKind::IDENT | TokenKind::THIS => { + return Ok(Expr::Ident(Ident { + name: t.lexeme.clone(), + range: t.range, + })) + } _ => return Err(Self::error(t, ErrorMsg::UnexpectedToken)), }, None => return Err(format!("Parse error: {}", ErrorMsg::EndOfStream)), @@ -592,18 +599,18 @@ impl<'a> Parser<'a> { #[cfg(test)] mod tests { use super::*; - use crate::lex::Lexer; + use crate::{lex::Lexer, token::TextRange}; fn parse_test(input: &str, expected: Source) { let tokens = Lexer::new(input).lex_all_sanitised().unwrap(); - let source = Parser::new(&tokens).parse_all().unwrap(); + let source = Parser::new(&tokens).parse().unwrap(); assert_eq!(source, expected); } fn parse_err_test(input: &str, expected: &str) { let tokens = Lexer::new(input).lex_all_sanitised().unwrap(); let err = Parser::new(&tokens) - .parse_all() + .parse() .map_err(|v| v.first().unwrap().to_owned()) .unwrap_err(); assert_eq!(err, expected); @@ -615,7 +622,10 @@ mod tests { "let x = 42;", Source { items: vec![Item::LetStmt { - name: Literal::Ident("x".to_owned()), + ident: Ident { + name: "x".to_owned(), + range: TextRange { start: 0, end: 0 }, + }, init: Some(Expr::Literal(Literal::Number(42.0))), }], }, @@ -673,23 +683,36 @@ mod tests { Source { items: vec![Item::Block(vec![ Item::LetStmt { - name: Literal::Ident(var.clone()), + ident: Ident { + name: var.clone(), + range: TextRange { start: 0, end: 0 }, + }, init: Some(Expr::Literal(Literal::Number(0.0))), }, Item::WhileStmt { condition: Expr::Binary { - lhs: Box::new(Expr::Literal(Literal::Ident(var.clone()))), + lhs: Box::new(Expr::Ident(Ident { + name: var.clone(), + range: TextRange { start: 0, end: 0 }, + })), op: ast::BinOp::Less, rhs: Box::new(Expr::Literal(Literal::Number(5.0))), }, body: Box::new(Item::Block(vec![ - Item::Block(vec![Item::ExprStmt(Expr::Literal(Literal::Ident( - var.clone(), - )))]), + Item::Block(vec![Item::ExprStmt(Expr::Ident(Ident { + name: var.clone(), + range: TextRange { start: 0, end: 0 }, + }))]), Item::ExprStmt(Expr::Assignment { - name: Literal::Ident(var.clone()), + name: Ident { + name: var.clone(), + range: TextRange { start: 0, end: 0 }, + }, value: Box::new(Expr::Binary { - lhs: Box::new(Expr::Literal(Literal::Ident(var.clone()))), + lhs: Box::new(Expr::Ident(Ident { + name: var.clone(), + range: TextRange { start: 0, end: 0 }, + })), op: ast::BinOp::Plus, rhs: Box::new(Expr::Literal(Literal::Number(1.0))), }), @@ -708,11 +731,17 @@ mod tests { Source { items: vec![Item::Block(vec![ Item::LetStmt { - name: Literal::Ident("x".to_owned()), + ident: Ident { + name: "x".to_owned(), + range: TextRange { start: 0, end: 0 }, + }, init: Some(Expr::Literal(Literal::Number(1.0))), }, Item::LetStmt { - name: Literal::Ident("y".to_owned()), + ident: Ident { + name: "y".to_owned(), + range: TextRange { start: 0, end: 0 }, + }, init: Some(Expr::Literal(Literal::Number(2.0))), }, ])], diff --git a/lost_syntax/src/token.rs b/lost_syntax/src/token.rs index 36d9ac6..1729075 100644 --- a/lost_syntax/src/token.rs +++ b/lost_syntax/src/token.rs @@ -1,6 +1,6 @@ use std::{fmt::Display, ops::AddAssign}; -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct TextRange { pub start: usize, pub end: usize, diff --git a/www/index.js b/www/index.js index 5cd35ab..c631d05 100644 --- a/www/index.js +++ b/www/index.js @@ -32,7 +32,7 @@ function init_repl() { repl.elemHistory.scrollTop = repl.elemHistory.scrollHeight; // Update the input history with a blank line repl.inputHistory.push("") - updateHistoryEntry(repl.inputHistoryIndex, true, wasm.init()) + updateHistoryEntry(repl.inputHistoryIndex, true, wasm.init_repl()) } // ----------------------------------------------------------------------------