diff --git a/src/ast/helpers.rs b/src/ast/helpers.rs index a19fadd..5e788a2 100644 --- a/src/ast/helpers.rs +++ b/src/ast/helpers.rs @@ -34,6 +34,7 @@ pub fn stmt_expr(expr: Expr) -> Statement { pub fn define_expr(name: &str, expr: Expr) -> Definition { Definition { + mutable: false, name: name.into(), expr, } diff --git a/src/ast/types.rs b/src/ast/types.rs index 5119c01..dee1b05 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -52,6 +52,7 @@ pub enum Statement { #[derive(PartialEq, PartialOrd, Debug, Clone)] pub struct Definition { + pub mutable: bool, pub name: Name, pub expr: Expr, } @@ -65,9 +66,15 @@ pub enum Value { Boolean(bool), Record(Record), Vector(Vector), + Label(Label), + Ref(Ref), Closure { env: EnvName, expr: Box }, } +/// A mutable variable. +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone)] +pub struct Ref(pub u64); + /// An anonymous function. #[derive(PartialEq, PartialOrd, Debug, Clone)] pub struct Fn { diff --git a/src/interpret/interpret.rs b/src/interpret/interpret.rs index 48cd8c0..93c7f0b 100644 --- a/src/interpret/interpret.rs +++ b/src/interpret/interpret.rs @@ -12,7 +12,7 @@ pub fn run( let env_name = ast::EnvName("global".to_string()); let mut state = State::new("global".into()); - defs_to_env(program.0, &env_name, &mut state.envs)?; + defs_to_env(program.0, &env_name, &mut state)?; let main = ast::Expr::Value(state.envs.get(&env_name)?.get(&func)?.clone()); @@ -39,8 +39,16 @@ fn eval_statement( ) -> Result { match statement { ast::Statement::Expr(expr) => eval_expr(expr_env, state, expr).map(StatementResult::Result), - ast::Statement::Let(ast::Definition { name, expr }) => { - let result = eval_expr(expr_env, state, expr)?; + ast::Statement::Let(ast::Definition { + mutable, + name, + expr, + }) => { + let mut result = eval_expr(expr_env, state, expr)?; + if *mutable { + let reference = state.insert_variable(result); + result = ast::Value::Ref(reference); + } expr_env.insert(name.clone(), result.clone()); Ok(StatementResult::Result(result)) } @@ -142,7 +150,19 @@ fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result { + let rhs = eval_expr(expr_env, state, rhs)?; + if let Some(mut nested_field) = + access_nested_field(expr_env, state, expr, field)? + { + todo!() + } else { + todo!() // error nested field unreachable + } + } */ _ => todo!(), }, _ => { @@ -153,17 +173,45 @@ fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result( + expr_env_name: &Env, + state: &'a mut State, + expr: &ast::Expr, + label: &ast::Label, +) -> Result<&'a mut ast::Value, Error> { + //let lhs = eval_expr(expr_env, state, expr)?; + match expr { + ast::Expr::Var(var) => { + if let Some(mut value) = state.envs.get(&expr_env.name)?.get(&var)? { + match &value { + &ast::Value::Record(record) => { + record. + } + _ => todo!(), + } + } + } + _ => todo!(), + } +} +*/ fn defs_to_env( defs: Vec, env_name: &ast::EnvName, - envs: &mut Envs, + state: &mut State, ) -> Result<(), Error> { let mut env = Env::new(env_name.clone()); for def in defs { - let (name, closure) = match def { - ast::Definition { expr, name } => ( + let (mutable, name, closure) = match def { + ast::Definition { + mutable, + expr, + name, + } => ( + mutable, name, ast::Value::Closure { expr: Box::new(expr.clone()), @@ -171,10 +219,15 @@ fn defs_to_env( }, ), }; - env.insert_nodup(&name, closure)?; + if mutable { + let reference = state.insert_variable(closure); + env.insert_nodup(&name, ast::Value::Ref(reference))?; + } else { + env.insert_nodup(&name, closure)?; + } } - envs.0.insert(env_name.clone(), env); + state.envs.0.insert(env_name.clone(), env); Ok(()) } diff --git a/src/interpret/types.rs b/src/interpret/types.rs index 73c59c8..b20f27a 100644 --- a/src/interpret/types.rs +++ b/src/interpret/types.rs @@ -2,6 +2,7 @@ use crate::ast; use std::collections::BTreeMap; +use std::collections::HashMap; #[derive(PartialEq, Debug, thiserror::Error)] pub enum Error { @@ -51,23 +52,35 @@ impl Namer { pub struct State { pub name: String, - namer: Namer, + env_namer: Namer, pub envs: Envs, + variable_namer: Namer, + pub variables: Variables, } +pub struct Variables(pub HashMap); + impl State { pub fn new(name: String) -> State { State { name, - namer: Namer::new(), + env_namer: Namer::new(), envs: Envs(BTreeMap::new()), + variable_namer: Namer::new(), + variables: Variables(HashMap::new()), } } pub fn generate_env(&mut self, env: Env) -> Result { - let env_name = ast::EnvName(format!("{}_{}", self.name, self.namer.next())); + let env_name = ast::EnvName(format!("{}_{}", self.name, self.env_namer.next())); self.envs.insert(&env_name, env)?; Ok(env_name) } + + pub fn insert_variable(&mut self, value: ast::Value) -> ast::Ref { + let name = ast::Ref(self.variable_namer.next()); + self.variables.0.insert(name.clone(), value); + name + } } #[derive(PartialEq, Debug, Clone)] diff --git a/src/parser/parser.rs b/src/parser/parser.rs index ed229d0..8eacc90 100644 --- a/src/parser/parser.rs +++ b/src/parser/parser.rs @@ -45,20 +45,13 @@ fn parse_set(tokens: &mut Tokens) -> ParseResult { } fn parse_dot(tokens: &mut Tokens, expr: ast::Expr) -> Result { - if let Some(_dot) = tokens.next_if(&Token::Dot) { - if let Some(labels) = tokens.many_sep_by(&Token::Dot, parse_identifier_label)? { - Ok(labels - .into_iter() - .fold(expr, |acc, label| ast::Expr::Access { - expr: Box::new(acc), - field: label, - })) - } else { - Err(Error::UnexpectedToken { - expected: Token::Dot, - got: tokens.next(), - }) - } + if let Some(labels) = tokens.many(parse_label)? { + Ok(labels + .into_iter() + .fold(expr, |acc, label| ast::Expr::Access { + expr: Box::new(acc), + field: label, + })) } else { Ok(expr) } @@ -66,9 +59,17 @@ fn parse_dot(tokens: &mut Tokens, expr: ast::Expr) -> Result { fn parse_definition(tokens: &mut Tokens) -> ParseResult { if let Some(_) = tokens.next_if(&Token::Let) { + let mutable = match tokens.next_if(&Token::Mut) { + Some(_) => true, + _ => false, + }; if let Some(name) = parse_identifier_name(tokens)? { if let Some(expr) = parse_set(tokens)? { - Ok(Some(ast::Definition { name, expr })) + Ok(Some(ast::Definition { + mutable, + name, + expr, + })) } else { Err(Error::UnexpectedToken { expected: Token::Equals, @@ -220,7 +221,7 @@ fn parse_vector(tokens: &mut Tokens) -> ParseResult { } fn parse_record_field_expr(tokens: &mut Tokens) -> ParseResult<(ast::Label, ast::Expr)> { - if let Some(label) = parse_identifier_label(tokens)? { + if let Some(label) = parse_label(tokens)? { if let Some(_) = tokens.next_if(&Token::Colon) { if let Some(expr) = parse_expr(tokens)? { Ok(Some((label, expr))) @@ -277,8 +278,16 @@ fn parse_identifier_name(tokens: &mut Tokens) -> ParseResult { Ok(parse_identifier_string(tokens)?.map(|string| ast::Name(string))) } -fn parse_identifier_label(tokens: &mut Tokens) -> ParseResult { - Ok(parse_identifier_string(tokens)?.map(|string| ast::Label(string))) +fn parse_label(tokens: &mut Tokens) -> ParseResult { + if let Some(LocatedToken { + token: Token::Label(string), + .. + }) = tokens.next_if(&Token::Label("".into())) + { + Ok(Some(ast::Label(string))) + } else { + Ok(None) + } } fn parse_identifier_string(tokens: &mut Tokens) -> ParseResult { @@ -335,7 +344,7 @@ mod tests { } #[test] fn record() { - let program = "{ hello: 17, hi: \"cool\"}".to_string(); + let program = "{ .hello: 17, .hi: \"cool\"}".to_string(); let result = parse(&mut scan(program), parse_expr); insta::assert_debug_snapshot!(result); } @@ -353,7 +362,7 @@ mod tests { } #[test] fn assign() { - let program = "x = [2, { hello: 7 }];".to_string(); + let program = "x = [2, { .hello: 7 }];".to_string(); let result = parse(&mut scan(program), parse_expr); insta::assert_debug_snapshot!(result); } @@ -382,11 +391,17 @@ mod tests { insta::assert_debug_snapshot!(result); } #[test] + fn let_mut_number() { + let program = "let mut x = 108;".to_string(); + let result = parse(&mut scan(program), parse_definition); + insta::assert_debug_snapshot!(result); + } + #[test] fn full_program() { let program = " let init = fn() { return { - player: { position: { x: 10, y: 20 }, }, + .player: { .position: { .x: 10, .y: 20 }, }, }; }; @@ -399,7 +414,7 @@ let draw = fn(frame, state) { }; let migrate = fn(state) { - return { player: { pos: state.player.position } }; + return { .player: { .pos: state.player.position } }; }; " .to_string(); diff --git a/src/parser/scanner.rs b/src/parser/scanner.rs index 392a34a..a4eb480 100644 --- a/src/parser/scanner.rs +++ b/src/parser/scanner.rs @@ -11,11 +11,6 @@ pub fn scan(source: String) -> Tokens { let start = scanner.cursor(); if let Some(c) = scanner.pop() { match *c { - '.' => tokens.push(LocatedToken { - start, - end: scanner.cursor(), - token: Token::Dot, - }), ',' => tokens.push(LocatedToken { start, end: scanner.cursor(), @@ -66,6 +61,31 @@ pub fn scan(source: String) -> Tokens { end: scanner.cursor(), token: Token::Equals, }), + // labels + '.' => { + let mut str = "".to_string(); + loop { + if let Some(ch) = scanner.peek() + && !ch.is_alphanumeric() + && *ch != '_' + { + break; + } + if let Some(ch) = scanner.pop() { + str.push(*ch); + } else { + break; + } + } + + tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: match str.as_str() { + _ => Token::Label(str), + }, + }); + } // comments '#' => { let mut str = "".to_string(); @@ -91,6 +111,7 @@ pub fn scan(source: String) -> Tokens { token: Token::String(str), }); } + // whitespace _ if c.is_whitespace() => {} // numbers _ if c.is_numeric() => { @@ -137,6 +158,7 @@ pub fn scan(source: String) -> Tokens { token: match str.as_str() { "fn" => Token::Fn, "let" => Token::Let, + "mut" => Token::Mut, "return" => Token::Return, "true" => Token::True, "false" => Token::False, diff --git a/src/parser/snapshots/ayin__parser__parser__tests__full_program.snap b/src/parser/snapshots/ayin__parser__parser__tests__full_program.snap index 9155220..07b80cb 100644 --- a/src/parser/snapshots/ayin__parser__parser__tests__full_program.snap +++ b/src/parser/snapshots/ayin__parser__parser__tests__full_program.snap @@ -5,6 +5,7 @@ expression: result Ok( [ Definition { + mutable: false, name: Name( "init", ), @@ -65,6 +66,7 @@ Ok( ), }, Definition { + mutable: false, name: Name( "update", ), @@ -99,6 +101,7 @@ Ok( ), }, Definition { + mutable: false, name: Name( "draw", ), @@ -155,6 +158,7 @@ Ok( ), }, Definition { + mutable: false, name: Name( "migrate", ), diff --git a/src/parser/snapshots/ayin__parser__parser__tests__let_mut_number.snap b/src/parser/snapshots/ayin__parser__parser__tests__let_mut_number.snap new file mode 100644 index 0000000..4ef1919 --- /dev/null +++ b/src/parser/snapshots/ayin__parser__parser__tests__let_mut_number.snap @@ -0,0 +1,17 @@ +--- +source: src/parser/parser.rs +expression: result +--- +Ok( + Definition { + mutable: true, + name: Name( + "x", + ), + expr: Value( + Int( + 108, + ), + ), + }, +) diff --git a/src/parser/snapshots/ayin__parser__parser__tests__let_number.snap b/src/parser/snapshots/ayin__parser__parser__tests__let_number.snap index 8dc972a..087a843 100644 --- a/src/parser/snapshots/ayin__parser__parser__tests__let_number.snap +++ b/src/parser/snapshots/ayin__parser__parser__tests__let_number.snap @@ -4,6 +4,7 @@ expression: result --- Ok( Definition { + mutable: false, name: Name( "x", ), diff --git a/src/parser/snapshots/ayin__parser__scanner__tests__let_fn.snap b/src/parser/snapshots/ayin__parser__scanner__tests__let_fn.snap index 3ad6382..a5f797a 100644 --- a/src/parser/snapshots/ayin__parser__scanner__tests__let_fn.snap +++ b/src/parser/snapshots/ayin__parser__scanner__tests__let_fn.snap @@ -18,8 +18,7 @@ expression: result Identifier( "console", ), - Dot, - Identifier( + Label( "log", ), OpenParen, diff --git a/src/parser/snapshots/ayin__parser__scanner__tests__scaffolding.snap b/src/parser/snapshots/ayin__parser__scanner__tests__scaffolding.snap index 245b69f..28ceab5 100644 --- a/src/parser/snapshots/ayin__parser__scanner__tests__scaffolding.snap +++ b/src/parser/snapshots/ayin__parser__scanner__tests__scaffolding.snap @@ -88,8 +88,7 @@ expression: result Identifier( "frame", ), - Dot, - Identifier( + Label( "clear", ), OpenParen, @@ -134,12 +133,10 @@ expression: result Identifier( "state", ), - Dot, - Identifier( + Label( "player", ), - Dot, - Identifier( + Label( "position", ), CloseCurly, diff --git a/src/parser/types.rs b/src/parser/types.rs index 232116f..7b9d9e4 100644 --- a/src/parser/types.rs +++ b/src/parser/types.rs @@ -9,6 +9,7 @@ pub struct LocatedToken { pub enum Token { Let, Fn, + Mut, Return, Equals, OpenParen, @@ -17,7 +18,6 @@ pub enum Token { CloseBracket, OpenCurly, CloseCurly, - Dot, Comma, Semicolon, Colon, @@ -26,6 +26,7 @@ pub enum Token { Number(u32), String(String), Identifier(String), + Label(String), } #[derive(Debug, Clone, PartialEq, Eq)] @@ -52,6 +53,7 @@ impl Tokens { (Token::Identifier(_), Token::Identifier(_)) => true, (Token::Number(_), Token::Number(_)) => true, (Token::String(_), Token::String(_)) => true, + (Token::Label(_), Token::Label(_)) => true, _ => false, }) }