diff --git a/crates/compiler/src/codegen.rs b/crates/compiler/src/codegen.rs index 81fd08cf..3b6ebf5c 100644 --- a/crates/compiler/src/codegen.rs +++ b/crates/compiler/src/codegen.rs @@ -30,7 +30,7 @@ use crate::labels::{JumpLabel, Label, Offset}; use crate::names::{Name, Names, UnboundName}; use crate::opcode::Op::Jump; use crate::opcode::{Op, ScatterArgs, ScatterLabel}; -use crate::parse::parse_program; +use crate::parse::{parse_program, CompileOptions}; use crate::program::Program; use moor_values::model::CompileError; @@ -841,12 +841,12 @@ impl CodegenState { } } -pub fn compile(program: &str) -> Result { +pub fn compile(program: &str, options: CompileOptions) -> Result { let compile_span = tracing::trace_span!("compile"); let _compile_guard = compile_span.enter(); let builtins = make_builtin_offsets(); - let parse = parse_program(program)?; + let parse = parse_program(program, options)?; // Generate the code into 'cg_state'. let mut cg_state = CodegenState::new(parse.names, parse.names_mapping, builtins); diff --git a/crates/compiler/src/codegen_tests.rs b/crates/compiler/src/codegen_tests.rs index 6d5dd5c9..3235168a 100644 --- a/crates/compiler/src/codegen_tests.rs +++ b/crates/compiler/src/codegen_tests.rs @@ -17,19 +17,19 @@ mod tests { use crate::builtins::BUILTIN_DESCRIPTORS; use crate::codegen::compile; use crate::labels::{Label, Offset}; + use crate::opcode::Op::*; + use crate::opcode::{ScatterArgs, ScatterLabel}; + use crate::CompileOptions; use moor_values::model::CompileError; use moor_values::var::Error::{E_INVARG, E_INVIND, E_PERM, E_PROPNF, E_RANGE}; use moor_values::var::Objid; use moor_values::var::Symbol; use moor_values::SYSTEM_OBJECT; - use crate::opcode::Op::*; - use crate::opcode::{ScatterArgs, ScatterLabel}; - #[test] fn test_simple_add_expr() { let program = "1 + 2;"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); assert_eq!( *binary.main_vector.as_ref(), vec![ImmInt(1), ImmInt(2), Add, Pop, Done] @@ -39,7 +39,7 @@ mod tests { #[test] fn test_var_assign_expr() { let program = "a = 1 + 2;"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let a = binary.find_var("a"); /* @@ -60,7 +60,7 @@ mod tests { #[test] fn test_var_assign_retr_expr() { let program = "a = 1 + 2; return a;"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let a = binary.find_var("a"); @@ -82,7 +82,7 @@ mod tests { #[test] fn test_if_stmt() { let program = "if (1 == 2) return 5; elseif (2 == 3) return 3; else return 6; endif"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); /* 0: 124 NUM 1 @@ -137,7 +137,7 @@ mod tests { #[test] fn test_while_stmt() { let program = "while (1) x = x + 1; endwhile"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let x = binary.find_var("x"); @@ -176,7 +176,7 @@ mod tests { #[test] fn test_while_label_stmt() { let program = "while chuckles (1) x = x + 1; if (x > 5) break chuckles; endif endwhile"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let x = binary.find_var("x"); let chuckles = binary.find_var("chuckles"); @@ -229,7 +229,7 @@ mod tests { #[test] fn test_while_break_continue_stmt() { let program = "while (1) x = x + 1; if (x == 5) break; else continue; endif endwhile"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let x = binary.find_var("x"); @@ -293,7 +293,7 @@ mod tests { #[test] fn test_for_in_list_stmt() { let program = "for x in ({1,2,3}) b = x + 5; endfor"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let b = binary.find_var("b"); let x = binary.find_var("x"); @@ -347,7 +347,7 @@ mod tests { #[test] fn test_for_range() { let program = "for n in [1..5] player:tell(a); endfor"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let player = binary.find_var("player"); let a = binary.find_var("a"); @@ -392,7 +392,7 @@ mod tests { #[test] fn test_fork() { let program = "fork (5) player:tell(\"a\"); endfork"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let player = binary.find_var("player"); let a = binary.find_literal("a".into()); @@ -426,7 +426,7 @@ mod tests { #[test] fn test_fork_id() { let program = "fork fid (5) player:tell(fid); endfork"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let player = binary.find_var("player"); let fid = binary.find_var("fid"); @@ -460,7 +460,7 @@ mod tests { #[test] fn test_and_or() { let program = "a = (1 && 2 || 3);"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let a = binary.find_var("a"); /* @@ -492,7 +492,7 @@ mod tests { #[test] fn test_unknown_builtin_call() { let program = "bad_builtin(1, 2, 3);"; - let parse = compile(program); + let parse = compile(program, CompileOptions::default()); assert!(matches!( parse, Err(CompileError::UnknownBuiltinFunction(_)) @@ -502,7 +502,7 @@ mod tests { #[test] fn test_known_builtin() { let program = "disassemble(player, \"test\");"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let player = binary.find_var("player"); let test = binary.find_literal("test".into()); @@ -531,7 +531,7 @@ mod tests { #[test] fn test_cond_expr() { let program = "a = (1 == 2 ? 3 | 4);"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let a = binary.find_var("a"); @@ -566,7 +566,7 @@ mod tests { #[test] fn test_verb_call() { let program = "player:tell(\"test\");"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let player = binary.find_var("player"); let tell = binary.find_literal("tell".into()); @@ -597,7 +597,7 @@ mod tests { #[test] fn test_string_get() { let program = "return \"test\"[1];"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); assert_eq!( *binary.main_vector.as_ref(), vec![Imm(0.into()), ImmInt(1), Ref, Return, Done] @@ -607,7 +607,7 @@ mod tests { #[test] fn test_string_get_range() { let program = "return \"test\"[1..2];"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); assert_eq!( *binary.main_vector.as_ref(), vec![Imm(0.into()), ImmInt(1), ImmInt(2), RangeRef, Return, Done] @@ -617,7 +617,7 @@ mod tests { #[test] fn test_index_set() { let program = "a[2] = \"3\";"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let a = binary.find_var("a"); assert_eq!( @@ -640,7 +640,7 @@ mod tests { #[test] fn test_range_set() { let program = "a[2..4] = \"345\";"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let a = binary.find_var("a"); assert_eq!( @@ -664,7 +664,7 @@ mod tests { #[test] fn test_list_get() { let program = "return {1,2,3}[1];"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); assert_eq!( *binary.main_vector.as_ref(), vec![ @@ -685,7 +685,7 @@ mod tests { #[test] fn test_list_get_range() { let program = "return {1,2,3}[1..2];"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); assert_eq!( *binary.main_vector.as_ref(), vec![ @@ -707,7 +707,7 @@ mod tests { #[test] fn test_range_length() { let program = "a = {1, 2, 3}; b = a[2..$];"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let a = binary.find_var("a"); let b = binary.find_var("b"); @@ -755,7 +755,7 @@ mod tests { #[test] fn test_list_splice() { let program = "return {@args[1..2]};"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); /* 0: 076 PUSH args @@ -784,7 +784,7 @@ mod tests { #[test] fn test_try_finally() { let program = "try a=1; finally a=2; endtry"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let a = binary.find_var("a"); /* @@ -821,7 +821,7 @@ mod tests { #[test] fn test_try_excepts() { let program = "try a=1; except a (E_INVARG) a=2; except b (E_PROPNF) a=3; endtry"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let a = binary.find_var("a"); let b = binary.find_var("b"); @@ -888,7 +888,7 @@ mod tests { #[test] fn test_catch_expr() { let program = "x = `x + 1 ! e_propnf, E_PERM => 17';"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); /* 0: 100 000 PUSH_LITERAL E_PROPNF 2: 016 * MAKE_SINGLETON_LIST @@ -935,7 +935,7 @@ mod tests { #[test] fn test_catch_any_expr() { let program = "return `raise(E_INVARG) ! ANY';"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); /* 0: 123 NUM 0 @@ -978,7 +978,7 @@ mod tests { #[test] fn test_sysobjref() { let program = "$string_utils:from_list(test_string);"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let string_utils = binary.find_literal("string_utils".into()); let from_list = binary.find_literal("from_list".into()); @@ -1011,7 +1011,7 @@ mod tests { #[test] fn test_sysverbcall() { let program = "$verb_metadata(#1, 1);"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let verb_metadata = binary.find_literal("verb_metadata".into()); assert_eq!( *binary.main_vector.as_ref(), @@ -1031,7 +1031,7 @@ mod tests { #[test] fn test_basic_scatter_assign() { let program = "{a, b, c} = args;"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let (a, b, c) = ( binary.find_var("a"), binary.find_var("b"), @@ -1065,7 +1065,7 @@ mod tests { #[test] fn test_more_scatter_assign() { let program = "{first, second, ?third = 0} = args;"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let (first, second, third) = ( binary.find_var("first"), binary.find_var("second"), @@ -1104,7 +1104,7 @@ mod tests { #[test] fn test_some_more_scatter_assign() { let program = "{a, b, ?c = 8, @d} = args;"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); /* 0: 076 PUSH args 1: 112 013 004 002 004 @@ -1147,7 +1147,7 @@ mod tests { #[test] fn test_even_more_scatter_assign() { let program = "{a, ?b, ?c = 8, @d, ?e = 9, f} = args;"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let (a, b, c, d, e, f) = ( binary.find_var("a"), binary.find_var("b"), @@ -1200,7 +1200,7 @@ mod tests { #[test] fn test_scatter_precedence() { let program = "{a,b,?c, @d} = {{1,2,player:kill(b)}}[1]; return {a,b,c};"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let (a, b, c, d) = ( binary.find_var("a"), binary.find_var("b"), @@ -1275,7 +1275,7 @@ mod tests { #[test] fn test_indexed_assignment() { let program = r#"this.stack[5] = 5;"#; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); /* 0: 073 PUSH this @@ -1311,7 +1311,7 @@ mod tests { #[test] fn test_assignment_from_range() { let program = r#"x = 1; y = {1,2,3}; x = x + y[2];"#; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let x = binary.find_var("x"); let y = binary.find_var("y"); @@ -1367,7 +1367,7 @@ mod tests { #[test] fn test_get_property() { let program = r#"return this.stack;"#; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); assert_eq!( *binary.main_vector.as_ref(), @@ -1384,7 +1384,7 @@ mod tests { #[test] fn test_call_verb() { let program = r#"#0:test_verb();"#; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); assert_eq!( *binary.main_vector.as_ref(), vec![ @@ -1401,7 +1401,7 @@ mod tests { #[test] fn test_0_arg_return() { let program = r#"return;"#; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); assert_eq!(*binary.main_vector.as_ref(), vec![Return0, Done]) } @@ -1414,7 +1414,7 @@ mod tests { pass = blop; return pass; "#; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let result = binary.find_var("result"); let pass = binary.find_var("pass"); let args = binary.find_var("args"); @@ -1461,6 +1461,7 @@ mod tests { except (E_RANGE) endtry "#, + CompileOptions::default(), ) .unwrap(); @@ -1505,7 +1506,7 @@ mod tests { #[test] fn test_catch_handler_regression() { let prg = "`this ! E_INVIND';"; - let binary = compile(prg).unwrap(); + let binary = compile(prg, CompileOptions::default()).unwrap(); let this = binary.find_var("this"); /* diff --git a/crates/compiler/src/decompile.rs b/crates/compiler/src/decompile.rs index b4c50f12..47094c5c 100644 --- a/crates/compiler/src/decompile.rs +++ b/crates/compiler/src/decompile.rs @@ -987,11 +987,12 @@ mod tests { use crate::parse::parse_program; use crate::parse::Parse; use crate::unparse::annotate_line_numbers; + use crate::CompileOptions; use test_case::test_case; fn parse_decompile(program_text: &str) -> (Parse, Parse) { - let parse_1 = parse_program(program_text).unwrap(); - let binary = compile(program_text).unwrap(); + let parse_1 = parse_program(program_text, CompileOptions::default()).unwrap(); + let binary = compile(program_text, CompileOptions::default()).unwrap(); let mut parse_2 = program_to_tree(&binary).unwrap(); annotate_line_numbers(1, &mut parse_2.stmts); (parse_1, parse_2) diff --git a/crates/compiler/src/lib.rs b/crates/compiler/src/lib.rs index 0c7973b6..6bcd297c 100644 --- a/crates/compiler/src/lib.rs +++ b/crates/compiler/src/lib.rs @@ -35,6 +35,7 @@ pub use crate::decompile::program_to_tree; pub use crate::labels::{JumpLabel, Label, Offset}; pub use crate::names::{Name, UnboundNames}; pub use crate::opcode::{Op, ScatterLabel}; +pub use crate::parse::CompileOptions; pub use crate::program::{Program, EMPTY_PROGRAM}; pub use crate::unparse::unparse; diff --git a/crates/compiler/src/parse.rs b/crates/compiler/src/parse.rs index a078ca32..82810ff4 100644 --- a/crates/compiler/src/parse.rs +++ b/crates/compiler/src/parse.rs @@ -50,835 +50,1009 @@ pub mod moo { pub struct MooParser; } -/// The emitted parse tree from the parse phase of the compiler. -#[derive(Debug)] -pub struct Parse { - pub stmts: Vec, - pub unbound_names: UnboundNames, - pub names: Names, - pub names_mapping: HashMap, +#[derive(Debug, Clone)] +pub struct CompileOptions { + /// Whether we allow lexical scope blocks. begin/end blocks and 'let' and 'global' statements + pub lexical_scopes: bool, } -fn parse_atom( - names: Rc>, - pairs: pest::iterators::Pair, -) -> Result { - match pairs.as_rule() { - Rule::ident => { - let name = names - .borrow_mut() - .find_or_add_name_global(pairs.as_str().trim()); - Ok(Expr::Id(name)) - } - Rule::object => { - let ostr = &pairs.as_str()[1..]; - let oid = i64::from_str(ostr).unwrap(); - let objid = Objid(oid); - Ok(Expr::Value(v_objid(objid))) - } - Rule::integer => match pairs.as_str().parse::() { - Ok(int) => Ok(Expr::Value(v_int(int))), - Err(e) => { - warn!("Failed to parse '{}' to i64: {}", pairs.as_str(), e); - Ok(Expr::Value(v_err(E_INVARG))) - } - }, - Rule::float => { - let float = pairs.as_str().parse::().unwrap(); - Ok(Expr::Value(v_float(float))) - } - Rule::string => { - let string = pairs.as_str(); - let parsed = unquote_str(string)?; - Ok(Expr::Value(v_str(&parsed))) - } - Rule::err => { - let e = pairs.as_str(); - Ok(Expr::Value(match e.to_lowercase().as_str() { - "e_args" => v_err(E_ARGS), - "e_div" => v_err(E_DIV), - "e_float" => v_err(E_FLOAT), - "e_invarg" => v_err(E_INVARG), - "e_invind" => v_err(E_INVIND), - "e_maxrec" => v_err(E_MAXREC), - "e_nacc" => v_err(E_NACC), - "e_none" => v_err(E_NONE), - "e_perm" => v_err(E_PERM), - "e_propnf" => v_err(E_PROPNF), - "e_quota" => v_err(E_QUOTA), - "e_range" => v_err(E_RANGE), - "e_recmove" => v_err(E_RECMOVE), - "e_type" => v_err(E_TYPE), - "e_varnf" => v_err(E_VARNF), - "e_verbnf" => v_err(E_VERBNF), - &_ => { - panic!("unknown error") - } - })) - } - _ => { - panic!("Unimplemented atom: {:?}", pairs); +impl Default for CompileOptions { + fn default() -> Self { + Self { + lexical_scopes: true, } } } -fn parse_exprlist( - names: Rc>, - pairs: pest::iterators::Pairs, -) -> Result, CompileError> { - let mut args = vec![]; - for pair in pairs { - match pair.as_rule() { - Rule::argument => { - let arg = if pair.as_str().starts_with('@') { - Splice(parse_expr( - names.clone(), - pair.into_inner().next().unwrap().into_inner(), - )?) - } else { - Normal(parse_expr( - names.clone(), - pair.into_inner().next().unwrap().into_inner(), - )?) - }; - args.push(arg); +pub struct TreeTransformer { + // TODO: this is Rc, + options: CompileOptions, +} + +impl TreeTransformer { + pub fn new(options: CompileOptions) -> Rc { + Rc::new(Self { + names: RefCell::new(UnboundNames::new()), + options, + }) + } + + fn parse_atom( + self: Rc, + pairs: pest::iterators::Pair, + ) -> Result { + match pairs.as_rule() { + Rule::ident => { + let name = self + .names + .borrow_mut() + .find_or_add_name_global(pairs.as_str().trim()); + Ok(Expr::Id(name)) + } + Rule::object => { + let ostr = &pairs.as_str()[1..]; + let oid = i64::from_str(ostr).unwrap(); + let objid = Objid(oid); + Ok(Expr::Value(v_objid(objid))) + } + Rule::integer => match pairs.as_str().parse::() { + Ok(int) => Ok(Expr::Value(v_int(int))), + Err(e) => { + warn!("Failed to parse '{}' to i64: {}", pairs.as_str(), e); + Ok(Expr::Value(v_err(E_INVARG))) + } + }, + Rule::float => { + let float = pairs.as_str().parse::().unwrap(); + Ok(Expr::Value(v_float(float))) + } + Rule::string => { + let string = pairs.as_str(); + let parsed = unquote_str(string)?; + Ok(Expr::Value(v_str(&parsed))) + } + Rule::err => { + let e = pairs.as_str(); + Ok(Expr::Value(match e.to_lowercase().as_str() { + "e_args" => v_err(E_ARGS), + "e_div" => v_err(E_DIV), + "e_float" => v_err(E_FLOAT), + "e_invarg" => v_err(E_INVARG), + "e_invind" => v_err(E_INVIND), + "e_maxrec" => v_err(E_MAXREC), + "e_nacc" => v_err(E_NACC), + "e_none" => v_err(E_NONE), + "e_perm" => v_err(E_PERM), + "e_propnf" => v_err(E_PROPNF), + "e_quota" => v_err(E_QUOTA), + "e_range" => v_err(E_RANGE), + "e_recmove" => v_err(E_RECMOVE), + "e_type" => v_err(E_TYPE), + "e_varnf" => v_err(E_VARNF), + "e_verbnf" => v_err(E_VERBNF), + &_ => { + panic!("unknown error") + } + })) } _ => { - panic!("Unimplemented exprlist: {:?}", pair); + panic!("Unimplemented atom: {:?}", pairs); } } } - Ok(args) -} -fn parse_arglist( - names: Rc>, - pairs: pest::iterators::Pairs, -) -> Result, CompileError> { - let Some(first) = pairs.peek() else { - return Ok(vec![]); - }; + fn parse_exprlist( + self: Rc, + pairs: pest::iterators::Pairs, + ) -> Result, CompileError> { + let mut args = vec![]; + for pair in pairs { + match pair.as_rule() { + Rule::argument => { + let arg = if pair.as_str().starts_with('@') { + Splice( + self.clone() + .parse_expr(pair.into_inner().next().unwrap().into_inner())?, + ) + } else { + Normal( + self.clone() + .parse_expr(pair.into_inner().next().unwrap().into_inner())?, + ) + }; + args.push(arg); + } + _ => { + panic!("Unimplemented exprlist: {:?}", pair); + } + } + } + Ok(args) + } - let Rule::exprlist = first.as_rule() else { - panic!("Unimplemented arglist: {:?}", first); - }; + fn parse_arglist( + self: Rc, + pairs: pest::iterators::Pairs, + ) -> Result, CompileError> { + let Some(first) = pairs.peek() else { + return Ok(vec![]); + }; - return parse_exprlist(names, first.into_inner()); -} + let Rule::exprlist = first.as_rule() else { + panic!("Unimplemented arglist: {:?}", first); + }; -fn parse_except_codes( - names: Rc>, - pairs: pest::iterators::Pair, -) -> Result { - match pairs.as_rule() { - Rule::anycode => Ok(CatchCodes::Any), - Rule::exprlist => Ok(CatchCodes::Codes(parse_exprlist( - names, - pairs.into_inner(), - )?)), - _ => { - panic!("Unimplemented except_codes: {:?}", pairs); + return self.parse_exprlist(first.into_inner()); + } + + fn parse_except_codes( + self: Rc, + pairs: pest::iterators::Pair, + ) -> Result { + match pairs.as_rule() { + Rule::anycode => Ok(CatchCodes::Any), + Rule::exprlist => Ok(CatchCodes::Codes(self.parse_exprlist(pairs.into_inner())?)), + _ => { + panic!("Unimplemented except_codes: {:?}", pairs); + } } } -} -fn parse_expr( - names: Rc>, - pairs: pest::iterators::Pairs, -) -> Result { - let pratt = PrattParser::new() - // Generally following C-like precedence order as described: - // https://en.cppreference.com/w/c/language/operator_precedence - // Precedence from lowest to highest. - // 14. Assignments are lowest precedence. - .op(Op::postfix(Rule::assign) | Op::prefix(Rule::scatter_assign)) - // 13. Ternary conditional - .op(Op::postfix(Rule::cond_expr)) - // 12. Logical or. - .op(Op::infix(Rule::lor, Assoc::Left)) - // 11. Logical and. - .op(Op::infix(Rule::land, Assoc::Left)) - // TODO: bitwise operators here (| 10, ^ XOR 9, & 8) if we ever get them. - // 7 - // Equality/inequality - .op(Op::infix(Rule::eq, Assoc::Left) | Op::infix(Rule::neq, Assoc::Left)) - // 6. Relational operators - .op(Op::infix(Rule::gt, Assoc::Left) - | Op::infix(Rule::lt, Assoc::Left) - | Op::infix(Rule::gte, Assoc::Left) - | Op::infix(Rule::lte, Assoc::Left)) - // TODO 5 bitwise shiftleft/shiftright if we ever get them. - // 5. In operator ended up above add - .op(Op::infix(Rule::in_range, Assoc::Left)) - // 4. Add & subtract same precedence - .op(Op::infix(Rule::add, Assoc::Left) | Op::infix(Rule::sub, Assoc::Left)) - // 3. * / % all same precedence - .op(Op::infix(Rule::mul, Assoc::Left) - | Op::infix(Rule::div, Assoc::Left) - | Op::infix(Rule::modulus, Assoc::Left)) - // Exponent is higher than multiply/divide (not present in C) - .op(Op::infix(Rule::pow, Assoc::Left)) - // 2. Unary negation & logical-not - .op(Op::prefix(Rule::neg) | Op::prefix(Rule::not)) - // 1. Indexing/suffix operator generally. - .op(Op::postfix(Rule::index_range) - | Op::postfix(Rule::index_single) - | Op::postfix(Rule::verb_call) - | Op::postfix(Rule::verb_expr_call) - | Op::postfix(Rule::prop) - | Op::postfix(Rule::prop_expr)); - - return pratt - .map_primary(|primary| match primary.as_rule() { - Rule::atom => { - let mut inner = primary.into_inner(); - let expr = parse_atom(names.clone(), inner.next().unwrap())?; - Ok(expr) + fn parse_expr( + self: Rc, + pairs: pest::iterators::Pairs, + ) -> Result { + let pratt = PrattParser::new() + // Generally following C-like precedence order as described: + // https://en.cppreference.com/w/c/language/operator_precedence + // Precedence from lowest to highest. + // 14. Assignments are lowest precedence. + .op(Op::postfix(Rule::assign) | Op::prefix(Rule::scatter_assign)) + // 13. Ternary conditional + .op(Op::postfix(Rule::cond_expr)) + // 12. Logical or. + .op(Op::infix(Rule::lor, Assoc::Left)) + // 11. Logical and. + .op(Op::infix(Rule::land, Assoc::Left)) + // TODO: bitwise operators here (| 10, ^ XOR 9, & 8) if we ever get them. + // 7 + // Equality/inequality + .op(Op::infix(Rule::eq, Assoc::Left) | Op::infix(Rule::neq, Assoc::Left)) + // 6. Relational operators + .op(Op::infix(Rule::gt, Assoc::Left) + | Op::infix(Rule::lt, Assoc::Left) + | Op::infix(Rule::gte, Assoc::Left) + | Op::infix(Rule::lte, Assoc::Left)) + // TODO 5 bitwise shiftleft/shiftright if we ever get them. + // 5. In operator ended up above add + .op(Op::infix(Rule::in_range, Assoc::Left)) + // 4. Add & subtract same precedence + .op(Op::infix(Rule::add, Assoc::Left) | Op::infix(Rule::sub, Assoc::Left)) + // 3. * / % all same precedence + .op(Op::infix(Rule::mul, Assoc::Left) + | Op::infix(Rule::div, Assoc::Left) + | Op::infix(Rule::modulus, Assoc::Left)) + // Exponent is higher than multiply/divide (not present in C) + .op(Op::infix(Rule::pow, Assoc::Left)) + // 2. Unary negation & logical-not + .op(Op::prefix(Rule::neg) | Op::prefix(Rule::not)) + // 1. Indexing/suffix operator generally. + .op(Op::postfix(Rule::index_range) + | Op::postfix(Rule::index_single) + | Op::postfix(Rule::verb_call) + | Op::postfix(Rule::verb_expr_call) + | Op::postfix(Rule::prop) + | Op::postfix(Rule::prop_expr)); + + let primary_self = self.clone(); + let prefix_self = self.clone(); + let postfix_self = self.clone(); + + return pratt + .map_primary(|primary| match primary.as_rule() { + Rule::atom => { + let mut inner = primary.into_inner(); + let expr = primary_self.clone().parse_atom(inner.next().unwrap())?; + Ok(expr) + } + Rule::sysprop => { + let mut inner = primary.into_inner(); + let property = inner.next().unwrap().as_str(); + Ok(Expr::Prop { + location: Box::new(Expr::Value(v_objid(SYSTEM_OBJECT))), + property: Box::new(Expr::Value(v_str(property))), + }) + } + Rule::sysprop_call => { + let mut inner = primary.into_inner(); + let verb = inner.next().unwrap().as_str()[1..].to_string(); + let args = primary_self + .clone() + .parse_arglist(inner.next().unwrap().into_inner())?; + Ok(Expr::Verb { + location: Box::new(Expr::Value(v_objid(SYSTEM_OBJECT))), + verb: Box::new(Expr::Value(v_string(verb))), + args, + }) + } + Rule::list => { + let mut inner = primary.into_inner(); + if let Some(arglist) = inner.next() { + let args = primary_self.clone().parse_exprlist(arglist.into_inner())?; + Ok(Expr::List(args)) + } else { + Ok(Expr::List(vec![])) + } + } + Rule::builtin_call => { + let mut inner = primary.into_inner(); + let bf = inner.next().unwrap().as_str(); + let args = primary_self + .clone() + .parse_arglist(inner.next().unwrap().into_inner())?; + Ok(Expr::Call { + function: Symbol::mk_case_insensitive(bf), + args, + }) + } + Rule::pass_expr => { + let mut inner = primary.into_inner(); + let args = if let Some(arglist) = inner.next() { + primary_self.clone().parse_exprlist(arglist.into_inner())? + } else { + vec![] + }; + Ok(Expr::Pass { args }) + } + Rule::range_end => Ok(Expr::Length), + Rule::try_expr => { + let mut inner = primary.into_inner(); + let try_expr = primary_self + .clone() + .parse_expr(inner.next().unwrap().into_inner())?; + let codes = inner.next().unwrap(); + let catch_codes = primary_self + .clone() + .parse_except_codes(codes.into_inner().next().unwrap())?; + let except = inner.next().map(|e| { + Box::new(primary_self.clone().parse_expr(e.into_inner()).unwrap()) + }); + Ok(Expr::TryCatch { + trye: Box::new(try_expr), + codes: catch_codes, + except, + }) + } + + Rule::paren_expr => { + let mut inner = primary.into_inner(); + let expr = primary_self + .clone() + .parse_expr(inner.next().unwrap().into_inner())?; + Ok(expr) + } + Rule::integer => match primary.as_str().parse::() { + Ok(int) => Ok(Expr::Value(v_int(int))), + Err(e) => { + warn!("Failed to parse '{}' to i64: {}", primary.as_str(), e); + Ok(Expr::Value(v_err(E_INVARG))) + } + }, + _ => todo!("Unimplemented primary: {:?}", primary.as_rule()), + }) + .map_infix(|lhs, op, rhs| match op.as_rule() { + Rule::add => Ok(Expr::Binary( + BinaryOp::Add, + Box::new(lhs?), + Box::new(rhs.unwrap()), + )), + Rule::sub => Ok(Expr::Binary( + BinaryOp::Sub, + Box::new(lhs?), + Box::new(rhs.unwrap()), + )), + Rule::mul => Ok(Expr::Binary( + BinaryOp::Mul, + Box::new(lhs?), + Box::new(rhs.unwrap()), + )), + Rule::div => Ok(Expr::Binary( + BinaryOp::Div, + Box::new(lhs?), + Box::new(rhs.unwrap()), + )), + Rule::pow => Ok(Expr::Binary( + BinaryOp::Exp, + Box::new(lhs?), + Box::new(rhs.unwrap()), + )), + Rule::modulus => Ok(Expr::Binary( + BinaryOp::Mod, + Box::new(lhs?), + Box::new(rhs.unwrap()), + )), + Rule::eq => Ok(Expr::Binary( + BinaryOp::Eq, + Box::new(lhs?), + Box::new(rhs.unwrap()), + )), + Rule::neq => Ok(Expr::Binary( + BinaryOp::NEq, + Box::new(lhs?), + Box::new(rhs.unwrap()), + )), + Rule::lt => Ok(Expr::Binary( + BinaryOp::Lt, + Box::new(lhs?), + Box::new(rhs.unwrap()), + )), + Rule::lte => Ok(Expr::Binary( + BinaryOp::LtE, + Box::new(lhs?), + Box::new(rhs.unwrap()), + )), + Rule::gt => Ok(Expr::Binary( + BinaryOp::Gt, + Box::new(lhs?), + Box::new(rhs.unwrap()), + )), + Rule::gte => Ok(Expr::Binary( + BinaryOp::GtE, + Box::new(lhs?), + Box::new(rhs.unwrap()), + )), + + Rule::land => Ok(Expr::And(Box::new(lhs?), Box::new(rhs.unwrap()))), + Rule::lor => Ok(Expr::Or(Box::new(lhs?), Box::new(rhs.unwrap()))), + Rule::in_range => Ok(Expr::Binary( + BinaryOp::In, + Box::new(lhs?), + Box::new(rhs.unwrap()), + )), + _ => todo!("Unimplemented infix: {:?}", op.as_rule()), + }) + .map_prefix(|op, rhs| match op.as_rule() { + Rule::scatter_assign => { + let inner = op.into_inner(); + let mut items = vec![]; + for scatter_item in inner { + match scatter_item.as_rule() { + Rule::scatter_optional => { + let mut inner = scatter_item.into_inner(); + let id = inner.next().unwrap().as_str(); + let id = primary_self + .clone() + .names + .borrow_mut() + .find_or_add_name_global(id); + let expr = inner.next().map(|e| { + primary_self.clone().parse_expr(e.into_inner()).unwrap() + }); + items.push(ScatterItem { + kind: ScatterKind::Optional, + id, + expr, + }); + } + Rule::scatter_target => { + let mut inner = scatter_item.into_inner(); + let id = inner.next().unwrap().as_str(); + let id = primary_self + .clone() + .names + .borrow_mut() + .find_or_add_name_global(id); + items.push(ScatterItem { + kind: ScatterKind::Required, + id, + expr: None, + }); + } + Rule::scatter_rest => { + let mut inner = scatter_item.into_inner(); + let id = inner.next().unwrap().as_str(); + let id = prefix_self + .clone() + .names + .borrow_mut() + .find_or_add_name_global(id); + items.push(ScatterItem { + kind: ScatterKind::Rest, + id, + expr: None, + }); + } + _ => { + panic!("Unimplemented scatter_item: {:?}", scatter_item); + } + } + } + Ok(Expr::Scatter(items, Box::new(rhs?))) + } + Rule::not => Ok(Expr::Unary(UnaryOp::Not, Box::new(rhs?))), + Rule::neg => Ok(Expr::Unary(UnaryOp::Neg, Box::new(rhs?))), + _ => todo!("Unimplemented prefix: {:?}", op.as_rule()), + }) + .map_postfix(|lhs, op| match op.as_rule() { + Rule::verb_call => { + let mut parts = op.into_inner(); + let ident = parts.next().unwrap().as_str(); + let args_expr = parts.next().unwrap(); + let args = postfix_self.clone().parse_arglist(args_expr.into_inner())?; + Ok(Expr::Verb { + location: Box::new(lhs?), + verb: Box::new(Expr::Value(v_str(ident))), + args, + }) + } + Rule::verb_expr_call => { + let mut parts = op.into_inner(); + let expr = postfix_self + .clone() + .parse_expr(parts.next().unwrap().into_inner())?; + let args_expr = parts.next().unwrap(); + let args = postfix_self.clone().parse_arglist(args_expr.into_inner())?; + Ok(Expr::Verb { + location: Box::new(lhs?), + verb: Box::new(expr), + args, + }) + } + Rule::prop => { + let mut parts = op.into_inner(); + let ident = parts.next().unwrap().as_str(); + Ok(Expr::Prop { + location: Box::new(lhs?), + property: Box::new(Expr::Value(v_str(ident))), + }) + } + Rule::prop_expr => { + let mut parts = op.into_inner(); + let expr = postfix_self + .clone() + .parse_expr(parts.next().unwrap().into_inner())?; + Ok(Expr::Prop { + location: Box::new(lhs?), + property: Box::new(expr), + }) + } + Rule::assign => { + let mut parts = op.into_inner(); + let right = postfix_self + .clone() + .parse_expr(parts.next().unwrap().into_inner())?; + Ok(Expr::Assign { + left: Box::new(lhs?), + right: Box::new(right), + }) + } + Rule::index_single => { + let mut parts = op.into_inner(); + let index = postfix_self + .clone() + .parse_expr(parts.next().unwrap().into_inner())?; + Ok(Expr::Index(Box::new(lhs?), Box::new(index))) + } + Rule::index_range => { + let mut parts = op.into_inner(); + let start = postfix_self + .clone() + .parse_expr(parts.next().unwrap().into_inner())?; + let end = postfix_self + .clone() + .parse_expr(parts.next().unwrap().into_inner())?; + Ok(Expr::Range { + base: Box::new(lhs?), + from: Box::new(start), + to: Box::new(end), + }) + } + Rule::cond_expr => { + let mut parts = op.into_inner(); + let true_expr = postfix_self + .clone() + .parse_expr(parts.next().unwrap().into_inner())?; + let false_expr = postfix_self + .clone() + .parse_expr(parts.next().unwrap().into_inner())?; + Ok(Expr::Cond { + condition: Box::new(lhs?), + consequence: Box::new(true_expr), + alternative: Box::new(false_expr), + }) + } + _ => todo!("Unimplemented postfix: {:?}", op.as_rule()), + }) + .parse(pairs); + } + + fn parse_statement( + self: Rc, + pair: pest::iterators::Pair, + ) -> Result, CompileError> { + let line = pair.line_col().0; + match pair.as_rule() { + Rule::expr_statement => { + let mut inner = pair.into_inner(); + if let Some(rule) = inner.next() { + let expr = self.parse_expr(rule.into_inner())?; + return Ok(Some(Stmt::new(StmtNode::Expr(expr), line))); + } + Ok(None) } - Rule::sysprop => { - let mut inner = primary.into_inner(); - let property = inner.next().unwrap().as_str(); - Ok(Expr::Prop { - location: Box::new(Expr::Value(v_objid(SYSTEM_OBJECT))), - property: Box::new(Expr::Value(v_str(property))), - }) + Rule::while_statement => { + self.enter_scope(); + let mut parts = pair.into_inner(); + let condition = self + .clone() + .parse_expr(parts.next().unwrap().into_inner())?; + let body = self + .clone() + .parse_statements(parts.next().unwrap().into_inner())?; + let environment_width = self.exit_scope(); + Ok(Some(Stmt::new( + StmtNode::While { + id: None, + condition, + body, + environment_width, + }, + line, + ))) } - Rule::sysprop_call => { - let mut inner = primary.into_inner(); - let verb = inner.next().unwrap().as_str()[1..].to_string(); - let args = parse_arglist(names.clone(), inner.next().unwrap().into_inner())?; - Ok(Expr::Verb { - location: Box::new(Expr::Value(v_objid(SYSTEM_OBJECT))), - verb: Box::new(Expr::Value(v_string(verb))), - args, - }) + Rule::labelled_while_statement => { + self.enter_scope(); + let mut parts = pair.into_inner(); + let id = self + .clone() + .names + .borrow_mut() + .find_or_add_name_global(parts.next().unwrap().as_str()); + let condition = self + .clone() + .parse_expr(parts.next().unwrap().into_inner())?; + let body = self + .clone() + .parse_statements(parts.next().unwrap().into_inner())?; + let environment_width = self.exit_scope(); + Ok(Some(Stmt::new( + StmtNode::While { + id: Some(id), + condition, + body, + environment_width, + }, + line, + ))) } - Rule::list => { - let mut inner = primary.into_inner(); - if let Some(arglist) = inner.next() { - let args = parse_exprlist(names.clone(), arglist.into_inner())?; - Ok(Expr::List(args)) - } else { - Ok(Expr::List(vec![])) + Rule::if_statement => { + self.enter_scope(); + let mut parts = pair.into_inner(); + let mut arms = vec![]; + let mut otherwise = None; + let condition = self + .clone() + .parse_expr(parts.next().unwrap().into_inner())?; + let body = self + .clone() + .parse_statements(parts.next().unwrap().into_inner())?; + let environment_width = { self.exit_scope() }; + arms.push(CondArm { + condition, + statements: body, + environment_width, + }); + for remainder in parts { + match remainder.as_rule() { + Rule::endif_clause => { + continue; + } + Rule::elseif_clause => { + { + self.clone().names.borrow_mut().push_scope(); + } + let mut parts = remainder.into_inner(); + let condition = self + .clone() + .parse_expr(parts.next().unwrap().into_inner())?; + let body = self + .clone() + .parse_statements(parts.next().unwrap().into_inner())?; + let environment_width = self.exit_scope(); + arms.push(CondArm { + condition, + statements: body, + environment_width, + }); + } + Rule::else_clause => { + self.clone().names.borrow_mut().push_scope(); + let mut parts = remainder.into_inner(); + let otherwise_statements = self + .clone() + .parse_statements(parts.next().unwrap().into_inner())?; + let otherwise_environment_width = self.exit_scope(); + let otherwise_arm = ElseArm { + statements: otherwise_statements, + environment_width: otherwise_environment_width, + }; + otherwise = Some(otherwise_arm); + } + _ => panic!("Unimplemented if clause: {:?}", remainder), + } } + Ok(Some(Stmt::new(StmtNode::Cond { arms, otherwise }, line))) } - Rule::builtin_call => { - let mut inner = primary.into_inner(); - let bf = inner.next().unwrap().as_str(); - let args = parse_arglist(names.clone(), inner.next().unwrap().into_inner())?; - Ok(Expr::Call { - function: Symbol::mk_case_insensitive(bf), - args, - }) + Rule::break_statement => { + let mut parts = pair.into_inner(); + let label = match parts.next() { + None => None, + Some(s) => { + let label = s.as_str(); + let Some(label) = self.names.borrow_mut().find_name(label) else { + return Err(CompileError::UnknownLoopLabel(label.to_string())); + }; + Some(label) + } + }; + Ok(Some(Stmt::new(StmtNode::Break { exit: label }, line))) } - Rule::pass_expr => { - let mut inner = primary.into_inner(); - let args = if let Some(arglist) = inner.next() { - parse_exprlist(names.clone(), arglist.into_inner())? - } else { - vec![] + Rule::continue_statement => { + let mut parts = pair.into_inner(); + let label = match parts.next() { + None => None, + Some(s) => { + let label = s.as_str(); + let Some(label) = self.names.borrow_mut().find_name(label) else { + return Err(CompileError::UnknownLoopLabel(label.to_string())); + }; + Some(label) + } }; - Ok(Expr::Pass { args }) + Ok(Some(Stmt::new(StmtNode::Continue { exit: label }, line))) } - Rule::range_end => Ok(Expr::Length), - Rule::try_expr => { - let mut inner = primary.into_inner(); - let try_expr = parse_expr(names.clone(), inner.next().unwrap().into_inner())?; - let codes = inner.next().unwrap(); - let catch_codes = - parse_except_codes(names.clone(), codes.into_inner().next().unwrap())?; - let except = inner + Rule::return_statement => { + let mut parts = pair.into_inner(); + let expr = parts .next() - .map(|e| Box::new(parse_expr(names.clone(), e.into_inner()).unwrap())); - Ok(Expr::TryCatch { - trye: Box::new(try_expr), - codes: catch_codes, - except, - }) + .map(|expr| self.parse_expr(expr.into_inner()).unwrap()); + Ok(Some(Stmt::new(StmtNode::Return(expr), line))) } - - Rule::paren_expr => { - let mut inner = primary.into_inner(); - let expr = parse_expr(names.clone(), inner.next().unwrap().into_inner())?; - Ok(expr) - } - Rule::integer => match primary.as_str().parse::() { - Ok(int) => Ok(Expr::Value(v_int(int))), - Err(e) => { - warn!("Failed to parse '{}' to i64: {}", primary.as_str(), e); - Ok(Expr::Value(v_err(E_INVARG))) - } - }, - _ => todo!("Unimplemented primary: {:?}", primary.as_rule()), - }) - .map_infix(|lhs, op, rhs| match op.as_rule() { - Rule::add => Ok(Expr::Binary( - BinaryOp::Add, - Box::new(lhs?), - Box::new(rhs.unwrap()), - )), - Rule::sub => Ok(Expr::Binary( - BinaryOp::Sub, - Box::new(lhs?), - Box::new(rhs.unwrap()), - )), - Rule::mul => Ok(Expr::Binary( - BinaryOp::Mul, - Box::new(lhs?), - Box::new(rhs.unwrap()), - )), - Rule::div => Ok(Expr::Binary( - BinaryOp::Div, - Box::new(lhs?), - Box::new(rhs.unwrap()), - )), - Rule::pow => Ok(Expr::Binary( - BinaryOp::Exp, - Box::new(lhs?), - Box::new(rhs.unwrap()), - )), - Rule::modulus => Ok(Expr::Binary( - BinaryOp::Mod, - Box::new(lhs?), - Box::new(rhs.unwrap()), - )), - Rule::eq => Ok(Expr::Binary( - BinaryOp::Eq, - Box::new(lhs?), - Box::new(rhs.unwrap()), - )), - Rule::neq => Ok(Expr::Binary( - BinaryOp::NEq, - Box::new(lhs?), - Box::new(rhs.unwrap()), - )), - Rule::lt => Ok(Expr::Binary( - BinaryOp::Lt, - Box::new(lhs?), - Box::new(rhs.unwrap()), - )), - Rule::lte => Ok(Expr::Binary( - BinaryOp::LtE, - Box::new(lhs?), - Box::new(rhs.unwrap()), - )), - Rule::gt => Ok(Expr::Binary( - BinaryOp::Gt, - Box::new(lhs?), - Box::new(rhs.unwrap()), - )), - Rule::gte => Ok(Expr::Binary( - BinaryOp::GtE, - Box::new(lhs?), - Box::new(rhs.unwrap()), - )), - - Rule::land => Ok(Expr::And(Box::new(lhs?), Box::new(rhs.unwrap()))), - Rule::lor => Ok(Expr::Or(Box::new(lhs?), Box::new(rhs.unwrap()))), - Rule::in_range => Ok(Expr::Binary( - BinaryOp::In, - Box::new(lhs?), - Box::new(rhs.unwrap()), - )), - _ => todo!("Unimplemented infix: {:?}", op.as_rule()), - }) - .map_prefix(|op, rhs| match op.as_rule() { - Rule::scatter_assign => { - let inner = op.into_inner(); - let mut items = vec![]; - for scatter_item in inner { - match scatter_item.as_rule() { - Rule::scatter_optional => { - let mut inner = scatter_item.into_inner(); - let id = inner.next().unwrap().as_str(); - let id = names.borrow_mut().find_or_add_name_global(id); - let expr = inner - .next() - .map(|e| parse_expr(names.clone(), e.into_inner()).unwrap()); - items.push(ScatterItem { - kind: ScatterKind::Optional, + Rule::for_statement => { + self.enter_scope(); + let mut parts = pair.into_inner(); + let id = self + .clone() + .names + .borrow_mut() + .find_or_add_name_global(parts.next().unwrap().as_str()); + let clause = parts.next().unwrap(); + let body = self + .clone() + .parse_statements(parts.next().unwrap().into_inner())?; + match clause.as_rule() { + Rule::for_range_clause => { + let mut clause_inner = clause.into_inner(); + let from_rule = clause_inner.next().unwrap(); + let to_rule = clause_inner.next().unwrap(); + let from = self.clone().parse_expr(from_rule.into_inner())?; + let to = self.clone().parse_expr(to_rule.into_inner())?; + let environment_width = self.exit_scope(); + Ok(Some(Stmt::new( + StmtNode::ForRange { id, - expr, - }); - } - Rule::scatter_target => { - let mut inner = scatter_item.into_inner(); - let id = inner.next().unwrap().as_str(); - let id = names.borrow_mut().find_or_add_name_global(id); - items.push(ScatterItem { - kind: ScatterKind::Required, + from, + to, + body, + environment_width, + }, + line, + ))) + } + Rule::for_in_clause => { + let mut clause_inner = clause.into_inner(); + let in_rule = clause_inner.next().unwrap(); + let expr = self.clone().parse_expr(in_rule.into_inner())?; + let environment_width = self.exit_scope(); + Ok(Some(Stmt::new( + StmtNode::ForList { id, - expr: None, - }); - } - Rule::scatter_rest => { - let mut inner = scatter_item.into_inner(); - let id = inner.next().unwrap().as_str(); - let id = names.borrow_mut().find_or_add_name_global(id); - items.push(ScatterItem { - kind: ScatterKind::Rest, + expr, + body, + environment_width, + }, + line, + ))) + } + _ => panic!("Unimplemented for clause: {:?}", clause), + } + } + Rule::try_finally_statement => { + self.enter_scope(); + let mut parts = pair.into_inner(); + let body = self + .clone() + .parse_statements(parts.next().unwrap().into_inner())?; + let handler = self + .clone() + .parse_statements(parts.next().unwrap().into_inner())?; + let environment_width = self.exit_scope(); + Ok(Some(Stmt::new( + StmtNode::TryFinally { + body, + handler, + environment_width, + }, + line, + ))) + } + Rule::try_except_statement => { + self.enter_scope(); + let mut parts = pair.into_inner(); + let body = self + .clone() + .parse_statements(parts.next().unwrap().into_inner())?; + let mut excepts = vec![]; + for except in parts { + match except.as_rule() { + Rule::except => { + let mut except_clause_parts = except.into_inner(); + let clause = except_clause_parts.next().unwrap(); + let (id, codes) = match clause.as_rule() { + Rule::labelled_except => { + let mut my_parts = clause.into_inner(); + let exception = my_parts.next().map(|id| { + self.names.borrow_mut().find_or_add_name_global(id.as_str()) + }); + + let codes = self.clone().parse_except_codes( + my_parts.next().unwrap().into_inner().next().unwrap(), + )?; + (exception, codes) + } + Rule::unlabelled_except => { + let mut my_parts = clause.into_inner(); + let codes = self.clone().parse_except_codes( + my_parts.next().unwrap().into_inner().next().unwrap(), + )?; + (None, codes) + } + _ => panic!("Unimplemented except clause: {:?}", clause), + }; + let statements = self.clone().parse_statements( + except_clause_parts.next().unwrap().into_inner(), + )?; + + excepts.push(ExceptArm { id, - expr: None, + codes, + statements, }); } - _ => { - panic!("Unimplemented scatter_item: {:?}", scatter_item); - } + _ => panic!("Unimplemented except clause: {:?}", except), } } - Ok(Expr::Scatter(items, Box::new(rhs?))) - } - Rule::not => Ok(Expr::Unary(UnaryOp::Not, Box::new(rhs?))), - Rule::neg => Ok(Expr::Unary(UnaryOp::Neg, Box::new(rhs?))), - _ => todo!("Unimplemented prefix: {:?}", op.as_rule()), - }) - .map_postfix(|lhs, op| match op.as_rule() { - Rule::verb_call => { - let mut parts = op.into_inner(); - let ident = parts.next().unwrap().as_str(); - let args_expr = parts.next().unwrap(); - let args = parse_arglist(names.clone(), args_expr.into_inner())?; - Ok(Expr::Verb { - location: Box::new(lhs?), - verb: Box::new(Expr::Value(v_str(ident))), - args, - }) - } - Rule::verb_expr_call => { - let mut parts = op.into_inner(); - let expr = parse_expr(names.clone(), parts.next().unwrap().into_inner())?; - let args_expr = parts.next().unwrap(); - let args = parse_arglist(names.clone(), args_expr.into_inner())?; - Ok(Expr::Verb { - location: Box::new(lhs?), - verb: Box::new(expr), - args, - }) - } - Rule::prop => { - let mut parts = op.into_inner(); - let ident = parts.next().unwrap().as_str(); - Ok(Expr::Prop { - location: Box::new(lhs?), - property: Box::new(Expr::Value(v_str(ident))), - }) - } - Rule::prop_expr => { - let mut parts = op.into_inner(); - let expr = parse_expr(names.clone(), parts.next().unwrap().into_inner())?; - Ok(Expr::Prop { - location: Box::new(lhs?), - property: Box::new(expr), - }) - } - Rule::assign => { - let mut parts = op.into_inner(); - let right = parse_expr(names.clone(), parts.next().unwrap().into_inner())?; - Ok(Expr::Assign { - left: Box::new(lhs?), - right: Box::new(right), - }) + let environment_width = self.exit_scope(); + Ok(Some(Stmt::new( + StmtNode::TryExcept { + body, + excepts, + environment_width, + }, + line, + ))) } - Rule::index_single => { - let mut parts = op.into_inner(); - let index = parse_expr(names.clone(), parts.next().unwrap().into_inner())?; - Ok(Expr::Index(Box::new(lhs?), Box::new(index))) + Rule::fork_statement => { + let mut parts = pair.into_inner(); + let time = self + .clone() + .parse_expr(parts.next().unwrap().into_inner())?; + let body = self.parse_statements(parts.next().unwrap().into_inner())?; + Ok(Some(Stmt::new( + StmtNode::Fork { + id: None, + time, + body, + }, + line, + ))) } - Rule::index_range => { - let mut parts = op.into_inner(); - let start = parse_expr(names.clone(), parts.next().unwrap().into_inner())?; - let end = parse_expr(names.clone(), parts.next().unwrap().into_inner())?; - Ok(Expr::Range { - base: Box::new(lhs?), - from: Box::new(start), - to: Box::new(end), - }) + Rule::labelled_fork_statement => { + let mut parts = pair.into_inner(); + let id = self + .names + .borrow_mut() + .find_or_add_name_global(parts.next().unwrap().as_str()); + let time = self + .clone() + .parse_expr(parts.next().unwrap().into_inner())?; + let body = self.parse_statements(parts.next().unwrap().into_inner())?; + Ok(Some(Stmt::new( + StmtNode::Fork { + id: Some(id), + time, + body, + }, + line, + ))) } - Rule::cond_expr => { - let mut parts = op.into_inner(); - let true_expr = parse_expr(names.clone(), parts.next().unwrap().into_inner())?; - let false_expr = parse_expr(names.clone(), parts.next().unwrap().into_inner())?; - Ok(Expr::Cond { - condition: Box::new(lhs?), - consequence: Box::new(true_expr), - alternative: Box::new(false_expr), - }) + Rule::begin_statement => { + if !self.options.lexical_scopes { + return Err(CompileError::ParseError( + "begin block when lexical scopes not enabled".to_string(), + )); + } + let mut parts = pair.into_inner(); + + self.enter_scope(); + + let body = self + .clone() + .parse_statements(parts.next().unwrap().into_inner())?; + let num_total_bindings = self.exit_scope(); + Ok(Some(Stmt::new( + Scope { + num_bindings: num_total_bindings, + body, + }, + line, + ))) } - _ => todo!("Unimplemented postfix: {:?}", op.as_rule()), - }) - .parse(pairs); -} + Rule::local_assignment => { + if !self.options.lexical_scopes { + return Err(CompileError::ParseError( + "local assignment when lexical scopes not enabled".to_string(), + )); + } -fn parse_statement( - names: Rc>, - pair: pest::iterators::Pair, -) -> Result, CompileError> { - let line = pair.line_col().0; - match pair.as_rule() { - Rule::expr_statement => { - let mut inner = pair.into_inner(); - if let Some(rule) = inner.next() { - let expr = parse_expr(names, rule.into_inner())?; - return Ok(Some(Stmt::new(StmtNode::Expr(expr), line))); + // An assignment declaration that introduces a locally lexically scoped variable. + // May be of form `let x = expr` or just `let x` + let mut parts = pair.into_inner(); + let id = self + .names + .borrow_mut() + .declare_name(parts.next().unwrap().as_str()); + let expr = parts + .next() + .map(|e| self.parse_expr(e.into_inner()).unwrap()); + + // Just becomes an assignment expression. + // But that means the decompiler will need to know what to do with it. + // Which is: if assignment is on its own in statement, and variable assigned to is + // restricted to the scope of the block, then it's a let. + Ok(Some(Stmt::new( + StmtNode::Expr(Expr::Assign { + left: Box::new(Expr::Id(id)), + right: Box::new(expr.unwrap_or(Expr::Value(v_none()))), + }), + line, + ))) } - Ok(None) - } - Rule::while_statement => { - names.borrow_mut().push_scope(); - let mut parts = pair.into_inner(); - let condition = parse_expr(names.clone(), parts.next().unwrap().into_inner())?; - let body = parse_statements(names.clone(), parts.next().unwrap().into_inner())?; - let environment_width = names.borrow_mut().pop_scope(); - Ok(Some(Stmt::new( - StmtNode::While { - id: None, - condition, - body, - environment_width, - }, - line, - ))) - } - Rule::labelled_while_statement => { - names.borrow_mut().push_scope(); - let mut parts = pair.into_inner(); - let id = names - .borrow_mut() - .find_or_add_name_global(parts.next().unwrap().as_str()); - let condition = parse_expr(names.clone(), parts.next().unwrap().into_inner())?; - let body = parse_statements(names.clone(), parts.next().unwrap().into_inner())?; - let environment_width = names.borrow_mut().pop_scope(); - Ok(Some(Stmt::new( - StmtNode::While { - id: Some(id), - condition, - body, - environment_width, - }, - line, - ))) - } - Rule::if_statement => { - names.borrow_mut().push_scope(); - let mut parts = pair.into_inner(); - let mut arms = vec![]; - let mut otherwise = None; - let condition = parse_expr(names.clone(), parts.next().unwrap().into_inner())?; - let body = parse_statements(names.clone(), parts.next().unwrap().into_inner())?; - let environment_width = names.borrow_mut().pop_scope(); - arms.push(CondArm { - condition, - statements: body, - environment_width, - }); - for remainder in parts { - match remainder.as_rule() { - Rule::endif_clause => { - continue; - } - Rule::elseif_clause => { - names.borrow_mut().push_scope(); - let mut parts = remainder.into_inner(); - let condition = - parse_expr(names.clone(), parts.next().unwrap().into_inner())?; - let body = - parse_statements(names.clone(), parts.next().unwrap().into_inner())?; - let environment_width = names.borrow_mut().pop_scope(); - arms.push(CondArm { - condition, - statements: body, - environment_width, - }); - } - Rule::else_clause => { - names.borrow_mut().push_scope(); - let mut parts = remainder.into_inner(); - let otherwise_statements = - parse_statements(names.clone(), parts.next().unwrap().into_inner())?; - let otherwise_environment_width = names.borrow_mut().pop_scope(); - let otherwise_arm = ElseArm { - statements: otherwise_statements, - environment_width: otherwise_environment_width, - }; - otherwise = Some(otherwise_arm); - } - _ => panic!("Unimplemented if clause: {:?}", remainder), + Rule::global_assignment => { + if !self.options.lexical_scopes { + return Err(CompileError::ParseError( + "global assignment when lexical scopes not enabled".to_string(), + )); } + + // An explicit global-declaration. + // global x, or global x = y + let mut parts = pair.into_inner(); + let id = self + .names + .borrow_mut() + .find_or_add_name_global(parts.next().unwrap().as_str()); + let expr = parts + .next() + .map(|e| self.parse_expr(e.into_inner()).unwrap()); + + // Produces an assignment expression as usual, but + // the decompiler will need to look and see that + // a) the statement is just an assignment on its own + // b) the variable being assigned to is in scope 0 (global) + // and then it's a global declaration. + // Note that this well have the effect of turning most existing MOO decompilations + // into global declarations, which is fine, if that feature is turned on. + Ok(Some(Stmt::new( + StmtNode::Expr(Expr::Assign { + left: Box::new(Expr::Id(id)), + right: Box::new(expr.unwrap_or(Expr::Value(v_none()))), + }), + line, + ))) } - Ok(Some(Stmt::new(StmtNode::Cond { arms, otherwise }, line))) + _ => panic!("Unimplemented statement: {:?}", pair.as_rule()), } - Rule::break_statement => { - let mut parts = pair.into_inner(); - let label = match parts.next() { - None => None, - Some(s) => { - let label = s.as_str(); - let Some(label) = names.borrow_mut().find_name(label) else { - return Err(CompileError::UnknownLoopLabel(label.to_string())); - }; - Some(label) - } - }; - Ok(Some(Stmt::new(StmtNode::Break { exit: label }, line))) - } - Rule::continue_statement => { - let mut parts = pair.into_inner(); - let label = match parts.next() { - None => None, - Some(s) => { - let label = s.as_str(); - let Some(label) = names.borrow_mut().find_name(label) else { - return Err(CompileError::UnknownLoopLabel(label.to_string())); - }; - Some(label) - } - }; - Ok(Some(Stmt::new(StmtNode::Continue { exit: label }, line))) - } - Rule::return_statement => { - let mut parts = pair.into_inner(); - let expr = parts - .next() - .map(|expr| parse_expr(names.clone(), expr.into_inner()).unwrap()); - Ok(Some(Stmt::new(StmtNode::Return(expr), line))) - } - Rule::for_statement => { - names.borrow_mut().push_scope(); - let mut parts = pair.into_inner(); - let id = names - .borrow_mut() - .find_or_add_name_global(parts.next().unwrap().as_str()); - let clause = parts.next().unwrap(); - let body = parse_statements(names.clone(), parts.next().unwrap().into_inner())?; - match clause.as_rule() { - Rule::for_range_clause => { - let mut clause_inner = clause.into_inner(); - let from_rule = clause_inner.next().unwrap(); - let to_rule = clause_inner.next().unwrap(); - let from = parse_expr(names.clone(), from_rule.into_inner())?; - let to = parse_expr(names.clone(), to_rule.into_inner())?; - let environment_width = names.borrow_mut().pop_scope(); - Ok(Some(Stmt::new( - StmtNode::ForRange { - id, - from, - to, - body, - environment_width, - }, - line, - ))) + } + + fn parse_statements( + self: Rc, + pairs: pest::iterators::Pairs, + ) -> Result, CompileError> { + let mut statements = vec![]; + for pair in pairs { + match pair.as_rule() { + Rule::statement => { + let stmt = self + .clone() + .parse_statement(pair.into_inner().next().unwrap())?; + if let Some(stmt) = stmt { + statements.push(stmt); + } } - Rule::for_in_clause => { - let mut clause_inner = clause.into_inner(); - let in_rule = clause_inner.next().unwrap(); - let expr = parse_expr(names.clone(), in_rule.into_inner())?; - let environment_width = names.borrow_mut().pop_scope(); - Ok(Some(Stmt::new( - StmtNode::ForList { - id, - expr, - body, - environment_width, - }, - line, - ))) + _ => { + panic!("Unexpected rule: {:?}", pair.as_rule()); } - _ => panic!("Unimplemented for clause: {:?}", clause), } } - Rule::try_finally_statement => { - names.borrow_mut().push_scope(); - let mut parts = pair.into_inner(); - let body = parse_statements(names.clone(), parts.next().unwrap().into_inner())?; - let handler = parse_statements(names.clone(), parts.next().unwrap().into_inner())?; - let environment_width = names.borrow_mut().pop_scope(); - Ok(Some(Stmt::new( - StmtNode::TryFinally { - body, - handler, - environment_width, - }, - line, - ))) - } - Rule::try_except_statement => { - names.borrow_mut().push_scope(); - let mut parts = pair.into_inner(); - let body = parse_statements(names.clone(), parts.next().unwrap().into_inner())?; - let mut excepts = vec![]; - for except in parts { - match except.as_rule() { - Rule::except => { - let mut except_clause_parts = except.into_inner(); - let clause = except_clause_parts.next().unwrap(); - let (id, codes) = match clause.as_rule() { - Rule::labelled_except => { - let mut my_parts = clause.into_inner(); - let exception = my_parts.next().map(|id| { - names.borrow_mut().find_or_add_name_global(id.as_str()) - }); + Ok(statements) + } - let codes = parse_except_codes( - names.clone(), - my_parts.next().unwrap().into_inner().next().unwrap(), - )?; - (exception, codes) - } - Rule::unlabelled_except => { - let mut my_parts = clause.into_inner(); - let codes = parse_except_codes( - names.clone(), - my_parts.next().unwrap().into_inner().next().unwrap(), - )?; - (None, codes) - } - _ => panic!("Unimplemented except clause: {:?}", clause), - }; - let statements = parse_statements( - names.clone(), - except_clause_parts.next().unwrap().into_inner(), - )?; - - excepts.push(ExceptArm { - id, - codes, - statements, - }); + fn compile(self: Rc, pairs: pest::iterators::Pairs) -> Result { + let mut program = Vec::new(); + for pair in pairs { + match pair.as_rule() { + Rule::program => { + let inna = pair.into_inner().next().unwrap(); + + match inna.as_rule() { + Rule::statements => { + let parsed_statements = + self.clone().parse_statements(inna.into_inner())?; + program.extend(parsed_statements); + } + + _ => { + panic!("Unexpected rule: {:?}", inna.as_rule()); + } } - _ => panic!("Unimplemented except clause: {:?}", except), + } + _ => { + panic!("Unexpected rule: {:?}", pair.as_rule()); } } - let environment_width = names.borrow_mut().pop_scope(); - Ok(Some(Stmt::new( - StmtNode::TryExcept { - body, - excepts, - environment_width, - }, - line, - ))) } - Rule::fork_statement => { - let mut parts = pair.into_inner(); - let time = parse_expr(names.clone(), parts.next().unwrap().into_inner())?; - let body = parse_statements(names, parts.next().unwrap().into_inner())?; - Ok(Some(Stmt::new( - StmtNode::Fork { - id: None, - time, - body, - }, - line, - ))) - } - Rule::labelled_fork_statement => { - let mut parts = pair.into_inner(); - let id = names - .borrow_mut() - .find_or_add_name_global(parts.next().unwrap().as_str()); - let time = parse_expr(names.clone(), parts.next().unwrap().into_inner())?; - let body = parse_statements(names, parts.next().unwrap().into_inner())?; - Ok(Some(Stmt::new( - StmtNode::Fork { - id: Some(id), - time, - body, - }, - line, - ))) - } - Rule::begin_statement => { - let mut parts = pair.into_inner(); + let names = self.names.borrow_mut(); + // Annotate the "true" line numbers of the AST nodes. + annotate_line_numbers(1, &mut program); - names.borrow_mut().push_scope(); + let (bound_names, names_mapping) = names.bind(); - let body = parse_statements(names.clone(), parts.next().unwrap().into_inner())?; - let num_total_bindings = names.borrow_mut().pop_scope(); - Ok(Some(Stmt::new( - Scope { - num_bindings: num_total_bindings, - body, - }, - line, - ))) - } - Rule::local_assignment => { - // An assignment declaration that introduces a locally lexically scoped variable. - // May be of form `let x = expr` or just `let x` - let mut parts = pair.into_inner(); - let id = names - .borrow_mut() - .declare_name(parts.next().unwrap().as_str()); - let expr = parts - .next() - .map(|e| parse_expr(names.clone(), e.into_inner()).unwrap()); - - // Just becomes an assignment expression. - // But that means the decompiler will need to know what to do with it. - // Which is: if assignment is on its own in statement, and variable assigned to is - // restricted to the scope of the block, then it's a let. - Ok(Some(Stmt::new( - StmtNode::Expr(Expr::Assign { - left: Box::new(Expr::Id(id)), - right: Box::new(expr.unwrap_or(Expr::Value(v_none()))), - }), - line, - ))) - } - Rule::global_assignment => { - // An explicit global-declaration. - // global x, or global x = y - let mut parts = pair.into_inner(); - let id = names - .borrow_mut() - .find_or_add_name_global(parts.next().unwrap().as_str()); - let expr = parts - .next() - .map(|e| parse_expr(names.clone(), e.into_inner()).unwrap()); - - // Produces an assignment expression as usual, but - // the decompiler will need to look and see that - // a) the statement is just an assignment on its own - // b) the variable being assigned to is in scope 0 (global) - // and then it's a global declaration. - // Note that this well have the effect of turning most existing MOO decompilations - // into global declarations, which is fine, if that feature is turned on. - Ok(Some(Stmt::new( - StmtNode::Expr(Expr::Assign { - left: Box::new(Expr::Id(id)), - right: Box::new(expr.unwrap_or(Expr::Value(v_none()))), - }), - line, - ))) + Ok(Parse { + stmts: program, + unbound_names: names.clone(), + names: bound_names, + names_mapping, + }) + } + + fn enter_scope(&self) { + if self.options.lexical_scopes { + self.names.borrow_mut().push_scope(); } - _ => panic!("Unimplemented statement: {:?}", pair.as_rule()), } -} -fn parse_statements( - names: Rc>, - pairs: pest::iterators::Pairs, -) -> Result, CompileError> { - let mut statements = vec![]; - for pair in pairs { - match pair.as_rule() { - Rule::statement => { - let stmt = parse_statement(names.clone(), pair.into_inner().next().unwrap())?; - if let Some(stmt) = stmt { - statements.push(stmt); - } - } - _ => { - panic!("Unexpected rule: {:?}", pair.as_rule()); - } + fn exit_scope(&self) -> usize { + if self.options.lexical_scopes { + return self.names.borrow_mut().pop_scope(); } + 0 } - Ok(statements) +} + +/// The emitted parse tree from the parse phase of the compiler. +#[derive(Debug)] +pub struct Parse { + pub stmts: Vec, + pub unbound_names: UnboundNames, + pub names: Names, + pub names_mapping: HashMap, } #[instrument(skip(program_text))] -pub fn parse_program(program_text: &str) -> Result { +pub fn parse_program(program_text: &str, options: CompileOptions) -> Result { let pairs = match MooParser::parse(Rule::program, program_text) { Ok(pairs) => pairs, Err(e) => { @@ -887,44 +1061,9 @@ pub fn parse_program(program_text: &str) -> Result { } }; - // TODO: this is Rc { - let inna = pair.into_inner().next().unwrap(); - - match inna.as_rule() { - Rule::statements => { - let parsed_statements = parse_statements(names.clone(), inna.into_inner())?; - program.extend(parsed_statements); - } - - _ => { - panic!("Unexpected rule: {:?}", inna.as_rule()); - } - } - } - _ => { - panic!("Unexpected rule: {:?}", pair.as_rule()); - } - } - } - let names = names.borrow_mut(); - - // Annotate the "true" line numbers of the AST nodes. - annotate_line_numbers(1, &mut program); - - let (bound_names, names_mapping) = names.bind(); - - Ok(Parse { - stmts: program, - unbound_names: names.clone(), - names: bound_names, - names_mapping, - }) + // TODO: this is in Rc because of borrowing issues in the Pratt parser + let tree_transform = TreeTransformer::new(options); + tree_transform.compile(pairs) } // Lex a simpe MOO string literal. Expectation is: @@ -983,6 +1122,7 @@ mod tests { StmtNode, UnaryOp, }; use crate::parse::{parse_program, unquote_str}; + use crate::CompileOptions; use moor_values::model::CompileError; fn stripped_stmts(statements: &[Stmt]) -> Vec { @@ -1001,7 +1141,7 @@ mod tests { #[test] fn test_parse_flt_no_decimal() { let program = "return 1e-09;"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!(parse.stmts.len(), 1); assert_eq!( stripped_stmts(&parse.stmts), @@ -1012,7 +1152,7 @@ mod tests { #[test] fn test_call_verb() { let program = r#"#0:test_verb(1,2,3,"test");"#; - let parsed = parse_program(program).unwrap(); + let parsed = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!(parsed.stmts.len(), 1); assert_eq!( stripped_stmts(&parsed.stmts), @@ -1032,7 +1172,7 @@ mod tests { #[test] fn test_parse_simple_var_assignment_precedence() { let program = "a = 1 + 2;"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let a = parse.unbound_names.find_name("a").unwrap(); assert_eq!(parse.stmts.len(), 1); @@ -1052,7 +1192,7 @@ mod tests { #[test] fn test_parse_call_literal() { let program = "notify(\"test\");"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!(parse.stmts.len(), 1); assert_eq!( @@ -1067,7 +1207,7 @@ mod tests { #[test] fn test_parse_if_stmt() { let program = "if (1 == 2) return 5; elseif (2 == 3) return 3; else return 6; endif"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!(parse.stmts.len(), 1); assert_eq!( stripped_stmts(&parse.stmts)[0], @@ -1126,7 +1266,7 @@ mod tests { return 6; endif "#; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!(parse.stmts.len(), 1); assert_eq!( stripped_stmts(&parse.stmts)[0], @@ -1188,7 +1328,7 @@ mod tests { #[test] fn test_not_precedence() { let program = "return !(#2:move(5));"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!(parse.stmts.len(), 1); assert_eq!( stripped_stmts(&parse.stmts)[0], @@ -1211,7 +1351,7 @@ mod tests { return; endif "#; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!(parse.stmts.len(), 1); assert_eq!( stripped_stmts(&parse.stmts)[0], @@ -1243,7 +1383,7 @@ mod tests { #[test] fn test_parse_for_loop() { let program = "for x in ({1,2,3}) b = x + 5; endfor"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let x = parse.unbound_names.find_name("x").unwrap(); let b = parse.unbound_names.find_name("b").unwrap(); assert_eq!(parse.stmts.len(), 1); @@ -1276,7 +1416,7 @@ mod tests { #[test] fn test_parse_for_range() { let program = "for x in [1..5] b = x + 5; endfor"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!(parse.stmts.len(), 1); let x = parse.unbound_names.find_name("x").unwrap(); let b = parse.unbound_names.find_name("b").unwrap(); @@ -1306,7 +1446,7 @@ mod tests { #[test] fn test_indexed_range_len() { let program = "a = {1, 2, 3}; b = a[2..$];"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let (a, b) = ( parse.unbound_names.find_name("a").unwrap(), parse.unbound_names.find_name("b").unwrap(), @@ -1337,7 +1477,7 @@ mod tests { #[test] fn test_parse_while() { let program = "while (1) x = x + 1; if (x > 5) break; endif endwhile"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let x = parse.unbound_names.find_name("x").unwrap(); assert_eq!( @@ -1387,7 +1527,7 @@ mod tests { #[test] fn test_parse_labelled_while() { let program = "while chuckles (1) x = x + 1; if (x > 5) break chuckles; endif endwhile"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let chuckles = parse.unbound_names.find_name("chuckles").unwrap(); let x = parse.unbound_names.find_name("x").unwrap(); @@ -1440,7 +1580,7 @@ mod tests { #[test] fn test_sysobjref() { let program = "$string_utils:from_list(test_string);"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let test_string = parse.unbound_names.find_name("test_string").unwrap(); assert_eq!( stripped_stmts(&parse.stmts), @@ -1458,7 +1598,7 @@ mod tests { #[test] fn test_scatter_assign() { let program = "{connection} = args;"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let connection = parse.unbound_names.find_name("connection").unwrap(); let args = parse.unbound_names.find_name("args").unwrap(); @@ -1479,7 +1619,7 @@ mod tests { // Regression test for a bug where the precedence of the right hand side of a scatter // assignment was incorrect. let program = "{connection} = args[1];"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let connection = parse.unbound_names.find_name("connection").unwrap(); let args = parse.unbound_names.find_name("args").unwrap(); @@ -1500,7 +1640,7 @@ mod tests { #[test] fn test_indexed_list() { let program = "{a,b,c}[1];"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let a = parse.unbound_names.find_name("a").unwrap(); let b = parse.unbound_names.find_name("b").unwrap(); let c = parse.unbound_names.find_name("c").unwrap(); @@ -1520,7 +1660,7 @@ mod tests { #[test] fn test_assigned_indexed_list() { let program = "a = {a,b,c}[1];"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let a = parse.unbound_names.find_name("a").unwrap(); let b = parse.unbound_names.find_name("b").unwrap(); let c = parse.unbound_names.find_name("c").unwrap(); @@ -1543,7 +1683,7 @@ mod tests { #[test] fn test_indexed_assign() { let program = "this.stack[5] = 5;"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let this = parse.unbound_names.find_name("this").unwrap(); assert_eq!( stripped_stmts(&parse.stmts), @@ -1563,7 +1703,7 @@ mod tests { #[test] fn test_for_list() { let program = "for i in ({1,2,3}) endfor return i;"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let i = parse.unbound_names.find_name("i").unwrap(); // Verify the structure of the syntax tree for a for-list loop. assert_eq!( @@ -1587,7 +1727,7 @@ mod tests { #[test] fn test_scatter_required() { let program = "{a, b, c} = args;"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![StmtNode::Expr(Expr::Scatter( @@ -1616,7 +1756,7 @@ mod tests { #[test] fn test_valid_underscore_and_no_underscore_ident() { let program = "_house == home;"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let house = parse.unbound_names.find_name("_house").unwrap(); let home = parse.unbound_names.find_name("home").unwrap(); assert_eq!( @@ -1632,7 +1772,7 @@ mod tests { #[test] fn test_arg_splice() { let program = "return {@results, frozzbozz(@args)};"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let results = parse.unbound_names.find_name("results").unwrap(); let args = parse.unbound_names.find_name("args").unwrap(); assert_eq!( @@ -1654,7 +1794,7 @@ mod tests { let program = r#" "\n \t \r \" \\"; "#; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![StmtNode::Expr(Value(v_str(r#"n t r " \"#)))] @@ -1667,7 +1807,7 @@ mod tests { ;;;; "#; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!(stripped_stmts(&parse.stmts), vec![]); } @@ -1680,7 +1820,7 @@ mod tests { forgotten = 3; "#; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let a = parse.unbound_names.find_name("a").unwrap(); let info = parse.unbound_names.find_name("info").unwrap(); let forgotten = parse.unbound_names.find_name("forgotten").unwrap(); @@ -1717,7 +1857,7 @@ mod tests { else return 3; endif"#; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( parse.stmts, vec![Stmt { @@ -1759,7 +1899,7 @@ mod tests { else return 3; endif"#; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![StmtNode::Cond { @@ -1807,7 +1947,7 @@ mod tests { fn test_if_in_range() { let program = r#"if (5 in {1,2,3}) endif"#; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![StmtNode::Cond { @@ -1837,7 +1977,7 @@ mod tests { except (E_PROPNF) return; endtry"#; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), @@ -1864,7 +2004,7 @@ mod tests { #[test] fn test_float() { let program = "10000.0;"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![StmtNode::Expr(Value(v_float(10000.0)))] @@ -1874,7 +2014,7 @@ mod tests { #[test] fn test_in_range() { let program = "a in {1,2,3};"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let a = parse.unbound_names.find_name("a").unwrap(); assert_eq!( stripped_stmts(&parse.stmts), @@ -1893,7 +2033,7 @@ mod tests { #[test] fn test_empty_list() { let program = "{};"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![StmtNode::Expr(Expr::List(vec![]))] @@ -1903,7 +2043,7 @@ mod tests { #[test] fn test_verb_expr() { let program = "this:(\"verb\")(1,2,3);"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![StmtNode::Expr(Verb { @@ -1921,7 +2061,7 @@ mod tests { #[test] fn test_prop_expr() { let program = "this.(\"prop\");"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![StmtNode::Expr(Prop { @@ -1934,7 +2074,7 @@ mod tests { #[test] fn test_not_expr() { let program = "!2;"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![StmtNode::Expr(Expr::Unary( @@ -1947,7 +2087,7 @@ mod tests { #[test] fn test_comparison_assign_chain() { let program = "(2 <= (len = length(text)));"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let len = parse.unbound_names.find_name("len").unwrap(); let text = parse.unbound_names.find_name("text").unwrap(); assert_eq!( @@ -1969,7 +2109,7 @@ mod tests { #[test] fn test_cond_expr() { let program = "a = (1 == 2 ? 3 | 4);"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![StmtNode::Expr(Expr::Assign { @@ -1990,7 +2130,7 @@ mod tests { #[test] fn test_list_compare() { let program = "{what} == args;"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![StmtNode::Expr(Expr::Binary( @@ -2008,7 +2148,7 @@ mod tests { fn test_raise_bf_call_incorrect_err() { // detect ambiguous match on E_PERMS != E_PERM let program = "raise(E_PERMS);"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), @@ -2028,7 +2168,7 @@ mod tests { for line in ({1,2,3}) endfor(52); "#; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![ @@ -2050,7 +2190,7 @@ mod tests { #[test] fn try_catch_expr() { let program = "return {`x ! e_varnf => 666'};"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let varnf = Normal(Value(v_err(E_VARNF))); assert_eq!( @@ -2068,7 +2208,7 @@ mod tests { #[test] fn try_catch_any_expr() { let program = "`raise(E_INVARG) ! ANY';"; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let invarg = Normal(Value(v_err(E_INVARG))); assert_eq!( @@ -2087,7 +2227,7 @@ mod tests { #[test] fn test_try_any_expr() { let program = r#"`$ftp_client:finish_get(this.connection) ! ANY';"#; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), @@ -2113,7 +2253,7 @@ mod tests { fn test_paren_expr() { // Verify that parenthesized expressions end up with correct precedence and nesting. let program_a = r#"1 && (2 || 3);"#; - let parse = parse_program(program_a).unwrap(); + let parse = parse_program(program_a, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![StmtNode::Expr(Expr::And( @@ -2125,7 +2265,7 @@ mod tests { ))] ); let program_b = r#"1 && 2 || 3;"#; - let parse = parse_program(program_b).unwrap(); + let parse = parse_program(program_b, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![StmtNode::Expr(Expr::Or( @@ -2147,7 +2287,7 @@ mod tests { pass = blop; return pass; "#; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![ @@ -2188,14 +2328,14 @@ mod tests { break unknown; endwhile "#; - let parse = parse_program(program); + let parse = parse_program(program, CompileOptions::default()); assert!(matches!(parse, Err(CompileError::UnknownLoopLabel(_)))); let program = r#" while (1) continue unknown; endwhile"#; - let parse = parse_program(program); + let parse = parse_program(program, CompileOptions::default()); assert!(matches!(parse, Err(CompileError::UnknownLoopLabel(_)))); } @@ -2205,7 +2345,7 @@ mod tests { return 5; end "#; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); assert_eq!( stripped_stmts(&parse.stmts), vec![StmtNode::Scope { @@ -2231,7 +2371,7 @@ mod tests { global a = 1; end return x;"#; - let parse = parse_program(program).unwrap(); + let parse = parse_program(program, CompileOptions::default()).unwrap(); let x_names = parse.unbound_names.find_named("x"); let y_names = parse.unbound_names.find_named("y"); let z_names = parse.unbound_names.find_named("z"); diff --git a/crates/compiler/src/unparse.rs b/crates/compiler/src/unparse.rs index 228523e6..ca2c4573 100644 --- a/crates/compiler/src/unparse.rs +++ b/crates/compiler/src/unparse.rs @@ -696,13 +696,13 @@ pub fn annotate_line_numbers(start_line_no: usize, tree: &mut [Stmt]) -> usize { #[cfg(test)] mod tests { + use super::*; use crate::ast::assert_trees_match_recursive; + use crate::CompileOptions; use pretty_assertions::assert_eq; use test_case::test_case; use unindent::unindent; - use super::*; - #[test_case("a = 1;\n"; "assignment")] #[test_case("a = 1 + 2;\n"; "assignment with expr")] #[test_case("1 + 2 * 3 + 4;\n"; "binops with same precident")] @@ -790,8 +790,10 @@ mod tests { // Now parse both again, and verify that the complete ASTs match, ignoring the parser line // numbers, but validating everything else. - let parsed_original = crate::parse::parse_program(&stripped).unwrap(); - let parsed_decompiled = crate::parse::parse_program(&result).unwrap(); + let parsed_original = + crate::parse::parse_program(&stripped, CompileOptions::default()).unwrap(); + let parsed_decompiled = + crate::parse::parse_program(&result, CompileOptions::default()).unwrap(); assert_trees_match_recursive(&parsed_original.stmts, &parsed_decompiled.stmts) } @@ -865,7 +867,7 @@ mod tests { } pub fn parse_and_unparse(original: &str) -> Result { - let tree = crate::parse::parse_program(original).unwrap(); + let tree = crate::parse::parse_program(original, CompileOptions::default()).unwrap(); Ok(unparse(&tree)?.join("\n")) } } diff --git a/crates/daemon/src/main.rs b/crates/daemon/src/main.rs index 51d53fa4..902ccf79 100644 --- a/crates/daemon/src/main.rs +++ b/crates/daemon/src/main.rs @@ -202,6 +202,16 @@ struct Args { default_value = "true" )] rich_notify: bool, + + #[arg( + long, + help = "Enable blockl-level lexical scoping in programs. \ + Adds the `begin`/`end` syntax for creating lexical scopes, and `let` and `global` + for declaring variables. \ + This is a feature that is not present in LambdaMOO, so if you need backwards compatibility, turn this off.", + default_value = "true" + )] + lexical_scopes: bool, } fn main() -> Result<(), Report> { @@ -285,6 +295,13 @@ fn main() -> Result<(), Report> { }; info!(path = ?args.db, "Opened database"); + let config = Arc::new(Config { + textdump_output: args.textdump_out, + textdump_encoding: args.textdump_encoding, + rich_notify: args.rich_notify, + lexical_scopes: args.lexical_scopes, + }); + // If the database already existed, do not try to import the textdump... if let Some(textdump) = args.textdump { if !freshly_made { @@ -295,7 +312,13 @@ fn main() -> Result<(), Report> { let mut loader_interface = database .loader_client() .expect("Unable to get loader interface from database"); - textdump_load(loader_interface.as_ref(), textdump, args.textdump_encoding).unwrap(); + textdump_load( + loader_interface.as_ref(), + textdump, + args.textdump_encoding, + config.compile_options(), + ) + .unwrap(); let duration = start.elapsed(); info!("Loaded textdump in {:?}", duration); loader_interface @@ -304,12 +327,6 @@ fn main() -> Result<(), Report> { } } - let config = Config { - textdump_output: args.textdump_out, - textdump_encoding: args.textdump_encoding, - rich_notify: args.rich_notify, - }; - let tasks_db: Box = match args.db_flavour { DatabaseFlavour::WiredTiger => { let (tasks_db, _) = tasks_wt::WiredTigerTasksDb::open(Some(&args.tasks_db)); @@ -325,7 +342,7 @@ fn main() -> Result<(), Report> { // The pieces from core we're going to use: // Our DB. // Our scheduler. - let scheduler = Scheduler::new(database, tasks_db, config); + let scheduler = Scheduler::new(database, tasks_db, config.clone()); let scheduler_client = scheduler.client().expect("Failed to get scheduler client"); // We have to create the RpcServer before starting the scheduler because we need to pass it in @@ -341,6 +358,7 @@ fn main() -> Result<(), Report> { zmq_ctx.clone(), args.events_listen.as_str(), args.db_flavour, + config, )); // The scheduler thread: diff --git a/crates/daemon/src/rpc_server.rs b/crates/daemon/src/rpc_server.rs index f2fd641a..e335b1d8 100644 --- a/crates/daemon/src/rpc_server.rs +++ b/crates/daemon/src/rpc_server.rs @@ -23,19 +23,11 @@ use std::time::SystemTime; use eyre::{Context, Error}; use moor_db::DatabaseFlavour; -use moor_kernel::SchedulerClient; -use rusty_paseto::core::{ - Footer, Paseto, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, Payload, Public, V4, -}; -use rusty_paseto::prelude::Key; -use serde_json::json; -use tracing::{debug, error, info, trace, warn}; -use uuid::Uuid; -use zmq::{Socket, SocketType}; - +use moor_kernel::config::Config; use moor_kernel::tasks::sessions::SessionError::DeliveryError; use moor_kernel::tasks::sessions::{Session, SessionError, SessionFactory}; use moor_kernel::tasks::TaskHandle; +use moor_kernel::SchedulerClient; use moor_values::tasks::SchedulerError::CommandExecutionError; use moor_values::tasks::{CommandError, NarrativeEvent, TaskId}; use moor_values::util::parse_into_words; @@ -50,6 +42,14 @@ use rpc_common::{ RpcRequestError, RpcResponse, RpcResult, BROADCAST_TOPIC, MOOR_AUTH_TOKEN_FOOTER, MOOR_SESSION_TOKEN_FOOTER, }; +use rusty_paseto::core::{ + Footer, Paseto, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, Payload, Public, V4, +}; +use rusty_paseto::prelude::Key; +use serde_json::json; +use tracing::{debug, error, info, trace, warn}; +use uuid::Uuid; +use zmq::{Socket, SocketType}; use crate::connections::ConnectionsDB; use crate::connections_wt::ConnectionsWT; @@ -64,6 +64,7 @@ pub struct RpcServer { events_publish: Arc>, connections: Arc, task_handles: Mutex>, + config: Arc, } pub(crate) fn make_response(result: Result) -> Vec { @@ -82,6 +83,7 @@ impl RpcServer { narrative_endpoint: &str, // For determining the flavor for the connections database. db_flavor: DatabaseFlavour, + config: Arc, ) -> Self { info!( "Creating new RPC server; with {} ZMQ IO threads...", @@ -110,6 +112,7 @@ impl RpcServer { events_publish: Arc::new(Mutex::new(publish)), zmq_context, task_handles: Default::default(), + config, } } @@ -837,14 +840,19 @@ impl RpcServer { return Err(RpcRequestError::CreateSessionFailed); }; - let task_handle = - match scheduler_client.submit_eval_task(connection, connection, expression, session) { - Ok(t) => t, - Err(e) => { - error!(error = ?e, "Error submitting eval task"); - return Err(RpcRequestError::InternalError(e.to_string())); - } - }; + let task_handle = match scheduler_client.submit_eval_task( + connection, + connection, + expression, + session, + self.config.clone(), + ) { + Ok(t) => t, + Err(e) => { + error!(error = ?e, "Error submitting eval task"); + return Err(RpcRequestError::InternalError(e.to_string())); + } + }; match task_handle.into_receiver().recv() { Ok(Ok(v)) => Ok(RpcResponse::EvalResult(v)), Ok(Err(e)) => Err(RpcRequestError::TaskError(e)), diff --git a/crates/kernel/benches/vm_benches.rs b/crates/kernel/benches/vm_benches.rs index 8cfec4a3..0d223a7b 100644 --- a/crates/kernel/benches/vm_benches.rs +++ b/crates/kernel/benches/vm_benches.rs @@ -21,7 +21,7 @@ use std::time::Duration; use criterion::{criterion_group, criterion_main, Criterion}; -use moor_compiler::compile; +use moor_compiler::{compile, CompileOptions}; use moor_db_wiredtiger::WiredTigerDB; use moor_kernel::builtins::BuiltinRegistry; use moor_kernel::config::Config; @@ -82,7 +82,7 @@ fn prepare_vm_execution( program: &str, max_ticks: usize, ) -> VmHost { - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let mut tx = ws_source.new_world_state().unwrap(); tx.add_verb( SYSTEM_OBJECT, diff --git a/crates/kernel/src/builtins/bf_server.rs b/crates/kernel/src/builtins/bf_server.rs index 6318e9e2..babca205 100644 --- a/crates/kernel/src/builtins/bf_server.rs +++ b/crates/kernel/src/builtins/bf_server.rs @@ -762,7 +762,7 @@ fn bf_eval(bf_args: &mut BfCallState<'_>) -> Result { match tramp { BF_SERVER_EVAL_TRAMPOLINE_START_INITIALIZE => { let program_code = program_code.as_str(); - let program = match compile(program_code) { + let program = match compile(program_code, bf_args.config.compile_options()) { Ok(program) => program, Err(e) => return Ok(Ret(v_listv(vec![v_int(0), v_string(e.to_string())]))), }; diff --git a/crates/kernel/src/builtins/bf_verbs.rs b/crates/kernel/src/builtins/bf_verbs.rs index 47183729..79c219f4 100644 --- a/crates/kernel/src/builtins/bf_verbs.rs +++ b/crates/kernel/src/builtins/bf_verbs.rs @@ -481,7 +481,7 @@ fn bf_set_verb_code(bf_args: &mut BfCallState<'_>) -> Result { code_string.push('\n'); } // Now try to compile... - let program = match compile(code_string.as_str()) { + let program = match compile(code_string.as_str(), bf_args.config.compile_options()) { Ok(program) => program, Err(e) => { // For set_verb_code(), the result is a list of strings, the error messages generated by the diff --git a/crates/kernel/src/config.rs b/crates/kernel/src/config.rs index 77847634..2b3128ee 100644 --- a/crates/kernel/src/config.rs +++ b/crates/kernel/src/config.rs @@ -16,9 +16,10 @@ //! available to all components. Used to hold things typically configured by CLI flags, etc. use crate::textdump::EncodingMode; +use moor_compiler::CompileOptions; use std::path::PathBuf; -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct Config { /// Whether to allow notify() to send arbitrary MOO values to players. The interpretation of /// the values varies depending on host/client. @@ -28,4 +29,26 @@ pub struct Config { pub textdump_output: Option, /// What encoding to use for textdumps (ISO-8859-1 or UTF-8). pub textdump_encoding: EncodingMode, + /// Whether to support block-level lexical scoping, and the 'begin', 'let' and 'global' + /// keywords. + pub lexical_scopes: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + rich_notify: true, + textdump_output: None, + textdump_encoding: EncodingMode::UTF8, + lexical_scopes: true, + } + } +} + +impl Config { + pub fn compile_options(&self) -> CompileOptions { + CompileOptions { + lexical_scopes: self.lexical_scopes, + } + } } diff --git a/crates/kernel/src/tasks/mod.rs b/crates/kernel/src/tasks/mod.rs index 864b33f9..833037a5 100644 --- a/crates/kernel/src/tasks/mod.rs +++ b/crates/kernel/src/tasks/mod.rs @@ -246,13 +246,13 @@ pub mod scheduler_test_utils { use moor_values::tasks::{CommandError, SchedulerError}; use moor_values::var::{Error::E_VERBNF, Objid, Var}; + use super::TaskHandle; + use crate::config::Config; use crate::tasks::scheduler_client::SchedulerClient; use crate::tasks::sessions::Session; use moor_values::tasks::Exception; use moor_values::tasks::SchedulerError::{CommandExecutionError, TaskAbortedException}; - use super::TaskHandle; - pub type ExecResult = Result; fn execute(fun: F) -> Result @@ -295,7 +295,9 @@ pub mod scheduler_test_utils { player: Objid, code: String, ) -> Result { - execute(|| scheduler.submit_eval_task(player, player, code, session)) + execute(|| { + scheduler.submit_eval_task(player, player, code, session, Arc::new(Config::default())) + }) } } diff --git a/crates/kernel/src/tasks/scheduler.rs b/crates/kernel/src/tasks/scheduler.rs index 98a1c6ff..a2d159d5 100644 --- a/crates/kernel/src/tasks/scheduler.rs +++ b/crates/kernel/src/tasks/scheduler.rs @@ -147,7 +147,7 @@ impl Scheduler { pub fn new( database: Box, tasks_database: Box, - config: Config, + config: Arc, ) -> Self { let (task_control_sender, task_control_receiver) = crossbeam_channel::unbounded(); let (scheduler_sender, scheduler_receiver) = crossbeam_channel::unbounded(); @@ -169,7 +169,7 @@ impl Scheduler { database, next_task_id: Default::default(), task_q, - config: Arc::new(config), + config, task_control_sender, task_control_receiver, scheduler_sender, @@ -311,9 +311,10 @@ impl Scheduler { return Err(VerbProgramFailed(VerbProgramError::NoVerbToProgram)); } - let program = compile(code.join("\n").as_str()).map_err(|e| { - VerbProgramFailed(VerbProgramError::CompilationError(vec![format!("{:?}", e)])) - })?; + let program = compile(code.join("\n").as_str(), self.config.compile_options()) + .map_err(|e| { + VerbProgramFailed(VerbProgramError::CompilationError(vec![format!("{:?}", e)])) + })?; // Now we have a program, we need to encode it. let binary = program diff --git a/crates/kernel/src/tasks/scheduler_client.rs b/crates/kernel/src/tasks/scheduler_client.rs index 3be76ef6..c880de5e 100644 --- a/crates/kernel/src/tasks/scheduler_client.rs +++ b/crates/kernel/src/tasks/scheduler_client.rs @@ -23,6 +23,7 @@ use moor_compiler::{compile, Program}; use moor_values::var::Symbol; use moor_values::var::{Objid, Var}; +use crate::config::Config; use crate::tasks::sessions::Session; use crate::tasks::TaskHandle; use moor_values::tasks::SchedulerError; @@ -159,9 +160,10 @@ impl SchedulerClient { perms: Objid, code: String, sessions: Arc, + config: Arc, ) -> Result { // Compile the text into a verb. - let program = match compile(code.as_str()) { + let program = match compile(code.as_str(), config.compile_options()) { Ok(b) => b, Err(e) => return Err(CompilationError(e)), }; diff --git a/crates/kernel/src/tasks/task.rs b/crates/kernel/src/tasks/task.rs index cf716bfe..44983464 100644 --- a/crates/kernel/src/tasks/task.rs +++ b/crates/kernel/src/tasks/task.rs @@ -646,7 +646,7 @@ mod tests { use crossbeam_channel::{unbounded, Receiver}; - use moor_compiler::{compile, Program}; + use moor_compiler::{compile, CompileOptions, Program}; use moor_db_wiredtiger::WiredTigerDB; use moor_values::model::{ ArgSpec, BinaryType, PrepSpec, VerbArgsSpec, VerbFlag, WorldState, WorldStateSource, @@ -758,7 +758,7 @@ mod tests { TaskSchedulerClient, Receiver<(TaskId, TaskControlMsg)>, ) { - let program = compile(program).unwrap(); + let program = compile(program, CompileOptions::default()).unwrap(); let task_start = Arc::new(TaskStart::StartEval { player: SYSTEM_OBJECT, program, @@ -1052,7 +1052,7 @@ mod tests { fn test_command_match() { let look_this = TestVerb { name: Symbol::mk("look"), - program: compile("return 1;").unwrap(), + program: compile("return 1;", CompileOptions::default()).unwrap(), argspec: VerbArgsSpec { dobj: ArgSpec::This, prep: PrepSpec::None, @@ -1086,7 +1086,7 @@ mod tests { fn test_command_do_command() { let do_command_verb = TestVerb { name: Symbol::mk("do_command"), - program: compile("return 1;").unwrap(), + program: compile("return 1;", CompileOptions::default()).unwrap(), argspec: VerbArgsSpec::this_none_this(), }; @@ -1118,7 +1118,7 @@ mod tests { fn test_command_do_command_false_no_match() { let do_command_verb = TestVerb { name: Symbol::mk("do_command"), - program: compile("return 0;").unwrap(), + program: compile("return 0;", CompileOptions::default()).unwrap(), argspec: VerbArgsSpec::this_none_this(), }; @@ -1149,13 +1149,13 @@ mod tests { fn test_command_do_command_false_match() { let do_command_verb = TestVerb { name: Symbol::mk("do_command"), - program: compile("return 0;").unwrap(), + program: compile("return 0;", CompileOptions::default()).unwrap(), argspec: VerbArgsSpec::this_none_this(), }; let look_this = TestVerb { name: Symbol::mk("look"), - program: compile("return 1;").unwrap(), + program: compile("return 1;", CompileOptions::default()).unwrap(), argspec: VerbArgsSpec { dobj: ArgSpec::This, prep: PrepSpec::None, diff --git a/crates/kernel/src/tasks/vm_host.rs b/crates/kernel/src/tasks/vm_host.rs index 94d2b5d7..b49cbd92 100644 --- a/crates/kernel/src/tasks/vm_host.rs +++ b/crates/kernel/src/tasks/vm_host.rs @@ -24,9 +24,9 @@ use bytes::Bytes; use daumtils::PhantomUnsync; use tracing::{debug, error, trace, warn}; -use moor_compiler::compile; use moor_compiler::Name; use moor_compiler::Program; +use moor_compiler::{compile, CompileOptions}; use moor_values::model::VerbInfo; use moor_values::model::WorldState; use moor_values::model::{BinaryType, ObjFlag}; @@ -204,7 +204,7 @@ impl VmHost { let program = if is_programmer { program } else { - compile("return E_PERM;").unwrap() + compile("return E_PERM;", CompileOptions::default()).unwrap() }; self.vm_exec_state.start_time = Some(SystemTime::now()); diff --git a/crates/kernel/src/textdump/load_db.rs b/crates/kernel/src/textdump/load_db.rs index 11768f1a..6e284c9d 100644 --- a/crates/kernel/src/textdump/load_db.rs +++ b/crates/kernel/src/textdump/load_db.rs @@ -20,8 +20,8 @@ use std::path::PathBuf; use tracing::{info, span, trace}; -use moor_compiler::compile; use moor_compiler::Program; +use moor_compiler::{compile, CompileOptions}; use moor_db::loader::LoaderInterface; use moor_values::model::Preposition; use moor_values::model::PropFlag; @@ -92,6 +92,7 @@ pub fn textdump_load( ldr: &dyn LoaderInterface, path: PathBuf, encoding_mode: EncodingMode, + compile_options: CompileOptions, ) -> Result<(), TextdumpReaderError> { let textdump_import_span = span!(tracing::Level::INFO, "textdump_import"); let _enter = textdump_import_span.enter(); @@ -101,13 +102,14 @@ pub fn textdump_load( let br = BufReader::new(corefile); - read_textdump(ldr, br, encoding_mode) + read_textdump(ldr, br, encoding_mode, compile_options) } pub fn read_textdump( loader: &dyn LoaderInterface, reader: BufReader, encoding_mode: EncodingMode, + compile_options: CompileOptions, ) -> Result<(), TextdumpReaderError> { let mut tdr = TextdumpReader::new(reader, encoding_mode); let td = tdr.read_textdump()?; @@ -208,14 +210,16 @@ pub fn read_textdump( let names: Vec<&str> = v.name.split(' ').collect(); let program = match td.verbs.get(&(*objid, vn)) { - Some(verb) if verb.program.is_some() => { - compile(verb.program.clone().unwrap().as_str()).map_err(|e| { - TextdumpReaderError::VerbCompileError( - format!("compiling verb #{}/{} ({:?})", objid.0, vn, names), - e.clone(), - ) - })? - } + Some(verb) if verb.program.is_some() => compile( + verb.program.clone().unwrap().as_str(), + compile_options.clone(), + ) + .map_err(|e| { + TextdumpReaderError::VerbCompileError( + format!("compiling verb #{}/{} ({:?})", objid.0, vn, names), + e.clone(), + ) + })?, // If the verb program is missing, then it's an empty program, and we'll put in // an empty binary. _ => Program::new(), diff --git a/crates/kernel/src/vm/vm_test.rs b/crates/kernel/src/vm/vm_test.rs index 73706462..5bd2be7e 100644 --- a/crates/kernel/src/vm/vm_test.rs +++ b/crates/kernel/src/vm/vm_test.rs @@ -33,11 +33,11 @@ mod tests { use crate::builtins::BuiltinRegistry; use crate::tasks::sessions::NoopClientSession; use crate::tasks::vm_test_utils::call_verb; - use moor_compiler::Names; use moor_compiler::Op; use moor_compiler::Op::*; use moor_compiler::Program; use moor_compiler::{compile, UnboundNames}; + use moor_compiler::{CompileOptions, Names}; use moor_db_wiredtiger::WiredTigerDB; use moor_values::var::Symbol; use test_case::test_case; @@ -285,7 +285,7 @@ mod tests { #[test] fn test_list_splice() { let program = "a = {1,2,3,4,5}; return {@a[2..4]};"; - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let mut state = test_db_with_verb("test", &binary) .new_world_state() .unwrap(); @@ -303,9 +303,12 @@ mod tests { #[test] fn test_if_or_expr() { let program = "if (1 || 0) return 1; else return 2; endif"; - let mut state = test_db_with_verb("test", &compile(program).unwrap()) - .new_world_state() - .unwrap(); + let mut state = test_db_with_verb( + "test", + &compile(program, CompileOptions::default()).unwrap(), + ) + .new_world_state() + .unwrap(); let session = Arc::new(NoopClientSession::new()); let result = call_verb( state.as_mut(), @@ -441,7 +444,7 @@ mod tests { } fn world_with_test_program(program: &str) -> Box { - let binary = compile(program).unwrap(); + let binary = compile(program, CompileOptions::default()).unwrap(); let db = test_db_with_verb("test", &binary); db.new_world_state().unwrap() } @@ -650,7 +653,7 @@ mod tests { fn test_try_finally_regression_1() { let program = r#"a = 1; try return "hello world"[2..$]; a = 3; finally a = 2; endtry return a;"#; - let compiled = compile(program).unwrap(); + let compiled = compile(program, CompileOptions::default()).unwrap(); let mut state = world_with_test_programs(&[("test", &compiled)]); let session = Arc::new(NoopClientSession::new()); let builtin_registry = Arc::new(BuiltinRegistry::new()); @@ -668,7 +671,7 @@ mod tests { // A 0 value was hanging around on the stack making the comparison fail. fn test_try_expr_regression() { let program = r#"if (E_INVARG == (vi = `verb_info(#-1, "blerg") ! ANY')) return 666; endif return 333;"#; - let compiled = compile(program).unwrap(); + let compiled = compile(program, CompileOptions::default()).unwrap(); let mut state = world_with_test_programs(&[("test", &compiled)]); let session = Arc::new(NoopClientSession::new()); let builtin_registry = Arc::new(BuiltinRegistry::new()); @@ -721,8 +724,14 @@ mod tests { let bottom_of_stack = r#"raise(E_ARGS);"#; let mut state = world_with_test_programs(&[ - ("raise_error", &compile(bottom_of_stack).unwrap()), - ("test", &compile(top_of_stack).unwrap()), + ( + "raise_error", + &compile(bottom_of_stack, CompileOptions::default()).unwrap(), + ), + ( + "test", + &compile(top_of_stack, CompileOptions::default()).unwrap(), + ), ]); let session = Arc::new(NoopClientSession::new()); let builtin_registry = Arc::new(BuiltinRegistry::new()); @@ -742,7 +751,7 @@ mod tests { return "hello world"[2..$]; except (E_RANGE) endtry"#; - let compiled = compile(program).unwrap(); + let compiled = compile(program, CompileOptions::default()).unwrap(); eprintln!("{}", compiled); let mut state = world_with_test_programs(&[("test", &compiled)]); let session = Arc::new(NoopClientSession::new()); @@ -760,7 +769,7 @@ mod tests { #[test] fn test_try_finally_returns() { let program = r#"try return 666; finally return 333; endtry"#; - let compiled = compile(program).unwrap(); + let compiled = compile(program, CompileOptions::default()).unwrap(); let mut state = world_with_test_programs(&[("test", &compiled)]); let session = Arc::new(NoopClientSession::new()); let builtin_registry = Arc::new(BuiltinRegistry::new()); @@ -785,7 +794,7 @@ mod tests { end return x; "#; - let compiled = compile(program).unwrap(); + let compiled = compile(program, CompileOptions::default()).unwrap(); let mut state = world_with_test_programs(&[("test", &compiled)]); let session = Arc::new(NoopClientSession::new()); let builtin_registry = Arc::new(BuiltinRegistry::new()); @@ -811,7 +820,7 @@ mod tests { end return x; "#; - let compiled = compile(program).unwrap(); + let compiled = compile(program, CompileOptions::default()).unwrap(); let mut state = world_with_test_programs(&[("test", &compiled)]); let session = Arc::new(NoopClientSession::new()); let builtin_registry = Arc::new(BuiltinRegistry::new()); @@ -837,7 +846,7 @@ mod tests { return {x, y}; end "#; - let compiled = compile(program).unwrap(); + let compiled = compile(program, CompileOptions::default()).unwrap(); let mut state = world_with_test_programs(&[("test", &compiled)]); let session = Arc::new(NoopClientSession::new()); let builtin_registry = Arc::new(BuiltinRegistry::new()); @@ -867,7 +876,7 @@ mod tests { end end "#; - let compiled = compile(program).unwrap(); + let compiled = compile(program, CompileOptions::default()).unwrap(); let mut state = world_with_test_programs(&[("test", &compiled)]); let session = Arc::new(NoopClientSession::new()); let builtin_registry = Arc::new(BuiltinRegistry::new()); @@ -894,7 +903,7 @@ mod tests { else return 0; endif"#; - let compiled = compile(program).unwrap(); + let compiled = compile(program, CompileOptions::default()).unwrap(); let mut state = world_with_test_programs(&[("test", &compiled)]); let session = Arc::new(NoopClientSession::new()); let builtin_registry = Arc::new(BuiltinRegistry::new()); @@ -918,7 +927,7 @@ mod tests { let y = 5; return {y, z}; endwhile"#; - let compiled = compile(program).unwrap(); + let compiled = compile(program, CompileOptions::default()).unwrap(); let mut state = world_with_test_programs(&[("test", &compiled)]); let session = Arc::new(NoopClientSession::new()); let builtin_registry = Arc::new(BuiltinRegistry::new()); @@ -942,7 +951,7 @@ mod tests { let y = 5; return {y, z}; endfor"#; - let compiled = compile(program).unwrap(); + let compiled = compile(program, CompileOptions::default()).unwrap(); let mut state = world_with_test_programs(&[("test", &compiled)]); let session = Arc::new(NoopClientSession::new()); let builtin_registry = Arc::new(BuiltinRegistry::new()); @@ -968,7 +977,7 @@ mod tests { except (E_INVARG) return 0; endtry"#; - let compiled = compile(program).unwrap(); + let compiled = compile(program, CompileOptions::default()).unwrap(); let mut state = world_with_test_programs(&[("test", &compiled)]); let session = Arc::new(NoopClientSession::new()); let builtin_registry = Arc::new(BuiltinRegistry::new()); diff --git a/crates/kernel/tests/textdump.rs b/crates/kernel/tests/textdump.rs index 66d70e8d..11025c0f 100644 --- a/crates/kernel/tests/textdump.rs +++ b/crates/kernel/tests/textdump.rs @@ -22,7 +22,7 @@ mod test { use text_diff::assert_diff; - use moor_compiler::Program; + use moor_compiler::{CompileOptions, Program}; use moor_db::loader::LoaderInterface; use moor_db::Database; use moor_db_wiredtiger::WiredTigerDB; @@ -45,8 +45,13 @@ mod test { } fn load_textdump_file(tx: &mut dyn LoaderInterface, path: &str) { - textdump_load(tx, PathBuf::from(path), EncodingMode::UTF8) - .expect("Could not load textdump"); + textdump_load( + tx, + PathBuf::from(path), + EncodingMode::UTF8, + CompileOptions::default(), + ) + .expect("Could not load textdump"); assert_eq!(tx.commit().unwrap(), CommitResult::Success); } @@ -183,7 +188,13 @@ mod test { let (db, _) = WiredTigerDB::open(None); let db = Arc::new(db); let mut tx = db.clone().loader_client().unwrap(); - textdump_load(tx.as_mut(), minimal_db, EncodingMode::UTF8).unwrap(); + textdump_load( + tx.as_mut(), + minimal_db, + EncodingMode::UTF8, + CompileOptions::default(), + ) + .unwrap(); assert_eq!(tx.commit().unwrap(), CommitResult::Success); // Check a few things in a new transaction. @@ -274,7 +285,13 @@ mod test { let db2 = Arc::new(db2); let buffered_string_reader = std::io::BufReader::new(textdump.as_bytes()); let mut lc = db2.clone().loader_client().unwrap(); - read_textdump(lc.as_mut(), buffered_string_reader, EncodingMode::UTF8).unwrap(); + read_textdump( + lc.as_mut(), + buffered_string_reader, + EncodingMode::UTF8, + CompileOptions::default(), + ) + .unwrap(); assert_eq!(lc.commit().unwrap(), CommitResult::Success); // Now go through the properties and verbs of all the objects on db1, and verify that diff --git a/crates/kernel/testsuite/common/mod.rs b/crates/kernel/testsuite/common/mod.rs index 9e19e82c..0f413a21 100644 --- a/crates/kernel/testsuite/common/mod.rs +++ b/crates/kernel/testsuite/common/mod.rs @@ -19,8 +19,8 @@ use pretty_assertions::assert_eq; use uuid::Uuid; use EncodingMode::UTF8; -use moor_compiler::compile; use moor_compiler::Program; +use moor_compiler::{compile, CompileOptions}; use moor_db::Database; #[cfg(feature = "relbox")] use moor_db_relbox::RelBoxWorldState; @@ -51,7 +51,8 @@ pub fn testsuite_dir() -> PathBuf { #[allow(dead_code)] pub fn load_textdump(db: &dyn Database) { let mut tx = db.loader_client().unwrap(); - textdump_load(tx.as_ref(), test_db_path(), UTF8).expect("Could not load textdump"); + textdump_load(tx.as_ref(), test_db_path(), UTF8, CompileOptions::default()) + .expect("Could not load textdump"); assert_eq!(tx.commit().unwrap(), CommitResult::Success); } @@ -106,7 +107,7 @@ pub fn compile_verbs(db: &dyn Database, verbs: &[(&str, &Program)]) { #[allow(dead_code)] pub fn run_as_verb(db: &dyn Database, expression: &str) -> ExecResult { - let binary = compile(expression).unwrap(); + let binary = compile(expression, CompileOptions::default()).unwrap(); let verb_uuid = Uuid::new_v4().to_string(); compile_verbs(db, &[(&verb_uuid, &binary)]); let mut state = db.new_world_state().unwrap(); @@ -129,7 +130,7 @@ pub fn eval( expression: &str, session: Arc, ) -> eyre::Result { - let binary = compile(expression)?; + let binary = compile(expression, CompileOptions::default())?; let mut state = db.new_world_state()?; let builtin_registry = Arc::new(BuiltinRegistry::new()); let result = diff --git a/crates/kernel/testsuite/moot_suite.rs b/crates/kernel/testsuite/moot_suite.rs index 29c612d5..a83a73e2 100644 --- a/crates/kernel/testsuite/moot_suite.rs +++ b/crates/kernel/testsuite/moot_suite.rs @@ -137,7 +137,7 @@ fn test(db: Box, path: &Path) { return; } let tasks_db = Box::new(NoopTasksDb {}); - let scheduler = Scheduler::new(db, tasks_db, Config::default()); + let scheduler = Scheduler::new(db, tasks_db, Arc::new(Config::default())); let scheduler_client = scheduler.client().unwrap(); let session_factory = Arc::new(NoopSessionFactory {}); let scheduler_loop_jh = std::thread::Builder::new()