├── playground ├── out │ └── .gitkeep ├── pkg │ └── index.js ├── .prettierignore ├── rust │ └── lox-wasm │ │ ├── pkg │ │ └── .gitkeep │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── .prettierrc ├── .eslintrc.json ├── app │ ├── icon.png │ ├── worker.ts │ ├── layout.tsx │ ├── globals.css │ └── page.tsx ├── postcss.config.js ├── next.config.js ├── src │ ├── lib │ │ └── utils.ts │ └── components │ │ └── ui │ │ ├── scroll-area.tsx │ │ ├── resizable.tsx │ │ └── button.tsx ├── components.json ├── .gitignore ├── tsconfig.json ├── README.md ├── package.json └── tailwind.config.ts ├── res ├── examples │ ├── empty_file.lox │ ├── comments │ │ ├── only_line_comment.lox │ │ ├── only_line_comment_and_line.lox │ │ ├── line_at_eof.lox │ │ └── unicode.lox │ ├── nil │ │ └── literal.lox │ ├── expressions │ │ ├── parse.lox │ │ └── evaluate.lox │ ├── function │ │ ├── empty_body.lox │ │ ├── body_must_be_block.lox │ │ ├── missing_comma_in_parameters.lox │ │ ├── print.lox │ │ ├── missing_arguments.lox │ │ ├── recursion.lox │ │ ├── extra_arguments.lox │ │ ├── local_recursion.lox │ │ ├── mutual_recursion.lox │ │ ├── nested_call_with_arguments.lox │ │ ├── local_mutual_recursion.lox │ │ ├── parameters.lox │ │ ├── too_many_parameters.lox │ │ └── too_many_arguments.lox │ ├── variable │ │ ├── uninitialized.lox │ │ ├── redeclare_global.lox │ │ ├── redefine_global.lox │ │ ├── use_nil_as_var.lox │ │ ├── use_this_as_var.lox │ │ ├── use_false_as_var.lox │ │ ├── use_global_in_initializer.lox │ │ ├── in_nested_block.lox │ │ ├── undefined_global.lox │ │ ├── unreached_undefined.lox │ │ ├── collide_with_parameter.lox │ │ ├── undefined_local.lox │ │ ├── duplicate_local.lox │ │ ├── duplicate_parameter.lox │ │ ├── shadow_global.lox │ │ ├── shadow_local.lox │ │ ├── use_local_in_initializer.lox │ │ ├── shadow_and_local.lox │ │ ├── local_from_method.lox │ │ ├── scope_reuse_in_different_blocks.lox │ │ ├── early_bound.lox │ │ └── in_middle_of_block.lox │ ├── call │ │ ├── nil.lox │ │ ├── bool.lox │ │ ├── num.lox │ │ ├── string.lox │ │ └── object.lox │ ├── class │ │ ├── empty.lox │ │ ├── inherit_self.lox │ │ ├── local_inherit_self.lox │ │ ├── local_inherit_other.lox │ │ ├── reference_self.lox │ │ ├── local_reference_self.lox │ │ └── inherited_method.lox │ ├── number │ │ ├── leading_dot.lox │ │ ├── trailing_dot.lox │ │ ├── decimal_point_at_eof.lox │ │ ├── literals.lox │ │ └── nan_equality.lox │ ├── operator │ │ ├── subtract.lox │ │ ├── add.lox │ │ ├── divide.lox │ │ ├── multiply.lox │ │ ├── negate_nonnum.lox │ │ ├── negate.lox │ │ ├── not_class.lox │ │ ├── add_nil_nil.lox │ │ ├── add_num_nil.lox │ │ ├── add_bool_nil.lox │ │ ├── add_bool_num.lox │ │ ├── add_string_nil.lox │ │ ├── add_bool_string.lox │ │ ├── divide_nonnum_num.lox │ │ ├── divide_num_nonnum.lox │ │ ├── greater_nonnum_num.lox │ │ ├── greater_num_nonnum.lox │ │ ├── less_nonnum_num.lox │ │ ├── less_num_nonnum.lox │ │ ├── multiply_nonnum_num.lox │ │ ├── multiply_num_nonnum.lox │ │ ├── subtract_nonnum_num.lox │ │ ├── subtract_num_nonnum.lox │ │ ├── greater_or_equal_nonnum_num.lox │ │ ├── greater_or_equal_num_nonnum.lox │ │ ├── less_or_equal_nonnum_num.lox │ │ ├── less_or_equal_num_nonnum.lox │ │ ├── not.lox │ │ ├── equals_method.lox │ │ ├── equals.lox │ │ ├── not_equals.lox │ │ ├── equals_class.lox │ │ └── comparison.lox │ ├── print │ │ └── missing_argument.lox │ ├── for │ │ ├── var_in_body.lox │ │ ├── fun_in_body.lox │ │ ├── class_in_body.lox │ │ ├── statement_condition.lox │ │ ├── statement_increment.lox │ │ ├── statement_initializer.lox │ │ ├── return_inside.lox │ │ ├── return_closure.lox │ │ ├── closure_in_body.lox │ │ ├── scope.lox │ │ └── syntax.lox │ ├── if │ │ ├── var_in_then.lox │ │ ├── fun_in_then.lox │ │ ├── class_in_then.lox │ │ ├── var_in_else.lox │ │ ├── fun_in_else.lox │ │ ├── class_in_else.lox │ │ ├── dangling_else.lox │ │ ├── else.lox │ │ ├── truth.lox │ │ └── if.lox │ ├── this │ │ ├── this_at_top_level.lox │ │ ├── this_in_top_level_function.lox │ │ ├── this_in_method.lox │ │ ├── closure.lox │ │ ├── nested_closure.lox │ │ └── nested_class.lox │ ├── unexpected_character.lox │ ├── while │ │ ├── var_in_body.lox │ │ ├── fun_in_body.lox │ │ ├── class_in_body.lox │ │ ├── return_inside.lox │ │ ├── return_closure.lox │ │ ├── closure_in_body.lox │ │ └── syntax.lox │ ├── assignment │ │ ├── grouping.lox │ │ ├── undefined.lox │ │ ├── prefix_operator.lox │ │ ├── infix_operator.lox │ │ ├── to_this.lox │ │ ├── syntax.lox │ │ ├── global.lox │ │ ├── associativity.lox │ │ └── local.lox │ ├── field │ │ ├── get_on_nil.lox │ │ ├── get_on_bool.lox │ │ ├── get_on_num.lox │ │ ├── get_on_string.lox │ │ ├── set_on_nil.lox │ │ ├── set_on_bool.lox │ │ ├── set_on_num.lox │ │ ├── get_on_class.lox │ │ ├── set_on_string.lox │ │ ├── set_evaluation_order.lox │ │ ├── get_on_function.lox │ │ ├── set_on_class.lox │ │ ├── set_on_function.lox │ │ ├── undefined.lox │ │ ├── call_nonfunction_field.lox │ │ ├── method.lox │ │ ├── call_function_field.lox │ │ ├── on_instance.lox │ │ ├── method_binds_this.lox │ │ ├── get_and_set_method.lox │ │ └── many.lox │ ├── method │ │ ├── empty_block.lox │ │ ├── not_found.lox │ │ ├── print_bound_method.lox │ │ ├── missing_arguments.lox │ │ ├── refer_to_name.lox │ │ ├── extra_arguments.lox │ │ ├── arity.lox │ │ ├── too_many_parameters.lox │ │ └── too_many_arguments.lox │ ├── return │ │ ├── after_if.lox │ │ ├── at_top_level.lox │ │ ├── after_while.lox │ │ ├── after_else.lox │ │ ├── in_function.lox │ │ ├── return_nil_if_no_value.lox │ │ └── in_method.lox │ ├── constructor │ ├── regression │ │ ├── 394.lox │ │ └── 40.lox │ ├── string │ │ ├── multiline.lox │ │ ├── unterminated.lox │ │ ├── literals.lox │ │ └── error_after_multiline.lox │ ├── bool │ │ ├── not.lox │ │ └── equality.lox │ ├── super │ │ ├── super_at_top_level.lox │ │ ├── super_in_top_level_function.lox │ │ ├── super_without_dot.lox │ │ ├── super_without_name.lox │ │ ├── no_superclass_bind.lox │ │ ├── no_superclass_call.lox │ │ ├── parenthesized.lox │ │ ├── no_superclass_method.lox │ │ ├── indirectly_inherited.lox │ │ ├── call_other_method.lox │ │ ├── call_same_method.lox │ │ ├── missing_arguments.lox │ │ ├── constructor.lox │ │ ├── this_in_superclass_method.lox │ │ ├── super_in_inherited_method.lox │ │ ├── extra_arguments.lox │ │ ├── bound_method.lox │ │ ├── closure.lox │ │ ├── super_in_closure_in_inherited_method.lox │ │ └── reassign_superclass.lox │ ├── inheritance │ │ ├── parenthesized_superclass.lox │ │ ├── inherit_from_nil.lox │ │ ├── inherit_from_number.lox │ │ ├── inherit_from_function.lox │ │ ├── constructor.lox │ │ ├── inherit_methods.lox │ │ └── set_fields_from_base_class.lox │ ├── block │ │ ├── empty.lox │ │ └── scope.lox │ ├── closure │ │ ├── open_closure_in_function.lox │ │ ├── closed_closure_in_function.lox │ │ ├── close_over_function_parameter.lox │ │ ├── reference_closure_multiple_times.lox │ │ ├── close_over_method_parameter.lox │ │ ├── assign_to_shadowed_later.lox │ │ ├── shadow_closure_with_local.lox │ │ ├── reuse_closure_slot.lox │ │ ├── nested_closure.lox │ │ ├── unused_closure.lox │ │ ├── assign_to_closure.lox │ │ ├── close_over_later_variable.lox │ │ └── unused_later_closure.lox │ ├── logical_operator │ │ ├── or_truth.lox │ │ ├── and_truth.lox │ │ ├── and.lox │ │ └── or.lox │ ├── limit │ │ ├── stack_overflow.lox │ │ ├── too_many_constants.lox │ │ ├── too_many_locals.lox │ │ └── too_many_upvalues.lox │ └── precedence.lox └── benchmarks │ ├── fib.lox │ ├── instantiation.lox │ ├── trees.lox │ ├── zoo.lox │ ├── zoo_batch.lox │ ├── equality.lox │ ├── invocation.lox │ ├── binary_trees.lox │ ├── method_call.lox │ ├── string_equality.lox │ └── properties.lox ├── src ├── types.rs ├── main.rs ├── lib.rs ├── syntax │ ├── parser.rs │ ├── mod.rs │ ├── lexer.rs │ └── ast.rs ├── vm │ ├── util.rs │ ├── allocator.rs │ ├── op.rs │ ├── gc.rs │ └── value.rs ├── playground.rs ├── cmd.rs └── lsp.rs ├── rustfmt.toml ├── .gitignore ├── LICENSE ├── tests └── lang.rs ├── .github └── workflows │ ├── pages.yml │ └── ci.yml ├── Taskfile.yml ├── Cargo.toml └── README.md /playground/out/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playground/pkg/index.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/examples/empty_file.lox: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playground/.prettierignore: -------------------------------------------------------------------------------- 1 | rust/ 2 | -------------------------------------------------------------------------------- /playground/rust/lox-wasm/pkg/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /res/examples/comments/only_line_comment.lox: -------------------------------------------------------------------------------- 1 | // comment -------------------------------------------------------------------------------- /res/examples/nil/literal.lox: -------------------------------------------------------------------------------- 1 | print nil; // out: nil 2 | -------------------------------------------------------------------------------- /playground/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all" 3 | } 4 | -------------------------------------------------------------------------------- /res/examples/comments/only_line_comment_and_line.lox: -------------------------------------------------------------------------------- 1 | // comment 2 | -------------------------------------------------------------------------------- /res/examples/expressions/parse.lox: -------------------------------------------------------------------------------- 1 | // out: 2 2 | print (5 - (3 - 1)) + -1; 3 | -------------------------------------------------------------------------------- /res/examples/function/empty_body.lox: -------------------------------------------------------------------------------- 1 | fun f() {} 2 | print f(); // out: nil 3 | -------------------------------------------------------------------------------- /res/examples/variable/uninitialized.lox: -------------------------------------------------------------------------------- 1 | var a; 2 | print a; // out: nil 3 | -------------------------------------------------------------------------------- /res/examples/comments/line_at_eof.lox: -------------------------------------------------------------------------------- 1 | print "ok"; // out: ok 2 | // comment 3 | -------------------------------------------------------------------------------- /res/examples/expressions/evaluate.lox: -------------------------------------------------------------------------------- 1 | // out: 2 2 | print (5 - (3 - 1)) + -1; 3 | -------------------------------------------------------------------------------- /res/examples/call/nil.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: "nil" object is not callable 2 | nil(); 3 | -------------------------------------------------------------------------------- /res/examples/class/empty.lox: -------------------------------------------------------------------------------- 1 | class Foo {} 2 | 3 | print Foo; // out: 4 | -------------------------------------------------------------------------------- /res/examples/number/leading_dot.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "." 2 | .123; 3 | -------------------------------------------------------------------------------- /res/examples/number/trailing_dot.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected ";" 2 | 123.; 3 | -------------------------------------------------------------------------------- /playground/rust/lox-wasm/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by wasm-pack 2 | pkg/* 3 | !pkg/.gitkeep 4 | -------------------------------------------------------------------------------- /res/examples/call/bool.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: "bool" object is not callable 2 | true(); 3 | -------------------------------------------------------------------------------- /res/examples/call/num.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: "number" object is not callable 2 | 123(); 3 | -------------------------------------------------------------------------------- /res/examples/operator/subtract.lox: -------------------------------------------------------------------------------- 1 | print 4 - 3; // out: 1 2 | print 1.2 - 1.2; // out: 0 3 | -------------------------------------------------------------------------------- /res/examples/print/missing_argument.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected ";" 2 | print; 3 | -------------------------------------------------------------------------------- /playground/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /res/examples/call/string.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: "string" object is not callable 2 | "str"(); 3 | -------------------------------------------------------------------------------- /res/examples/for/var_in_body.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "var" 2 | for (;;) var foo; 3 | -------------------------------------------------------------------------------- /res/examples/if/var_in_then.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "var" 2 | if (true) var foo; 3 | -------------------------------------------------------------------------------- /res/examples/this/this_at_top_level.lox: -------------------------------------------------------------------------------- 1 | this; // out: SyntaxError: "this" used outside class 2 | -------------------------------------------------------------------------------- /res/examples/unexpected_character.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected input 2 | foo(a | b); 3 | -------------------------------------------------------------------------------- /res/examples/variable/redeclare_global.lox: -------------------------------------------------------------------------------- 1 | var a = "1"; 2 | var a; 3 | print a; // out: nil 4 | -------------------------------------------------------------------------------- /res/examples/variable/redefine_global.lox: -------------------------------------------------------------------------------- 1 | var a = "1"; 2 | var a = "2"; 3 | print a; // out: 2 4 | -------------------------------------------------------------------------------- /playground/app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ajeetdsouza/loxcraft/HEAD/playground/app/icon.png -------------------------------------------------------------------------------- /res/examples/for/fun_in_body.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "fun" 2 | for (;;) fun foo() {} 3 | -------------------------------------------------------------------------------- /res/examples/function/body_must_be_block.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "123" 2 | fun f() 123; 3 | -------------------------------------------------------------------------------- /res/examples/if/fun_in_then.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "fun" 2 | if (true) fun foo() {} 3 | -------------------------------------------------------------------------------- /res/examples/number/decimal_point_at_eof.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected end of file 2 | 123. 3 | -------------------------------------------------------------------------------- /res/examples/operator/add.lox: -------------------------------------------------------------------------------- 1 | print 123 + 456; // out: 579 2 | print "str" + "ing"; // out: string 3 | -------------------------------------------------------------------------------- /res/examples/operator/divide.lox: -------------------------------------------------------------------------------- 1 | print 8 / 2; // out: 4 2 | print 12.34 / 12.34; // out: 1 3 | -------------------------------------------------------------------------------- /res/examples/operator/multiply.lox: -------------------------------------------------------------------------------- 1 | print 5 * 3; // out: 15 2 | print 12.34 * 0.3; // out: 3.702 3 | -------------------------------------------------------------------------------- /res/examples/while/var_in_body.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "var" 2 | while (true) var foo; 3 | -------------------------------------------------------------------------------- /res/examples/assignment/grouping.lox: -------------------------------------------------------------------------------- 1 | var a = "a"; 2 | (a) = "value"; // out: SyntaxError: unexpected "=" 3 | -------------------------------------------------------------------------------- /res/examples/field/get_on_nil.lox: -------------------------------------------------------------------------------- 1 | nil.foo; // out: AttributeError: "nil" object has no attribute "foo" 2 | -------------------------------------------------------------------------------- /res/examples/for/class_in_body.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "class" 2 | for (;;) class Foo {} 3 | -------------------------------------------------------------------------------- /res/examples/if/class_in_then.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "class" 2 | if (true) class Foo {} 3 | -------------------------------------------------------------------------------- /res/examples/if/var_in_else.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "var" 2 | if (true) "ok"; else var foo; 3 | -------------------------------------------------------------------------------- /res/examples/variable/use_nil_as_var.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "nil" 2 | var nil = "value"; 3 | -------------------------------------------------------------------------------- /res/examples/variable/use_this_as_var.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "this" 2 | var this = "value"; 3 | -------------------------------------------------------------------------------- /res/examples/while/fun_in_body.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "fun" 2 | while (true) fun foo() {} 3 | -------------------------------------------------------------------------------- /res/examples/assignment/undefined.lox: -------------------------------------------------------------------------------- 1 | // out: NameError: name "unknown" is not defined 2 | unknown = "what"; 3 | -------------------------------------------------------------------------------- /res/examples/class/inherit_self.lox: -------------------------------------------------------------------------------- 1 | class Foo < Foo {} // out: NameError: class "Foo" inherits from itself 2 | -------------------------------------------------------------------------------- /res/examples/field/get_on_bool.lox: -------------------------------------------------------------------------------- 1 | true.foo; // out: AttributeError: "bool" object has no attribute "foo" 2 | -------------------------------------------------------------------------------- /res/examples/field/get_on_num.lox: -------------------------------------------------------------------------------- 1 | 123.foo; // out: AttributeError: "number" object has no attribute "foo" 2 | -------------------------------------------------------------------------------- /res/examples/if/fun_in_else.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "fun" 2 | if (true) "ok"; else fun foo() {} 3 | -------------------------------------------------------------------------------- /res/examples/method/empty_block.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | bar() {} 3 | } 4 | 5 | print Foo().bar(); // out: nil 6 | -------------------------------------------------------------------------------- /res/examples/operator/negate_nonnum.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type for -: "string" 2 | -"s"; 3 | -------------------------------------------------------------------------------- /res/examples/return/after_if.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | if (true) return "ok"; 3 | } 4 | 5 | print f(); // out: ok 6 | -------------------------------------------------------------------------------- /res/examples/return/at_top_level.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: "return" used outside function 2 | return "wat"; 3 | -------------------------------------------------------------------------------- /res/examples/variable/use_false_as_var.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "false" 2 | var false = "value"; 3 | -------------------------------------------------------------------------------- /res/examples/while/class_in_body.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "class" 2 | while (true) class Foo {} 3 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | pub type Spanned = (T, Span); 4 | pub type Span = Range; 5 | -------------------------------------------------------------------------------- /res/examples/assignment/prefix_operator.lox: -------------------------------------------------------------------------------- 1 | var a = "a"; 2 | !a = "value"; // out: SyntaxError: unexpected "=" 3 | -------------------------------------------------------------------------------- /res/examples/constructor/default.lox: -------------------------------------------------------------------------------- 1 | class Foo {} 2 | 3 | var foo = Foo(); 4 | print foo; // out: 5 | -------------------------------------------------------------------------------- /res/examples/field/get_on_string.lox: -------------------------------------------------------------------------------- 1 | "str".foo; // out: AttributeError: "string" object has no attribute "foo" 2 | -------------------------------------------------------------------------------- /res/examples/field/set_on_nil.lox: -------------------------------------------------------------------------------- 1 | nil.foo = "value"; // out: AttributeError: "nil" object has no attribute "foo" 2 | -------------------------------------------------------------------------------- /res/examples/for/statement_condition.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "{" 2 | for (var a = 1; {}; a = a + 1) {} 3 | -------------------------------------------------------------------------------- /res/examples/for/statement_increment.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "{" 2 | for (var a = 1; a < 2; {}) {} 3 | -------------------------------------------------------------------------------- /res/examples/for/statement_initializer.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "{" 2 | for ({}; a < 2; a = a + 1) {} 3 | -------------------------------------------------------------------------------- /res/examples/if/class_in_else.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "class" 2 | if (true) "ok"; else class Foo {} 3 | -------------------------------------------------------------------------------- /res/examples/operator/negate.lox: -------------------------------------------------------------------------------- 1 | print -(3); // out: -3 2 | print --(3); // out: 3 3 | print ---(3); // out: -3 4 | -------------------------------------------------------------------------------- /res/examples/regression/394.lox: -------------------------------------------------------------------------------- 1 | { 2 | class A {} 3 | class B < A {} 4 | print B; // out: 5 | } 6 | -------------------------------------------------------------------------------- /res/examples/return/after_while.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | while (true) return "ok"; 3 | } 4 | 5 | print f(); // out: ok 6 | -------------------------------------------------------------------------------- /res/examples/string/multiline.lox: -------------------------------------------------------------------------------- 1 | // out: 1 2 | // out: 2 3 | // out: 3 4 | var a = "1 5 | 2 6 | 3"; 7 | print a; 8 | -------------------------------------------------------------------------------- /res/examples/string/unterminated.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unterminated string 2 | "this string has no close quote 3 | -------------------------------------------------------------------------------- /res/examples/variable/use_global_in_initializer.lox: -------------------------------------------------------------------------------- 1 | var a = "value"; 2 | var a = a; 3 | print a; // out: value 4 | -------------------------------------------------------------------------------- /res/examples/field/set_on_bool.lox: -------------------------------------------------------------------------------- 1 | true.foo = "value"; // out: AttributeError: "bool" object has no attribute "foo" 2 | -------------------------------------------------------------------------------- /res/examples/field/set_on_num.lox: -------------------------------------------------------------------------------- 1 | 123.foo = "value"; // out: AttributeError: "number" object has no attribute "foo" 2 | -------------------------------------------------------------------------------- /res/examples/operator/not_class.lox: -------------------------------------------------------------------------------- 1 | class Bar {} 2 | print !Bar; // out: false 3 | print !Bar(); // out: false 4 | -------------------------------------------------------------------------------- /res/examples/variable/in_nested_block.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = "outer"; 3 | { 4 | print a; // out: outer 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /res/examples/variable/undefined_global.lox: -------------------------------------------------------------------------------- 1 | // out: NameError: name "notDefined" is not defined 2 | print notDefined; 3 | -------------------------------------------------------------------------------- /res/examples/bool/not.lox: -------------------------------------------------------------------------------- 1 | print !true; // out: false 2 | print !false; // out: true 3 | print !!true; // out: true 4 | -------------------------------------------------------------------------------- /res/examples/field/get_on_class.lox: -------------------------------------------------------------------------------- 1 | class Foo {} 2 | Foo.bar; // out: AttributeError: "class" object has no attribute "bar" 3 | -------------------------------------------------------------------------------- /res/examples/field/set_on_string.lox: -------------------------------------------------------------------------------- 1 | "str".foo = "value"; // out: AttributeError: "string" object has no attribute "foo" 2 | -------------------------------------------------------------------------------- /res/examples/function/missing_comma_in_parameters.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: unexpected "c" 2 | fun foo(a, b c, d, e, f) {} 3 | -------------------------------------------------------------------------------- /res/examples/operator/add_nil_nil.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for +: "nil" and "nil" 2 | nil + nil; 3 | -------------------------------------------------------------------------------- /res/examples/operator/add_num_nil.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for +: "number" and "nil" 2 | 1 + nil; 3 | -------------------------------------------------------------------------------- /res/examples/return/after_else.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | if (false) "no"; else return "ok"; 3 | } 4 | 5 | print f(); // out: ok 6 | -------------------------------------------------------------------------------- /res/examples/return/in_function.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | return "ok"; 3 | print "bad"; 4 | } 5 | 6 | print f(); // out: ok 7 | -------------------------------------------------------------------------------- /res/examples/variable/unreached_undefined.lox: -------------------------------------------------------------------------------- 1 | if (false) { 2 | print notDefined; 3 | } 4 | 5 | // out: ok 6 | print "ok"; 7 | -------------------------------------------------------------------------------- /res/examples/assignment/infix_operator.lox: -------------------------------------------------------------------------------- 1 | var a = "a"; 2 | var b = "b"; 3 | a + b = "value"; // out: SyntaxError: unexpected "=" 4 | -------------------------------------------------------------------------------- /res/examples/class/local_inherit_self.lox: -------------------------------------------------------------------------------- 1 | { 2 | class Foo < Foo {} // out: NameError: class "Foo" inherits from itself 3 | } 4 | -------------------------------------------------------------------------------- /res/examples/field/set_evaluation_order.lox: -------------------------------------------------------------------------------- 1 | // out: NameError: name "undefined2" is not defined 2 | undefined1.bar = undefined2; 3 | -------------------------------------------------------------------------------- /res/examples/function/print.lox: -------------------------------------------------------------------------------- 1 | fun foo() {} 2 | print foo; // out: 3 | 4 | print clock; // out: 5 | -------------------------------------------------------------------------------- /res/examples/operator/add_bool_nil.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for +: "bool" and "nil" 2 | true + nil; 3 | -------------------------------------------------------------------------------- /res/examples/operator/add_bool_num.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for +: "bool" and "number" 2 | true + 123; 3 | -------------------------------------------------------------------------------- /res/examples/operator/add_string_nil.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for +: "string" and "nil" 2 | "s" + nil; 3 | -------------------------------------------------------------------------------- /res/examples/return/return_nil_if_no_value.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | return; 3 | print "bad"; 4 | } 5 | 6 | print f(); // out: nil 7 | -------------------------------------------------------------------------------- /res/examples/super/super_at_top_level.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: "super" used outside class 2 | super.foo("bar"); 3 | super.foo; 4 | -------------------------------------------------------------------------------- /res/examples/variable/collide_with_parameter.lox: -------------------------------------------------------------------------------- 1 | fun foo(a) { 2 | var a; // out: NameError: name "a" is already defined 3 | } 4 | -------------------------------------------------------------------------------- /playground/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /res/examples/call/object.lox: -------------------------------------------------------------------------------- 1 | class Foo {} 2 | 3 | var foo = Foo(); 4 | foo(); // out: TypeError: "instance" object is not callable 5 | -------------------------------------------------------------------------------- /res/examples/field/get_on_function.lox: -------------------------------------------------------------------------------- 1 | fun foo() {} 2 | 3 | foo.bar; // out: AttributeError: "function" object has no attribute "bar" 4 | -------------------------------------------------------------------------------- /res/examples/field/set_on_class.lox: -------------------------------------------------------------------------------- 1 | class Foo {} 2 | Foo.bar = "value"; // out: AttributeError: "class" object has no attribute "bar" 3 | -------------------------------------------------------------------------------- /res/examples/operator/add_bool_string.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for +: "bool" and "string" 2 | true + "s"; 3 | -------------------------------------------------------------------------------- /res/examples/operator/divide_nonnum_num.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for /: "string" and "number" 2 | "1" / 1; 3 | -------------------------------------------------------------------------------- /res/examples/operator/divide_num_nonnum.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for /: "number" and "string" 2 | 1 / "1"; 3 | -------------------------------------------------------------------------------- /res/examples/operator/greater_nonnum_num.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for >: "string" and "number" 2 | "1" > 1; 3 | -------------------------------------------------------------------------------- /res/examples/operator/greater_num_nonnum.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for >: "number" and "string" 2 | 1 > "1"; 3 | -------------------------------------------------------------------------------- /res/examples/operator/less_nonnum_num.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for <: "string" and "number" 2 | "1" < 1; 3 | -------------------------------------------------------------------------------- /res/examples/operator/less_num_nonnum.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for <: "number" and "string" 2 | 1 < "1"; 3 | -------------------------------------------------------------------------------- /res/examples/operator/multiply_nonnum_num.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for *: "string" and "number" 2 | "1" * 1; 3 | -------------------------------------------------------------------------------- /res/examples/operator/multiply_num_nonnum.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for *: "number" and "string" 2 | 1 * "1"; 3 | -------------------------------------------------------------------------------- /res/examples/operator/subtract_nonnum_num.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for -: "string" and "number" 2 | "1" - 1; 3 | -------------------------------------------------------------------------------- /res/examples/operator/subtract_num_nonnum.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for -: "number" and "string" 2 | 1 - "1"; 3 | -------------------------------------------------------------------------------- /res/examples/variable/undefined_local.lox: -------------------------------------------------------------------------------- 1 | { 2 | // out: NameError: name "notDefined" is not defined 3 | print notDefined; 4 | } 5 | -------------------------------------------------------------------------------- /res/examples/function/missing_arguments.lox: -------------------------------------------------------------------------------- 1 | fun f(a, b) {} 2 | 3 | // out: TypeError: f() takes 2 arguments but 1 were given 4 | f(1); 5 | -------------------------------------------------------------------------------- /res/examples/inheritance/parenthesized_superclass.lox: -------------------------------------------------------------------------------- 1 | class Foo {} 2 | 3 | // out: SyntaxError: unexpected "(" 4 | class Bar < (Foo) {} 5 | -------------------------------------------------------------------------------- /res/examples/super/super_in_top_level_function.lox: -------------------------------------------------------------------------------- 1 | // out: SyntaxError: "super" used outside class 2 | super.bar(); 3 | fun foo() { 4 | } 5 | -------------------------------------------------------------------------------- /res/examples/field/set_on_function.lox: -------------------------------------------------------------------------------- 1 | fun foo() {} 2 | 3 | foo.bar = "value"; // out: AttributeError: "function" object has no attribute "bar" 4 | -------------------------------------------------------------------------------- /res/examples/field/undefined.lox: -------------------------------------------------------------------------------- 1 | class Foo {} 2 | var foo = Foo(); 3 | 4 | foo.bar; // out: AttributeError: "Foo" object has no attribute "bar" 5 | -------------------------------------------------------------------------------- /res/examples/for/return_inside.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | for (;;) { 3 | var i = "i"; 4 | return i; 5 | } 6 | } 7 | 8 | print f(); // out: i 9 | -------------------------------------------------------------------------------- /res/examples/method/not_found.lox: -------------------------------------------------------------------------------- 1 | class Foo {} 2 | 3 | // out: AttributeError: "Foo" object has no attribute "unknown" 4 | Foo().unknown(); 5 | -------------------------------------------------------------------------------- /res/examples/operator/greater_or_equal_nonnum_num.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for >=: "string" and "number" 2 | "1" >= 1; 3 | -------------------------------------------------------------------------------- /res/examples/operator/greater_or_equal_num_nonnum.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for >=: "number" and "string" 2 | 1 >= "1"; 3 | -------------------------------------------------------------------------------- /res/examples/operator/less_or_equal_nonnum_num.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for <=: "string" and "number" 2 | "1" <= 1; 3 | -------------------------------------------------------------------------------- /res/examples/operator/less_or_equal_num_nonnum.lox: -------------------------------------------------------------------------------- 1 | // out: TypeError: unsupported operand type(s) for <=: "number" and "string" 2 | 1 <= "1"; 3 | -------------------------------------------------------------------------------- /res/examples/variable/duplicate_local.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = "value"; 3 | var a = "other"; // out: NameError: name "a" is already defined 4 | } 5 | -------------------------------------------------------------------------------- /res/examples/block/empty.lox: -------------------------------------------------------------------------------- 1 | {} // By itself. 2 | 3 | // In a statement. 4 | if (true) {} 5 | if (false) {} else {} 6 | 7 | print "ok"; // out: ok 8 | -------------------------------------------------------------------------------- /res/examples/block/scope.lox: -------------------------------------------------------------------------------- 1 | var a = "outer"; 2 | 3 | { 4 | var a = "inner"; 5 | print a; // out: inner 6 | } 7 | 8 | print a; // out: outer 9 | -------------------------------------------------------------------------------- /res/examples/this/this_in_top_level_function.lox: -------------------------------------------------------------------------------- 1 | fun foo() { 2 | this; 3 | } 4 | 5 | // out: SyntaxError: "this" used outside class 6 | foo(); 7 | -------------------------------------------------------------------------------- /res/examples/variable/duplicate_parameter.lox: -------------------------------------------------------------------------------- 1 | fun foo(arg, 2 | arg) { // out: NameError: name "arg" is already defined 3 | "body"; 4 | } 5 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use clap::Parser; 3 | use loxcraft::cmd::Cmd; 4 | 5 | fn main() -> Result<()> { 6 | Cmd::parse().run() 7 | } 8 | -------------------------------------------------------------------------------- /res/examples/assignment/to_this.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | Foo() { 3 | this = "value"; // out: SyntaxError: unexpected "=" 4 | } 5 | } 6 | 7 | Foo(); 8 | -------------------------------------------------------------------------------- /res/examples/class/local_inherit_other.lox: -------------------------------------------------------------------------------- 1 | class A {} 2 | 3 | fun f() { 4 | class B < A {} 5 | return B; 6 | } 7 | 8 | print f(); // out: 9 | -------------------------------------------------------------------------------- /res/examples/closure/open_closure_in_function.lox: -------------------------------------------------------------------------------- 1 | { 2 | var local = "local"; 3 | fun f() { 4 | print local; // out: local 5 | } 6 | f(); 7 | } 8 | -------------------------------------------------------------------------------- /res/examples/constructor/default_arguments.lox: -------------------------------------------------------------------------------- 1 | class Foo {} 2 | 3 | var foo = Foo(1, 2, 3); // out: TypeError: init() takes 0 arguments but 3 were given 4 | -------------------------------------------------------------------------------- /res/examples/function/recursion.lox: -------------------------------------------------------------------------------- 1 | fun fib(n) { 2 | if (n < 2) return n; 3 | return fib(n - 1) + fib(n - 2); 4 | } 5 | 6 | print fib(8); // out: 21 7 | -------------------------------------------------------------------------------- /res/examples/inheritance/inherit_from_nil.lox: -------------------------------------------------------------------------------- 1 | var Nil = nil; 2 | // out: TypeError: superclass should be of type "class", not "nil" 3 | class Foo < Nil {} 4 | -------------------------------------------------------------------------------- /res/examples/method/print_bound_method.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | method() { } 3 | } 4 | var foo = Foo(); 5 | print foo.method; // out: 6 | -------------------------------------------------------------------------------- /res/examples/variable/shadow_global.lox: -------------------------------------------------------------------------------- 1 | var a = "global"; 2 | { 3 | var a = "shadow"; 4 | print a; // out: shadow 5 | } 6 | print a; // out: global 7 | -------------------------------------------------------------------------------- /res/examples/while/return_inside.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | while (true) { 3 | var i = "i"; 4 | return i; 5 | } 6 | } 7 | 8 | print f(); // out: i 9 | -------------------------------------------------------------------------------- /res/examples/class/reference_self.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | returnSelf() { 3 | return Foo; 4 | } 5 | } 6 | 7 | print Foo().returnSelf(); // out: 8 | -------------------------------------------------------------------------------- /res/examples/this/this_in_method.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | bar() { return this; } 3 | baz() { return "baz"; } 4 | } 5 | 6 | print Foo().bar().baz(); // out: baz 7 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod cmd; 2 | pub mod error; 3 | pub mod lsp; 4 | pub mod playground; 5 | pub mod repl; 6 | pub mod syntax; 7 | pub mod types; 8 | pub mod vm; 9 | -------------------------------------------------------------------------------- /playground/next.config.js: -------------------------------------------------------------------------------- 1 | const nextConfig = { 2 | basePath: process.env.BASE_PATH ?? "", 3 | output: "export", 4 | }; 5 | 6 | module.exports = nextConfig; 7 | -------------------------------------------------------------------------------- /res/examples/assignment/syntax.lox: -------------------------------------------------------------------------------- 1 | // Assignment on RHS of variable. 2 | var a = "before"; 3 | var c = a = "var"; 4 | print a; // out: var 5 | print c; // out: var 6 | -------------------------------------------------------------------------------- /res/examples/return/in_method.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | method() { 3 | return "ok"; 4 | print "bad"; 5 | } 6 | } 7 | 8 | print Foo().method(); // out: ok 9 | -------------------------------------------------------------------------------- /res/examples/string/literals.lox: -------------------------------------------------------------------------------- 1 | print "(" + "" + ")"; // out: () 2 | print "a string"; // out: a string 3 | 4 | // Non-ASCII. 5 | print "A~¶Þॐஃ"; // out: A~¶Þॐஃ 6 | -------------------------------------------------------------------------------- /res/examples/super/super_without_dot.lox: -------------------------------------------------------------------------------- 1 | class A {} 2 | 3 | class B < A { 4 | method() { 5 | // out: SyntaxError: unexpected ";" 6 | super; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /playground/app/worker.ts: -------------------------------------------------------------------------------- 1 | import init, { loxRun } from "lox-wasm"; 2 | 3 | onmessage = async (event) => { 4 | await init(); 5 | loxRun(event.data as string); 6 | }; 7 | -------------------------------------------------------------------------------- /res/examples/inheritance/inherit_from_number.lox: -------------------------------------------------------------------------------- 1 | var Number = 123; 2 | // out: TypeError: superclass should be of type "class", not "number" 3 | class Foo < Number {} 4 | -------------------------------------------------------------------------------- /res/examples/super/super_without_name.lox: -------------------------------------------------------------------------------- 1 | class A {} 2 | 3 | class B < A { 4 | method() { 5 | // out: SyntaxError: unexpected ";" 6 | super.; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /res/examples/constructor/missing_arguments.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | init(a, b) {} 3 | } 4 | 5 | var foo = Foo(1); // out: TypeError: init() takes 2 arguments but 1 were given 6 | -------------------------------------------------------------------------------- /res/examples/inheritance/inherit_from_function.lox: -------------------------------------------------------------------------------- 1 | fun foo() {} 2 | 3 | // out: TypeError: superclass should be of type "class", not "function" 4 | class Subclass < foo {} 5 | -------------------------------------------------------------------------------- /res/examples/method/missing_arguments.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | method(a, b) {} 3 | } 4 | 5 | // out: TypeError: method() takes 2 arguments but 1 were given 6 | Foo().method(1); 7 | -------------------------------------------------------------------------------- /res/examples/variable/shadow_local.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = "local"; 3 | { 4 | var a = "shadow"; 5 | print a; // out: shadow 6 | } 7 | print a; // out: local 8 | } 9 | -------------------------------------------------------------------------------- /res/examples/variable/use_local_in_initializer.lox: -------------------------------------------------------------------------------- 1 | var a = "outer"; 2 | { 3 | // out: NameError: cannot access variable "a" in its own initializer 4 | var a = a; 5 | } 6 | -------------------------------------------------------------------------------- /res/examples/field/call_nonfunction_field.lox: -------------------------------------------------------------------------------- 1 | class Foo {} 2 | 3 | var foo = Foo(); 4 | foo.bar = "not fn"; 5 | 6 | foo.bar(); // out: TypeError: "string" object is not callable 7 | -------------------------------------------------------------------------------- /res/examples/function/extra_arguments.lox: -------------------------------------------------------------------------------- 1 | fun f(a, b) { 2 | print a; 3 | print b; 4 | } 5 | 6 | // out: TypeError: f() takes 2 arguments but 4 were given 7 | f(1, 2, 3, 4); 8 | -------------------------------------------------------------------------------- /res/examples/method/refer_to_name.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | method() { 3 | print method; // out: NameError: name "method" is not defined 4 | } 5 | } 6 | 7 | Foo().method(); 8 | -------------------------------------------------------------------------------- /res/examples/variable/shadow_and_local.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = "outer"; 3 | { 4 | print a; // out: outer 5 | var a = "inner"; 6 | print a; // out: inner 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /res/examples/constructor/return_value.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | init() { 3 | return "result"; 4 | } 5 | } 6 | 7 | // out: SyntaxError: init() should not return a value 8 | Foo(); 9 | -------------------------------------------------------------------------------- /res/examples/function/local_recursion.lox: -------------------------------------------------------------------------------- 1 | { 2 | fun fib(n) { 3 | if (n < 2) return n; 4 | return fib(n - 1) + fib(n - 2); 5 | } 6 | 7 | print fib(8); // out: 21 8 | } 9 | -------------------------------------------------------------------------------- /res/examples/variable/local_from_method.lox: -------------------------------------------------------------------------------- 1 | var foo = "variable"; 2 | 3 | class Foo { 4 | method() { 5 | print foo; 6 | } 7 | } 8 | 9 | Foo().method(); // out: variable 10 | -------------------------------------------------------------------------------- /res/benchmarks/fib.lox: -------------------------------------------------------------------------------- 1 | fun fib(n) { 2 | if (n < 2) return n; 3 | return fib(n - 2) + fib(n - 1); 4 | } 5 | 6 | var start = clock(); 7 | print fib(39) == 63245986; 8 | print clock() - start; 9 | -------------------------------------------------------------------------------- /res/examples/assignment/global.lox: -------------------------------------------------------------------------------- 1 | var a = "before"; 2 | print a; // out: before 3 | 4 | a = "after"; 5 | print a; // out: after 6 | 7 | print a = "arg"; // out: arg 8 | print a; // out: arg 9 | -------------------------------------------------------------------------------- /res/examples/class/local_reference_self.lox: -------------------------------------------------------------------------------- 1 | { 2 | class Foo { 3 | returnSelf() { 4 | return Foo; 5 | } 6 | } 7 | 8 | print Foo().returnSelf(); // out: 9 | } 10 | -------------------------------------------------------------------------------- /res/examples/for/return_closure.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | for (;;) { 3 | var i = "i"; 4 | fun g() { print i; } 5 | return g; 6 | } 7 | } 8 | 9 | var h = f(); 10 | h(); // out: i 11 | -------------------------------------------------------------------------------- /res/examples/variable/scope_reuse_in_different_blocks.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = "first"; 3 | print a; // out: first 4 | } 5 | 6 | { 7 | var a = "second"; 8 | print a; // out: second 9 | } 10 | -------------------------------------------------------------------------------- /res/examples/closure/closed_closure_in_function.lox: -------------------------------------------------------------------------------- 1 | var f; 2 | 3 | { 4 | var local = "local"; 5 | fun f_() { 6 | print local; 7 | } 8 | f = f_; 9 | } 10 | 11 | f(); // out: local 12 | -------------------------------------------------------------------------------- /res/examples/variable/early_bound.lox: -------------------------------------------------------------------------------- 1 | var a = "outer"; 2 | { 3 | fun foo() { 4 | print a; 5 | } 6 | 7 | foo(); // out: outer 8 | var a = "inner"; 9 | foo(); // out: outer 10 | } 11 | -------------------------------------------------------------------------------- /res/examples/while/return_closure.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | while (true) { 3 | var i = "i"; 4 | fun g() { print i; } 5 | return g; 6 | } 7 | } 8 | 9 | var h = f(); 10 | h(); // out: i 11 | -------------------------------------------------------------------------------- /res/examples/if/dangling_else.lox: -------------------------------------------------------------------------------- 1 | // A dangling else binds to the right-most if. 2 | if (true) if (false) print "bad"; else print "good"; // out: good 3 | if (false) if (true) print "bad"; else print "bad"; 4 | -------------------------------------------------------------------------------- /res/examples/super/no_superclass_bind.lox: -------------------------------------------------------------------------------- 1 | class Base { 2 | foo() { 3 | // out: SyntaxError: "super" used in class without a superclass 4 | super.doesNotExist; 5 | } 6 | } 7 | 8 | Base().foo(); 9 | -------------------------------------------------------------------------------- /res/examples/super/no_superclass_call.lox: -------------------------------------------------------------------------------- 1 | class Base { 2 | foo() { 3 | // out: SyntaxError: "super" used in class without a superclass 4 | super.doesNotExist(1); 5 | } 6 | } 7 | 8 | Base().foo(); 9 | -------------------------------------------------------------------------------- /res/examples/super/parenthesized.lox: -------------------------------------------------------------------------------- 1 | class A { 2 | method() {} 3 | } 4 | 5 | class B < A { 6 | method() { 7 | // out: SyntaxError: unexpected ")" 8 | (super).method(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /res/examples/closure/close_over_function_parameter.lox: -------------------------------------------------------------------------------- 1 | var f; 2 | 3 | fun foo(param) { 4 | fun f_() { 5 | print param; 6 | } 7 | f = f_; 8 | } 9 | foo("param"); 10 | 11 | f(); // out: param 12 | -------------------------------------------------------------------------------- /res/examples/field/method.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | bar(arg) { 3 | print arg; 4 | } 5 | } 6 | 7 | var bar = Foo().bar; 8 | print "got method"; // out: got method 9 | bar("arg"); // out: arg 10 | -------------------------------------------------------------------------------- /res/examples/string/error_after_multiline.lox: -------------------------------------------------------------------------------- 1 | // Tests that we correctly track the line info across multiline strings. 2 | var a = "1 3 | 2 4 | 3 5 | "; 6 | 7 | err; // out: NameError: name "err" is not defined 8 | -------------------------------------------------------------------------------- /playground/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /src/syntax/parser.rs: -------------------------------------------------------------------------------- 1 | use lalrpop_util::lalrpop_mod; 2 | 3 | pub type Parser = grammar::ProgramParser; 4 | 5 | lalrpop_mod!( 6 | #[allow(clippy::all)] 7 | grammar, 8 | "/res/grammar.rs" 9 | ); 10 | -------------------------------------------------------------------------------- /res/examples/assignment/associativity.lox: -------------------------------------------------------------------------------- 1 | var a = "a"; 2 | var b = "b"; 3 | var c = "c"; 4 | 5 | // Assignment is right-associative. 6 | a = b = c; 7 | print a; // out: c 8 | print b; // out: c 9 | print c; // out: c 10 | -------------------------------------------------------------------------------- /res/examples/assignment/local.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = "before"; 3 | print a; // out: before 4 | 5 | a = "after"; 6 | print a; // out: after 7 | 8 | print a = "arg"; // out: arg 9 | print a; // out: arg 10 | } 11 | -------------------------------------------------------------------------------- /res/examples/constructor/early_return.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | init() { 3 | print "init"; 4 | return; 5 | print "nope"; 6 | } 7 | } 8 | 9 | var foo = Foo(); // out: init 10 | print foo; // out: 11 | -------------------------------------------------------------------------------- /res/examples/constructor/extra_arguments.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | init(a, b) { 3 | this.a = a; 4 | this.b = b; 5 | } 6 | } 7 | 8 | var foo = Foo(1, 2, 3, 4); // out: TypeError: init() takes 2 arguments but 4 were given 9 | -------------------------------------------------------------------------------- /res/examples/number/literals.lox: -------------------------------------------------------------------------------- 1 | print 123; // out: 123 2 | print 987654; // out: 987654 3 | print 0; // out: 0 4 | print -0; // out: -0 5 | 6 | print 123.456; // out: 123.456 7 | print -0.001; // out: -0.001 8 | -------------------------------------------------------------------------------- /res/examples/closure/reference_closure_multiple_times.lox: -------------------------------------------------------------------------------- 1 | var f; 2 | 3 | { 4 | var a = "a"; 5 | fun f_() { 6 | print a; 7 | print a; 8 | } 9 | f = f_; 10 | } 11 | 12 | f(); 13 | // out: a 14 | // out: a 15 | -------------------------------------------------------------------------------- /res/examples/method/extra_arguments.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | method(a, b) { 3 | print a; 4 | print b; 5 | } 6 | } 7 | 8 | // out: TypeError: method() takes 2 arguments but 4 were given 9 | Foo().method(1, 2, 3, 4); 10 | -------------------------------------------------------------------------------- /res/examples/constructor/return_in_nested_function.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | init() { 3 | fun init() { 4 | return "bar"; 5 | } 6 | print init(); // out: bar 7 | } 8 | } 9 | 10 | print Foo(); // out: 11 | -------------------------------------------------------------------------------- /res/examples/number/nan_equality.lox: -------------------------------------------------------------------------------- 1 | var nan = 0/0; 2 | 3 | print nan == 0; // out: false 4 | print nan != 1; // out: true 5 | 6 | // NaN is not equal to self. 7 | // print nan == nan; // expected: false 8 | // print nan != nan; // expected: true 9 | -------------------------------------------------------------------------------- /res/examples/closure/close_over_method_parameter.lox: -------------------------------------------------------------------------------- 1 | var f; 2 | 3 | class Foo { 4 | method(param) { 5 | fun f_() { 6 | print param; 7 | } 8 | f = f_; 9 | } 10 | } 11 | 12 | Foo().method("param"); 13 | f(); // out: param 14 | -------------------------------------------------------------------------------- /res/examples/constructor/arguments.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | init(a, b) { 3 | print "init"; // out: init 4 | this.a = a; 5 | this.b = b; 6 | } 7 | } 8 | 9 | var foo = Foo(1, 2); 10 | print foo.a; // out: 1 11 | print foo.b; // out: 2 12 | -------------------------------------------------------------------------------- /res/examples/closure/assign_to_shadowed_later.lox: -------------------------------------------------------------------------------- 1 | var a = "global"; 2 | 3 | { 4 | fun assign() { 5 | a = "assigned"; 6 | } 7 | 8 | var a = "inner"; 9 | assign(); 10 | print a; // out: inner 11 | } 12 | 13 | print a; // out: assigned 14 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | group_imports = "StdExternalCrate" 3 | imports_granularity = "Module" 4 | newline_style = "Native" 5 | use_field_init_shorthand = true 6 | use_small_heuristics = "Max" 7 | use_try_shorthand = true 8 | style_edition = "2024" 9 | -------------------------------------------------------------------------------- /res/examples/constructor/call_init_early_return.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | init() { 3 | print "init"; 4 | return; 5 | print "nope"; 6 | } 7 | } 8 | 9 | var foo = Foo(); // out: init 10 | print foo.init(); // out: init 11 | // out: 12 | -------------------------------------------------------------------------------- /res/examples/field/call_function_field.lox: -------------------------------------------------------------------------------- 1 | class Foo {} 2 | 3 | fun bar(a, b) { 4 | print "bar"; 5 | print a; 6 | print b; 7 | } 8 | 9 | var foo = Foo(); 10 | foo.bar = bar; 11 | 12 | foo.bar(1, 2); 13 | // out: bar 14 | // out: 1 15 | // out: 2 16 | -------------------------------------------------------------------------------- /res/examples/inheritance/constructor.lox: -------------------------------------------------------------------------------- 1 | class A { 2 | init(param) { 3 | this.field = param; 4 | } 5 | 6 | test() { 7 | print this.field; 8 | } 9 | } 10 | 11 | class B < A {} 12 | 13 | var b = B("value"); 14 | b.test(); // out: value 15 | -------------------------------------------------------------------------------- /res/examples/super/no_superclass_method.lox: -------------------------------------------------------------------------------- 1 | class Base {} 2 | 3 | class Derived < Base { 4 | foo() { 5 | // out: AttributeError: "Base" object has no attribute "doesNotExist" 6 | super.doesNotExist(1); 7 | } 8 | } 9 | 10 | Derived().foo(); 11 | -------------------------------------------------------------------------------- /res/examples/variable/in_middle_of_block.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = "a"; 3 | print a; // out: a 4 | var b = a + " b"; 5 | print b; // out: a b 6 | var c = a + " c"; 7 | print c; // out: a c 8 | var d = b + " d"; 9 | print d; // out: a b d 10 | } 11 | -------------------------------------------------------------------------------- /res/examples/constructor/init_not_method.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | init(arg) { 3 | print "Foo.init(" + arg + ")"; 4 | this.field = "init"; 5 | } 6 | } 7 | 8 | fun init() { 9 | print "not initializer"; 10 | } 11 | 12 | init(); // out: not initializer 13 | -------------------------------------------------------------------------------- /res/examples/field/on_instance.lox: -------------------------------------------------------------------------------- 1 | class Foo {} 2 | 3 | var foo = Foo(); 4 | 5 | print foo.bar = "bar value"; // out: bar value 6 | print foo.baz = "baz value"; // out: baz value 7 | 8 | print foo.bar; // out: bar value 9 | print foo.baz; // out: baz value 10 | -------------------------------------------------------------------------------- /res/examples/comments/unicode.lox: -------------------------------------------------------------------------------- 1 | // Unicode characters are allowed in comments. 2 | // 3 | // Latin 1 Supplement: £§¶ÜÞ 4 | // Latin Extended-A: ĐĦŋœ 5 | // Latin Extended-B: ƂƢƩǁ 6 | // Other stuff: ឃᢆ᯽₪ℜ↩⊗┺░ 7 | // Emoji: ☃☺♣ 8 | 9 | print "ok"; // out: ok 10 | -------------------------------------------------------------------------------- /res/examples/logical_operator/or_truth.lox: -------------------------------------------------------------------------------- 1 | // False and nil are false. 2 | print false or "ok"; // out: ok 3 | print nil or "ok"; // out: ok 4 | 5 | // Everything else is true. 6 | print true or "ok"; // out: true 7 | print 0 or "ok"; // out: 0 8 | print "s" or "ok"; // out: s 9 | -------------------------------------------------------------------------------- /res/examples/logical_operator/and_truth.lox: -------------------------------------------------------------------------------- 1 | // False and nil are false. 2 | print false and "bad"; // out: false 3 | print nil and "bad"; // out: nil 4 | 5 | // Everything else is true. 6 | print true and "ok"; // out: ok 7 | print 0 and "ok"; // out: ok 8 | print "" and "ok"; // out: ok 9 | -------------------------------------------------------------------------------- /res/examples/closure/shadow_closure_with_local.lox: -------------------------------------------------------------------------------- 1 | { 2 | var foo = "closure"; 3 | fun f() { 4 | { 5 | print foo; // out: closure 6 | var foo = "shadow"; 7 | print foo; // out: shadow 8 | } 9 | print foo; // out: closure 10 | } 11 | f(); 12 | } 13 | -------------------------------------------------------------------------------- /res/examples/if/else.lox: -------------------------------------------------------------------------------- 1 | // Evaluate the 'else' expression if the condition is false. 2 | if (true) print "good"; else print "bad"; // out: good 3 | if (false) print "bad"; else print "good"; // out: good 4 | 5 | // Allow block body. 6 | if (false) nil; else { print "block"; } // out: block 7 | -------------------------------------------------------------------------------- /res/examples/function/mutual_recursion.lox: -------------------------------------------------------------------------------- 1 | fun isEven(n) { 2 | if (n == 0) return true; 3 | return isOdd(n - 1); 4 | } 5 | 6 | fun isOdd(n) { 7 | if (n == 0) return false; 8 | return isEven(n - 1); 9 | } 10 | 11 | print isEven(4); // out: true 12 | print isOdd(3); // out: true 13 | -------------------------------------------------------------------------------- /res/examples/this/closure.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | getClosure() { 3 | fun closure() { 4 | return this.toString(); 5 | } 6 | return closure; 7 | } 8 | 9 | toString() { return "Foo"; } 10 | } 11 | 12 | var closure = Foo().getClosure(); 13 | print closure(); // out: Foo 14 | -------------------------------------------------------------------------------- /res/examples/super/indirectly_inherited.lox: -------------------------------------------------------------------------------- 1 | class A { 2 | foo() { 3 | print "A.foo()"; 4 | } 5 | } 6 | 7 | class B < A {} 8 | 9 | class C < B { 10 | foo() { 11 | print "C.foo()"; 12 | super.foo(); 13 | } 14 | } 15 | 16 | C().foo(); 17 | // out: C.foo() 18 | // out: A.foo() 19 | -------------------------------------------------------------------------------- /res/examples/super/call_other_method.lox: -------------------------------------------------------------------------------- 1 | class Base { 2 | foo() { 3 | print "Base.foo()"; 4 | } 5 | } 6 | 7 | class Derived < Base { 8 | bar() { 9 | print "Derived.bar()"; 10 | super.foo(); 11 | } 12 | } 13 | 14 | Derived().bar(); 15 | // out: Derived.bar() 16 | // out: Base.foo() 17 | -------------------------------------------------------------------------------- /res/examples/super/call_same_method.lox: -------------------------------------------------------------------------------- 1 | class Base { 2 | foo() { 3 | print "Base.foo()"; 4 | } 5 | } 6 | 7 | class Derived < Base { 8 | foo() { 9 | print "Derived.foo()"; 10 | super.foo(); 11 | } 12 | } 13 | 14 | Derived().foo(); 15 | // out: Derived.foo() 16 | // out: Base.foo() 17 | -------------------------------------------------------------------------------- /res/examples/if/truth.lox: -------------------------------------------------------------------------------- 1 | // False and nil are false. 2 | if (false) print "bad"; else print "false"; // out: false 3 | if (nil) print "bad"; else print "nil"; // out: nil 4 | 5 | // Everything else is true. 6 | if (true) print true; // out: true 7 | if (0) print 0; // out: 0 8 | if ("") print "empty"; // out: empty 9 | -------------------------------------------------------------------------------- /res/examples/super/missing_arguments.lox: -------------------------------------------------------------------------------- 1 | class Base { 2 | foo(a, b) { 3 | print "Base.foo(" + a + ", " + b + ")"; 4 | } 5 | } 6 | 7 | class Derived < Base { 8 | foo() { 9 | super.foo(1); // out: TypeError: foo() takes 2 arguments but 1 were given 10 | } 11 | } 12 | 13 | Derived().foo(); 14 | -------------------------------------------------------------------------------- /res/examples/function/nested_call_with_arguments.lox: -------------------------------------------------------------------------------- 1 | fun returnArg(arg) { 2 | return arg; 3 | } 4 | 5 | fun returnFunCallWithArg(func, arg) { 6 | return returnArg(func)(arg); 7 | } 8 | 9 | fun printArg(arg) { 10 | print arg; 11 | } 12 | 13 | returnFunCallWithArg(printArg, "hello world"); // out: hello world 14 | -------------------------------------------------------------------------------- /res/examples/function/local_mutual_recursion.lox: -------------------------------------------------------------------------------- 1 | { 2 | fun isEven(n) { 3 | if (n == 0) return true; 4 | // out: NameError: name "isOdd" is not defined 5 | return isOdd(n - 1); 6 | } 7 | 8 | fun isOdd(n) { 9 | if (n == 0) return false; 10 | return isEven(n - 1); 11 | } 12 | 13 | isEven(4); 14 | } 15 | -------------------------------------------------------------------------------- /res/examples/if/if.lox: -------------------------------------------------------------------------------- 1 | // Evaluate the 'then' expression if the condition is true. 2 | if (true) print "good"; // out: good 3 | if (false) print "bad"; 4 | 5 | // Allow block body. 6 | if (true) { print "block"; } // out: block 7 | 8 | // Assignment in if condition. 9 | var a = false; 10 | if (a = true) print a; // out: true 11 | -------------------------------------------------------------------------------- /res/examples/super/constructor.lox: -------------------------------------------------------------------------------- 1 | class Base { 2 | init(a, b) { 3 | print "Base.init(" + a + ", " + b + ")"; 4 | } 5 | } 6 | 7 | class Derived < Base { 8 | init() { 9 | print "Derived.init()"; 10 | super.init("a", "b"); 11 | } 12 | } 13 | 14 | Derived(); 15 | // out: Derived.init() 16 | // out: Base.init(a, b) 17 | -------------------------------------------------------------------------------- /res/examples/super/this_in_superclass_method.lox: -------------------------------------------------------------------------------- 1 | class Base { 2 | init(a) { 3 | this.a = a; 4 | } 5 | } 6 | 7 | class Derived < Base { 8 | init(a, b) { 9 | super.init(a); 10 | this.b = b; 11 | } 12 | } 13 | 14 | var derived = Derived("a", "b"); 15 | print derived.a; // out: a 16 | print derived.b; // out: b 17 | -------------------------------------------------------------------------------- /res/examples/super/super_in_inherited_method.lox: -------------------------------------------------------------------------------- 1 | class A { 2 | say() { 3 | print "A"; 4 | } 5 | } 6 | 7 | class B < A { 8 | test() { 9 | super.say(); 10 | } 11 | 12 | say() { 13 | print "B"; 14 | } 15 | } 16 | 17 | class C < B { 18 | say() { 19 | print "C"; 20 | } 21 | } 22 | 23 | C().test(); // out: A 24 | -------------------------------------------------------------------------------- /res/examples/while/closure_in_body.lox: -------------------------------------------------------------------------------- 1 | var f1; 2 | var f2; 3 | var f3; 4 | 5 | var i = 1; 6 | while (i < 4) { 7 | var j = i; 8 | fun f() { print j; } 9 | 10 | if (j == 1) f1 = f; 11 | else if (j == 2) f2 = f; 12 | else f3 = f; 13 | 14 | i = i + 1; 15 | } 16 | 17 | f1(); // out: 1 18 | f2(); // out: 2 19 | f3(); // out: 3 20 | -------------------------------------------------------------------------------- /res/examples/operator/not.lox: -------------------------------------------------------------------------------- 1 | print !true; // out: false 2 | print !false; // out: true 3 | print !!true; // out: true 4 | 5 | print !123; // out: false 6 | print !0; // out: false 7 | 8 | print !nil; // out: true 9 | 10 | print !""; // out: false 11 | 12 | fun foo() {} 13 | print !foo; // out: false 14 | -------------------------------------------------------------------------------- /res/examples/closure/reuse_closure_slot.lox: -------------------------------------------------------------------------------- 1 | { 2 | var f; 3 | 4 | { 5 | var a = "a"; 6 | fun f_() { print a; } 7 | f = f_; 8 | } 9 | 10 | { 11 | // Since a is out of scope, the local slot will be reused by b. Make sure 12 | // that f still closes over a. 13 | var b = "b"; 14 | f(); // out: a 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /res/examples/operator/equals_method.lox: -------------------------------------------------------------------------------- 1 | // Bound methods have identity equality. 2 | class Foo { 3 | method() {} 4 | } 5 | 6 | var foo = Foo(); 7 | var fooMethod = foo.method; 8 | 9 | // Same bound method. 10 | print fooMethod == fooMethod; // out: true 11 | 12 | // Different closurizations. 13 | print foo.method == foo.method; // out: false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Custom ### 2 | .vscode/ 3 | flamegraph.svg 4 | profile.pb 5 | 6 | ### Rust ### 7 | # Generated by Cargo 8 | # will have compiled files and executables 9 | debug/ 10 | target/ 11 | 12 | # These are backup files generated by rustfmt 13 | **/*.rs.bk 14 | 15 | # MSVC Windows builds of rustc generate these, which store debugging information 16 | *.pdb 17 | -------------------------------------------------------------------------------- /res/examples/limit/stack_overflow.lox: -------------------------------------------------------------------------------- 1 | fun foo() { 2 | var a1; 3 | var a2; 4 | var a3; 5 | var a4; 6 | var a5; 7 | var a6; 8 | var a7; 9 | var a8; 10 | var a9; 11 | var a10; 12 | var a11; 13 | var a12; 14 | var a13; 15 | var a14; 16 | var a15; 17 | var a16; 18 | foo(); // out: OverflowError: stack overflow 19 | } 20 | 21 | foo(); 22 | -------------------------------------------------------------------------------- /res/examples/inheritance/inherit_methods.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | methodOnFoo() { print "foo"; } 3 | override() { print "foo"; } 4 | } 5 | 6 | class Bar < Foo { 7 | methodOnBar() { print "bar"; } 8 | override() { print "bar"; } 9 | } 10 | 11 | var bar = Bar(); 12 | bar.methodOnFoo(); // out: foo 13 | bar.methodOnBar(); // out: bar 14 | bar.override(); // out: bar 15 | -------------------------------------------------------------------------------- /res/examples/super/extra_arguments.lox: -------------------------------------------------------------------------------- 1 | class Base { 2 | foo(a, b) { 3 | print "Base.foo(" + a + ", " + b + ")"; 4 | } 5 | } 6 | 7 | class Derived < Base { 8 | foo() { 9 | print "Derived.foo()"; // out: Derived.foo() 10 | super.foo("a", "b", "c", "d"); // out: TypeError: foo() takes 2 arguments but 4 were given 11 | } 12 | } 13 | 14 | Derived().foo(); 15 | -------------------------------------------------------------------------------- /res/examples/super/bound_method.lox: -------------------------------------------------------------------------------- 1 | class A { 2 | method(arg) { 3 | print "A.method(" + arg + ")"; 4 | } 5 | } 6 | 7 | class B < A { 8 | getClosure() { 9 | return super.method; 10 | } 11 | 12 | method(arg) { 13 | print "B.method(" + arg + ")"; 14 | } 15 | } 16 | 17 | 18 | var closure = B().getClosure(); 19 | closure("arg"); // out: A.method(arg) 20 | -------------------------------------------------------------------------------- /res/examples/super/closure.lox: -------------------------------------------------------------------------------- 1 | class Base { 2 | toString() { return "Base"; } 3 | } 4 | 5 | class Derived < Base { 6 | getClosure() { 7 | fun closure() { 8 | return super.toString(); 9 | } 10 | return closure; 11 | } 12 | 13 | toString() { return "Derived"; } 14 | } 15 | 16 | var closure = Derived().getClosure(); 17 | print closure(); // out: Base 18 | -------------------------------------------------------------------------------- /res/examples/class/inherited_method.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | inFoo() { 3 | print "in foo"; 4 | } 5 | } 6 | 7 | class Bar < Foo { 8 | inBar() { 9 | print "in bar"; 10 | } 11 | } 12 | 13 | class Baz < Bar { 14 | inBaz() { 15 | print "in baz"; 16 | } 17 | } 18 | 19 | var baz = Baz(); 20 | baz.inFoo(); // out: in foo 21 | baz.inBar(); // out: in bar 22 | baz.inBaz(); // out: in baz 23 | -------------------------------------------------------------------------------- /res/examples/this/nested_closure.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | getClosure() { 3 | fun f() { 4 | fun g() { 5 | fun h() { 6 | return this.toString(); 7 | } 8 | return h; 9 | } 10 | return g; 11 | } 12 | return f; 13 | } 14 | 15 | toString() { return "Foo"; } 16 | } 17 | 18 | var closure = Foo().getClosure(); 19 | print closure()()(); // out: Foo 20 | -------------------------------------------------------------------------------- /res/examples/closure/nested_closure.lox: -------------------------------------------------------------------------------- 1 | var f; 2 | 3 | fun f1() { 4 | var a = "a"; 5 | fun f2() { 6 | var b = "b"; 7 | fun f3() { 8 | var c = "c"; 9 | fun f4() { 10 | print a; 11 | print b; 12 | print c; 13 | } 14 | f = f4; 15 | } 16 | f3(); 17 | } 18 | f2(); 19 | } 20 | f1(); 21 | 22 | f(); 23 | // out: a 24 | // out: b 25 | // out: c 26 | -------------------------------------------------------------------------------- /res/examples/field/method_binds_this.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | sayName(a) { 3 | print this.name; 4 | print a; 5 | } 6 | } 7 | 8 | var foo1 = Foo(); 9 | foo1.name = "foo1"; 10 | 11 | var foo2 = Foo(); 12 | foo2.name = "foo2"; 13 | 14 | // Store the method reference on another object. 15 | foo2.fn = foo1.sayName; 16 | // Still retains original receiver. 17 | foo2.fn(1); 18 | // out: foo1 19 | // out: 1 20 | -------------------------------------------------------------------------------- /res/examples/for/closure_in_body.lox: -------------------------------------------------------------------------------- 1 | var f1; 2 | var f2; 3 | var f3; 4 | 5 | for (var i = 1; i < 4; i = i + 1) { 6 | var j = i; 7 | fun f() { 8 | print i; 9 | print j; 10 | } 11 | 12 | if (j == 1) f1 = f; 13 | else if (j == 2) f2 = f; 14 | else f3 = f; 15 | } 16 | 17 | f1(); // out: 4 18 | // out: 1 19 | f2(); // out: 4 20 | // out: 2 21 | f3(); // out: 4 22 | // out: 3 23 | -------------------------------------------------------------------------------- /res/examples/operator/equals.lox: -------------------------------------------------------------------------------- 1 | print nil == nil; // out: true 2 | 3 | print true == true; // out: true 4 | print true == false; // out: false 5 | 6 | print 1 == 1; // out: true 7 | print 1 == 2; // out: false 8 | 9 | print "str" == "str"; // out: true 10 | print "str" == "ing"; // out: false 11 | 12 | print nil == false; // out: false 13 | print false == 0; // out: false 14 | print 0 == "0"; // out: false 15 | -------------------------------------------------------------------------------- /res/examples/this/nested_class.lox: -------------------------------------------------------------------------------- 1 | class Outer { 2 | method() { 3 | print this; // out: 4 | 5 | fun f() { 6 | print this; // out: 7 | 8 | class Inner { 9 | method() { 10 | print this; // out: 11 | } 12 | } 13 | 14 | Inner().method(); 15 | } 16 | f(); 17 | } 18 | } 19 | 20 | Outer().method(); 21 | -------------------------------------------------------------------------------- /res/examples/operator/not_equals.lox: -------------------------------------------------------------------------------- 1 | print nil != nil; // out: false 2 | 3 | print true != true; // out: false 4 | print true != false; // out: true 5 | 6 | print 1 != 1; // out: false 7 | print 1 != 2; // out: true 8 | 9 | print "str" != "str"; // out: false 10 | print "str" != "ing"; // out: true 11 | 12 | print nil != false; // out: true 13 | print false != 0; // out: true 14 | print 0 != "0"; // out: true 15 | -------------------------------------------------------------------------------- /res/examples/operator/equals_class.lox: -------------------------------------------------------------------------------- 1 | // Bound methods have identity equality. 2 | class Foo {} 3 | class Bar {} 4 | 5 | print Foo == Foo; // out: true 6 | print Foo == Bar; // out: false 7 | print Bar == Foo; // out: false 8 | print Bar == Bar; // out: true 9 | 10 | print Foo == "Foo"; // out: false 11 | print Foo == nil; // out: false 12 | print Foo == 123; // out: false 13 | print Foo == true; // out: false 14 | -------------------------------------------------------------------------------- /res/examples/closure/unused_closure.lox: -------------------------------------------------------------------------------- 1 | // This is a regression test. There was a bug where the VM would try to close 2 | // an upvalue even if the upvalue was never created because the codepath for 3 | // the closure was not executed. 4 | 5 | { 6 | var a = "a"; 7 | if (false) { 8 | fun foo() { a; } 9 | } 10 | } 11 | 12 | // If we get here, we didn't segfault when a went out of scope. 13 | print "ok"; // out: ok 14 | -------------------------------------------------------------------------------- /res/examples/super/super_in_closure_in_inherited_method.lox: -------------------------------------------------------------------------------- 1 | class A { 2 | say() { 3 | print "A"; 4 | } 5 | } 6 | 7 | class B < A { 8 | getClosure() { 9 | fun closure() { 10 | super.say(); 11 | } 12 | return closure; 13 | } 14 | 15 | say() { 16 | print "B"; 17 | } 18 | } 19 | 20 | class C < B { 21 | say() { 22 | print "C"; 23 | } 24 | } 25 | 26 | C().getClosure()(); // out: A 27 | -------------------------------------------------------------------------------- /res/examples/constructor/call_init_explicitly.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | init(arg) { 3 | print "Foo.init(" + arg + ")"; 4 | this.field = "init"; 5 | } 6 | } 7 | 8 | var foo = Foo("one"); // out: Foo.init(one) 9 | foo.field = "field"; 10 | 11 | var foo2 = foo.init("two"); // out: Foo.init(two) 12 | print foo2; // out: 13 | 14 | // Make sure init() doesn't create a fresh instance. 15 | print foo.field; // out: init 16 | -------------------------------------------------------------------------------- /res/examples/closure/assign_to_closure.lox: -------------------------------------------------------------------------------- 1 | var f; 2 | var g; 3 | 4 | { 5 | var local = "local"; 6 | fun f_() { 7 | print local; 8 | local = "after f"; 9 | print local; 10 | } 11 | f = f_; 12 | 13 | fun g_() { 14 | print local; 15 | local = "after g"; 16 | print local; 17 | } 18 | g = g_; 19 | } 20 | 21 | f(); 22 | // out: local 23 | // out: after f 24 | 25 | g(); 26 | // out: after f 27 | // out: after g 28 | -------------------------------------------------------------------------------- /res/examples/super/reassign_superclass.lox: -------------------------------------------------------------------------------- 1 | class Base { 2 | method() { 3 | print "Base.method()"; 4 | } 5 | } 6 | 7 | class Derived < Base { 8 | method() { 9 | super.method(); 10 | } 11 | } 12 | 13 | class OtherBase { 14 | method() { 15 | print "OtherBase.method()"; 16 | } 17 | } 18 | 19 | var derived = Derived(); 20 | derived.method(); // out: Base.method() 21 | Base = OtherBase; 22 | derived.method(); // out: Base.method() 23 | -------------------------------------------------------------------------------- /res/examples/while/syntax.lox: -------------------------------------------------------------------------------- 1 | // Single-expression body. 2 | // 3 | // out: 1 4 | // out: 2 5 | // out: 3 6 | // 7 | var c = 0; 8 | while (c < 3) print c = c + 1; 9 | 10 | // Block body. 11 | // 12 | // out: 0 13 | // out: 1 14 | // out: 2 15 | // 16 | var a = 0; 17 | while (a < 3) { 18 | print a; 19 | a = a + 1; 20 | } 21 | 22 | // Statement bodies. 23 | while (false) if (true) 1; else 2; 24 | while (false) while (true) 1; 25 | while (false) for (;;) 1; 26 | -------------------------------------------------------------------------------- /res/examples/regression/40.lox: -------------------------------------------------------------------------------- 1 | fun caller(g) { 2 | g(); 3 | // g should be a function, not nil. 4 | print g == nil; // out: false 5 | } 6 | 7 | fun callCaller() { 8 | var capturedVar = "before"; 9 | var a = "a"; 10 | 11 | fun f() { 12 | // Commenting the next line out prevents the bug! 13 | capturedVar = "after"; 14 | 15 | // Returning anything also fixes it, even nil: 16 | //return nil; 17 | } 18 | 19 | caller(f); 20 | } 21 | 22 | callCaller(); 23 | -------------------------------------------------------------------------------- /res/examples/closure/close_over_later_variable.lox: -------------------------------------------------------------------------------- 1 | // This is a regression test. There was a bug where if an upvalue for an 2 | // earlier local (here "a") was captured *after* a later one ("b"), then it 3 | // would crash because it walked to the end of the upvalue list (correct), but 4 | // then didn't handle not finding the variable. 5 | 6 | fun f() { 7 | var a = "a"; 8 | var b = "b"; 9 | fun g() { 10 | print b; // out: b 11 | print a; // out: a 12 | } 13 | g(); 14 | } 15 | f(); 16 | -------------------------------------------------------------------------------- /playground/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "globals.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /res/examples/field/get_and_set_method.lox: -------------------------------------------------------------------------------- 1 | // Bound methods have identity equality. 2 | class Foo { 3 | method(a) { 4 | print "method"; 5 | print a; 6 | } 7 | other(a) { 8 | print "other"; 9 | print a; 10 | } 11 | } 12 | 13 | var foo = Foo(); 14 | var method = foo.method; 15 | 16 | // Setting a property shadows the instance method. 17 | foo.method = foo.other; 18 | foo.method(1); 19 | // out: other 20 | // out: 1 21 | 22 | // The old method handle still points to the original method. 23 | method(2); 24 | // out: method 25 | // out: 2 26 | -------------------------------------------------------------------------------- /playground/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/* 15 | !/out/.gitkeep 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /res/examples/logical_operator/and.lox: -------------------------------------------------------------------------------- 1 | // Note: These tests implicitly depend on ints being truthy. 2 | 3 | // Return the first non-true argument. 4 | print false and 1; // out: false 5 | print true and 1; // out: 1 6 | print 1 and 2 and false; // out: false 7 | 8 | // Return the last argument if all are true. 9 | print 1 and true; // out: true 10 | print 1 and 2 and 3; // out: 3 11 | 12 | // Short-circuit at the first false argument. 13 | var a = "before"; 14 | var b = "before"; 15 | (a = true) and 16 | (b = false) and 17 | (a = "bad"); 18 | print a; // out: true 19 | print b; // out: false 20 | -------------------------------------------------------------------------------- /playground/rust/lox-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lox-wasm" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | askama_escape = "0.10.3" 11 | loxcraft = { path = "../../../", default-features = false } 12 | serde = { version = "1.0.160", features = ["derive"] } 13 | serde_json = "1.0.96" 14 | termcolor = "1.2.0" 15 | wasm-bindgen = "0.2.84" 16 | 17 | [package.metadata.wasm-pack.profile.release] 18 | wasm-opt = ['-O4'] 19 | 20 | [profile.release] 21 | codegen-units = 1 22 | debug = false 23 | lto = true 24 | panic = "abort" 25 | strip = true 26 | -------------------------------------------------------------------------------- /res/examples/for/scope.lox: -------------------------------------------------------------------------------- 1 | { 2 | var i = "before"; 3 | 4 | // New variable is in inner scope. 5 | for (var i = 0; i < 1; i = i + 1) { 6 | print i; // out: 0 7 | 8 | // Loop body is in second inner scope. 9 | var i = -1; 10 | print i; // out: -1 11 | } 12 | } 13 | 14 | { 15 | // New variable shadows outer variable. 16 | for (var i = 0; i > 0; i = i + 1) {} 17 | 18 | // Goes out of scope after loop. 19 | var i = "after"; 20 | print i; // out: after 21 | 22 | // Can reuse an existing variable. 23 | for (i = 0; i < 1; i = i + 1) { 24 | print i; // out: 0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /res/examples/logical_operator/or.lox: -------------------------------------------------------------------------------- 1 | // Note: These tests implicitly depend on ints being truthy. 2 | 3 | // Return the first true argument. 4 | print 1 or true; // out: 1 5 | print false or 1; // out: 1 6 | print false or false or true; // out: true 7 | 8 | // Return the last argument if all are false. 9 | print false or false; // out: false 10 | print false or false or false; // out: false 11 | 12 | // Short-circuit at the first true argument. 13 | var a = "before"; 14 | var b = "before"; 15 | (a = false) or 16 | (b = true) or 17 | (a = "bad"); 18 | print a; // out: false 19 | print b; // out: true 20 | -------------------------------------------------------------------------------- /res/benchmarks/instantiation.lox: -------------------------------------------------------------------------------- 1 | // This benchmark stresses instance creation and initializer calling. 2 | 3 | class Foo { 4 | init() {} 5 | } 6 | 7 | var start = clock(); 8 | var i = 0; 9 | while (i < 8000000) { 10 | Foo(); 11 | Foo(); 12 | Foo(); 13 | Foo(); 14 | Foo(); 15 | Foo(); 16 | Foo(); 17 | Foo(); 18 | Foo(); 19 | Foo(); 20 | Foo(); 21 | Foo(); 22 | Foo(); 23 | Foo(); 24 | Foo(); 25 | Foo(); 26 | Foo(); 27 | Foo(); 28 | Foo(); 29 | Foo(); 30 | Foo(); 31 | Foo(); 32 | Foo(); 33 | Foo(); 34 | Foo(); 35 | Foo(); 36 | Foo(); 37 | Foo(); 38 | Foo(); 39 | Foo(); 40 | i = i + 1; 41 | } 42 | 43 | print clock() - start; 44 | -------------------------------------------------------------------------------- /playground/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "next-themes"; 2 | import type { Metadata } from "next"; 3 | import "./globals.css"; 4 | 5 | export const metadata: Metadata = { 6 | title: "Lox Playground", 7 | description: "Run Lox code in your browser.", 8 | metadataBase: new URL("https://ajeetdsouza.github.io/loxcraft/"), 9 | }; 10 | 11 | export default function RootLayout({ 12 | children, 13 | }: { 14 | children: React.ReactNode; 15 | }) { 16 | return ( 17 | 18 | 19 | 20 | {children} 21 | 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /res/examples/inheritance/set_fields_from_base_class.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | foo(a, b) { 3 | this.field1 = a; 4 | this.field2 = b; 5 | } 6 | 7 | fooPrint() { 8 | print this.field1; 9 | print this.field2; 10 | } 11 | } 12 | 13 | class Bar < Foo { 14 | bar(a, b) { 15 | this.field1 = a; 16 | this.field2 = b; 17 | } 18 | 19 | barPrint() { 20 | print this.field1; 21 | print this.field2; 22 | } 23 | } 24 | 25 | var bar = Bar(); 26 | bar.foo("foo 1", "foo 2"); 27 | bar.fooPrint(); 28 | // out: foo 1 29 | // out: foo 2 30 | 31 | bar.bar("bar 1", "bar 2"); 32 | bar.barPrint(); 33 | // out: bar 1 34 | // out: bar 2 35 | 36 | bar.fooPrint(); 37 | // out: bar 1 38 | // out: bar 2 39 | -------------------------------------------------------------------------------- /res/examples/closure/unused_later_closure.lox: -------------------------------------------------------------------------------- 1 | // This is a regression test. When closing upvalues for discarded locals, it 2 | // wouldn't make sure it discarded the upvalue for the correct stack slot. 3 | // 4 | // Here we create two locals that can be closed over, but only the first one 5 | // actually is. When "b" goes out of scope, we need to make sure we don't 6 | // prematurely close "a". 7 | var closure; 8 | 9 | { 10 | var a = "a"; 11 | 12 | { 13 | var b = "b"; 14 | fun returnA() { 15 | return a; 16 | } 17 | 18 | closure = returnA; 19 | 20 | if (false) { 21 | fun returnB() { 22 | return b; 23 | } 24 | } 25 | } 26 | 27 | print closure(); // out: a 28 | } 29 | -------------------------------------------------------------------------------- /res/benchmarks/trees.lox: -------------------------------------------------------------------------------- 1 | class Tree { 2 | init(depth) { 3 | this.depth = depth; 4 | if (depth > 0) { 5 | this.a = Tree(depth - 1); 6 | this.b = Tree(depth - 1); 7 | this.c = Tree(depth - 1); 8 | this.d = Tree(depth - 1); 9 | this.e = Tree(depth - 1); 10 | } 11 | } 12 | 13 | walk() { 14 | if (this.depth == 0) return 0; 15 | return this.depth 16 | + this.a.walk() 17 | + this.b.walk() 18 | + this.c.walk() 19 | + this.d.walk() 20 | + this.e.walk(); 21 | } 22 | } 23 | 24 | var tree = Tree(8); 25 | var start = clock(); 26 | for (var i = 0; i < 300; i = i + 1) { 27 | if (tree.walk() != 122068) print "Error"; 28 | } 29 | print clock() - start; 30 | -------------------------------------------------------------------------------- /res/examples/operator/comparison.lox: -------------------------------------------------------------------------------- 1 | print 1 < 2; // out: true 2 | print 2 < 2; // out: false 3 | print 2 < 1; // out: false 4 | 5 | print 1 <= 2; // out: true 6 | print 2 <= 2; // out: true 7 | print 2 <= 1; // out: false 8 | 9 | print 1 > 2; // out: false 10 | print 2 > 2; // out: false 11 | print 2 > 1; // out: true 12 | 13 | print 1 >= 2; // out: false 14 | print 2 >= 2; // out: true 15 | print 2 >= 1; // out: true 16 | 17 | // Zero and negative zero compare the same. 18 | print 0 < -0; // out: false 19 | print -0 < 0; // out: false 20 | print 0 > -0; // out: false 21 | print -0 > 0; // out: false 22 | print 0 <= -0; // out: true 23 | print -0 <= 0; // out: true 24 | print 0 >= -0; // out: true 25 | print -0 >= 0; // out: true 26 | -------------------------------------------------------------------------------- /src/vm/util.rs: -------------------------------------------------------------------------------- 1 | use std::hint; 2 | 3 | #[cfg(target_family = "wasm")] 4 | use wasm_bindgen::prelude::*; 5 | 6 | #[cfg(target_family = "wasm")] 7 | #[wasm_bindgen] 8 | extern "C" { 9 | #[wasm_bindgen(js_namespace = Date, js_name = now)] 10 | fn date_now() -> f64; 11 | } 12 | 13 | #[cfg(target_family = "wasm")] 14 | pub fn now() -> f64 { 15 | date_now() / 1000.0 16 | } 17 | 18 | #[cfg(not(target_family = "wasm"))] 19 | pub fn now() -> f64 { 20 | std::time::SystemTime::now() 21 | .duration_since(std::time::UNIX_EPOCH) 22 | .unwrap_or_default() 23 | .as_secs_f64() 24 | } 25 | 26 | #[inline(always)] 27 | pub const fn unreachable() -> ! { 28 | if cfg!(debug_assertions) { unreachable!() } else { unsafe { hint::unreachable_unchecked() } } 29 | } 30 | -------------------------------------------------------------------------------- /res/benchmarks/zoo.lox: -------------------------------------------------------------------------------- 1 | class Zoo { 2 | init() { 3 | this.aarvark = 1; 4 | this.baboon = 1; 5 | this.cat = 1; 6 | this.donkey = 1; 7 | this.elephant = 1; 8 | this.fox = 1; 9 | } 10 | ant() { return this.aarvark; } 11 | banana() { return this.baboon; } 12 | tuna() { return this.cat; } 13 | hay() { return this.donkey; } 14 | grass() { return this.elephant; } 15 | mouse() { return this.fox; } 16 | } 17 | 18 | var zoo = Zoo(); 19 | var sum = 0; 20 | var start = clock(); 21 | while (sum < 200000000) { 22 | sum = sum + zoo.ant() 23 | + zoo.banana() 24 | + zoo.tuna() 25 | + zoo.hay() 26 | + zoo.grass() 27 | + zoo.mouse(); 28 | } 29 | 30 | print sum; 31 | print clock() - start; 32 | -------------------------------------------------------------------------------- /res/examples/bool/equality.lox: -------------------------------------------------------------------------------- 1 | print true == true; // out: true 2 | print true == false; // out: false 3 | print false == true; // out: false 4 | print false == false; // out: true 5 | 6 | // Not equal to other types. 7 | print true == 1; // out: false 8 | print false == 0; // out: false 9 | print true == "true"; // out: false 10 | print false == "false"; // out: false 11 | print false == ""; // out: false 12 | 13 | print true != true; // out: false 14 | print true != false; // out: true 15 | print false != true; // out: true 16 | print false != false; // out: false 17 | 18 | // Not equal to other types. 19 | print true != 1; // out: true 20 | print false != 0; // out: true 21 | print true != "true"; // out: true 22 | print false != "false"; // out: true 23 | print false != ""; // out: true 24 | -------------------------------------------------------------------------------- /res/examples/function/parameters.lox: -------------------------------------------------------------------------------- 1 | fun f0() { return 0; } 2 | print f0(); // out: 0 3 | 4 | fun f1(a) { return a; } 5 | print f1(1); // out: 1 6 | 7 | fun f2(a, b) { return a + b; } 8 | print f2(1, 2); // out: 3 9 | 10 | fun f3(a, b, c) { return a + b + c; } 11 | print f3(1, 2, 3); // out: 6 12 | 13 | fun f4(a, b, c, d) { return a + b + c + d; } 14 | print f4(1, 2, 3, 4); // out: 10 15 | 16 | fun f5(a, b, c, d, e) { return a + b + c + d + e; } 17 | print f5(1, 2, 3, 4, 5); // out: 15 18 | 19 | fun f6(a, b, c, d, e, f) { return a + b + c + d + e + f; } 20 | print f6(1, 2, 3, 4, 5, 6); // out: 21 21 | 22 | fun f7(a, b, c, d, e, f, g) { return a + b + c + d + e + f + g; } 23 | print f7(1, 2, 3, 4, 5, 6, 7); // out: 28 24 | 25 | fun f8(a, b, c, d, e, f, g, h) { return a + b + c + d + e + f + g + h; } 26 | print f8(1, 2, 3, 4, 5, 6, 7, 8); // out: 36 27 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "paths": { 17 | "@/*": ["./src/*"] 18 | }, 19 | "incremental": true, 20 | "plugins": [ 21 | { 22 | "name": "next" 23 | } 24 | ] 25 | }, 26 | "include": [ 27 | "next-env.d.ts", 28 | "**/*.ts", 29 | "**/*.tsx", 30 | ".next/types/**/*.ts", 31 | "next.config.js", 32 | "postcss.config.ts" 33 | ], 34 | "exclude": ["node_modules"] 35 | } 36 | -------------------------------------------------------------------------------- /res/examples/precedence.lox: -------------------------------------------------------------------------------- 1 | // * has higher precedence than +. 2 | print 2 + 3 * 4; // out: 14 3 | 4 | // * has higher precedence than -. 5 | print 20 - 3 * 4; // out: 8 6 | 7 | // / has higher precedence than +. 8 | print 2 + 6 / 3; // out: 4 9 | 10 | // / has higher precedence than -. 11 | print 2 - 6 / 3; // out: 0 12 | 13 | // < has higher precedence than ==. 14 | print false == 2 < 1; // out: true 15 | 16 | // > has higher precedence than ==. 17 | print false == 1 > 2; // out: true 18 | 19 | // <= has higher precedence than ==. 20 | print false == 2 <= 1; // out: true 21 | 22 | // >= has higher precedence than ==. 23 | print false == 1 >= 2; // out: true 24 | 25 | // 1 - 1 is not space-sensitive. 26 | print 1 - 1; // out: 0 27 | print 1 -1; // out: 0 28 | print 1- 1; // out: 0 29 | print 1-1; // out: 0 30 | 31 | // Using () for grouping. 32 | print (2 * (6 - (2 + 2))); // out: 4 33 | -------------------------------------------------------------------------------- /res/benchmarks/zoo_batch.lox: -------------------------------------------------------------------------------- 1 | class Zoo { 2 | init() { 3 | this.aarvark = 1; 4 | this.baboon = 1; 5 | this.cat = 1; 6 | this.donkey = 1; 7 | this.elephant = 1; 8 | this.fox = 1; 9 | } 10 | ant() { return this.aarvark; } 11 | banana() { return this.baboon; } 12 | tuna() { return this.cat; } 13 | hay() { return this.donkey; } 14 | grass() { return this.elephant; } 15 | mouse() { return this.fox; } 16 | } 17 | 18 | var zoo = Zoo(); 19 | var sum = 0; 20 | var start = clock(); 21 | var batch = 0; 22 | while (clock() - start < 10) { 23 | for (var i = 0; i < 10000; i = i + 1) { 24 | sum = sum + zoo.ant() 25 | + zoo.banana() 26 | + zoo.tuna() 27 | + zoo.hay() 28 | + zoo.grass() 29 | + zoo.mouse(); 30 | } 31 | batch = batch + 1; 32 | } 33 | 34 | print sum; 35 | print batch; 36 | print clock() - start; 37 | -------------------------------------------------------------------------------- /res/benchmarks/equality.lox: -------------------------------------------------------------------------------- 1 | var i = 0; 2 | 3 | var loopStart = clock(); 4 | 5 | while (i < 50000000) { 6 | i = i + 1; 7 | 8 | 1; 1; 1; 2; 1; nil; 1; "str"; 1; true; 9 | nil; nil; nil; 1; nil; "str"; nil; true; 10 | true; true; true; 1; true; false; true; "str"; true; nil; 11 | "str"; "str"; "str"; "stru"; "str"; 1; "str"; nil; "str"; true; 12 | } 13 | 14 | var loopTime = clock() - loopStart; 15 | 16 | var start = clock(); 17 | 18 | i = 0; 19 | while (i < 50000000) { 20 | i = i + 1; 21 | 22 | 1 == 1; 1 == 2; 1 == nil; 1 == "str"; 1 == true; 23 | nil == nil; nil == 1; nil == "str"; nil == true; 24 | true == true; true == 1; true == false; true == "str"; true == nil; 25 | "str" == "str"; "str" == "stru"; "str" == 1; "str" == nil; "str" == true; 26 | } 27 | 28 | var elapsed = clock() - start; 29 | print "loop"; 30 | print loopTime; 31 | print "elapsed"; 32 | print elapsed; 33 | print "equals"; 34 | print elapsed - loopTime; 35 | -------------------------------------------------------------------------------- /res/examples/method/arity.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | method0() { return "no args"; } 3 | method1(a) { return a; } 4 | method2(a, b) { return a + b; } 5 | method3(a, b, c) { return a + b + c; } 6 | method4(a, b, c, d) { return a + b + c + d; } 7 | method5(a, b, c, d, e) { return a + b + c + d + e; } 8 | method6(a, b, c, d, e, f) { return a + b + c + d + e + f; } 9 | method7(a, b, c, d, e, f, g) { return a + b + c + d + e + f + g; } 10 | method8(a, b, c, d, e, f, g, h) { return a + b + c + d + e + f + g + h; } 11 | } 12 | 13 | var foo = Foo(); 14 | print foo.method0(); // out: no args 15 | print foo.method1(1); // out: 1 16 | print foo.method2(1, 2); // out: 3 17 | print foo.method3(1, 2, 3); // out: 6 18 | print foo.method4(1, 2, 3, 4); // out: 10 19 | print foo.method5(1, 2, 3, 4, 5); // out: 15 20 | print foo.method6(1, 2, 3, 4, 5, 6); // out: 21 21 | print foo.method7(1, 2, 3, 4, 5, 6, 7); // out: 28 22 | print foo.method8(1, 2, 3, 4, 5, 6, 7, 8); // out: 36 23 | -------------------------------------------------------------------------------- /src/playground.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "playground")] 2 | 3 | use std::net::{Ipv4Addr, SocketAddrV4}; 4 | 5 | use anyhow::{Context as _, Result}; 6 | use rust_embed::Embed; 7 | 8 | #[derive(Debug, Embed)] 9 | #[folder = "playground/out/"] 10 | struct Asset; 11 | 12 | pub fn serve(port: u16) -> Result<()> { 13 | let url = format!("http://127.0.0.1:{port}"); 14 | 15 | let ip_address = Ipv4Addr::new(127, 0, 0, 1); 16 | let socket_address = SocketAddrV4::new(ip_address, port); 17 | 18 | let serve = warp_embed::embed(&Asset); 19 | let server = warp::serve(serve).run(socket_address); 20 | 21 | eprintln!("Running playground on {url}"); 22 | if let Err(e) = webbrowser::open(&url) { 23 | eprintln!("Failed to open browser: {e}"); 24 | } 25 | 26 | tokio::runtime::Builder::new_current_thread() 27 | .enable_all() 28 | .build() 29 | .context("failed to start async runtime")? 30 | .block_on(server); 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /res/examples/for/syntax.lox: -------------------------------------------------------------------------------- 1 | // Single-expression body. 2 | // 3 | // out: 1 4 | // out: 2 5 | // out: 3 6 | // 7 | for (var c = 0; c < 3;) print c = c + 1; 8 | 9 | // Block body. 10 | // 11 | // out: 0 12 | // out: 1 13 | // out: 2 14 | // 15 | for (var a = 0; a < 3; a = a + 1) { 16 | print a; 17 | } 18 | 19 | // No clauses. 20 | // 21 | // out: done 22 | // 23 | fun foo() { 24 | for (;;) return "done"; 25 | } 26 | print foo(); 27 | 28 | // No variable. 29 | // 30 | // out: 0 31 | // out: 1 32 | // 33 | var i = 0; 34 | for (; i < 2; i = i + 1) print i; 35 | 36 | // No condition. 37 | // 38 | // out: 0 39 | // out: 1 40 | // out: 2 41 | // 42 | fun bar() { 43 | for (var i = 0;; i = i + 1) { 44 | print i; 45 | if (i >= 2) return; 46 | } 47 | } 48 | bar(); 49 | 50 | 51 | // No increment. 52 | // 53 | // out: 0 54 | // out: 1 55 | // 56 | for (var i = 0; i < 2;) { 57 | print i; 58 | i = i + 1; 59 | } 60 | 61 | 62 | // Statement bodies. 63 | for (; false;) if (true) 1; else 2; 64 | for (; false;) while (true) 1; 65 | for (; false;) for (;;) 1; 66 | -------------------------------------------------------------------------------- /playground/README.md: -------------------------------------------------------------------------------- 1 | # Next.js + Turbopack 2 | 3 | This example allows you to get started with `next dev --turbo` quicky. 4 | 5 | ## Deploy your own 6 | 7 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-turbopack&project-name=with-turbopack&repository-name=with-turbopack) 8 | 9 | ## How to use 10 | 11 | Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init), [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/), or [pnpm](https://pnpm.io) to bootstrap the example: 12 | 13 | ```bash 14 | npx create-next-app --example with-turbopack with-turbopack-app 15 | ``` 16 | 17 | ```bash 18 | yarn create next-app --example with-turbopack with-turbopack-app 19 | ``` 20 | 21 | ```bash 22 | pnpm create next-app --example with-turbopack with-turbopack-app 23 | ``` 24 | 25 | Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ajeet D'Souza, Kartik Sharma 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/lang.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::{fs, str}; 3 | 4 | use loxcraft::vm::VM; 5 | use pretty_assertions::assert_eq; 6 | use test_generator::test_resources; 7 | 8 | #[test_resources("res/examples/**/*.lox")] 9 | fn lox(path: &str) { 10 | // Miri is too slow to run these tests. 11 | const MIRI_SKIP_PATHS: &[&str] = 12 | &["res/examples/limit/loop_too_large.lox", "res/examples/limit/stack_overflow.lox"]; 13 | if cfg!(miri) && MIRI_SKIP_PATHS.contains(&path) { 14 | return; 15 | } 16 | 17 | let source = fs::read_to_string(path).expect("unable to read test file"); 18 | let mut exp_output = String::new(); 19 | for line in source.lines() { 20 | const OUT_COMMENT: &str = "// out: "; 21 | if let Some(idx) = line.find(OUT_COMMENT) { 22 | exp_output += &line[idx + OUT_COMMENT.len()..]; 23 | exp_output += "\n"; 24 | } 25 | } 26 | 27 | let mut got_output = Vec::new(); 28 | if let Err(e) = VM::default().run(&source, &mut got_output) { 29 | let (e, _) = e.first().expect("received empty error"); 30 | writeln!(&mut got_output, "{e}").expect("could not write to output"); 31 | } 32 | let got_output = str::from_utf8(&got_output).expect("invalid UTF-8 in output"); 33 | assert_eq!(exp_output, got_output); 34 | } 35 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "next dev", 5 | "build": "next build", 6 | "build:wasm": "cd rust/lox-wasm && wasm-pack build --release --target=web", 7 | "fmt": "prettier --write .", 8 | "lint": "next lint", 9 | "start": "next start" 10 | }, 11 | "dependencies": { 12 | "@radix-ui/react-scroll-area": "^1.1.0", 13 | "@radix-ui/react-slot": "^1.1.0", 14 | "ace-builds": "^1.36.2", 15 | "class-variance-authority": "^0.7.0", 16 | "clsx": "^2.1.1", 17 | "lox-wasm": "file:rust/lox-wasm/pkg", 18 | "lucide-react": "^0.446.0", 19 | "next": "latest", 20 | "next-themes": "^0.3.0", 21 | "react": "^18.2.0", 22 | "react-ace": "^12.0.0", 23 | "react-dom": "^18.2.0", 24 | "react-resizable-panels": "^2.1.3", 25 | "tailwind-merge": "^2.5.2", 26 | "tailwindcss-animate": "^1.0.7", 27 | "zustand": "^5.0.0-rc.2" 28 | }, 29 | "devDependencies": { 30 | "@types/node": "20.8.10", 31 | "@types/react": "18.2.33", 32 | "@types/react-dom": "18.2.14", 33 | "autoprefixer": "^10.4.20", 34 | "eslint": "8.57.1", 35 | "eslint-config-next": "14.2.13", 36 | "postcss": "^8.4.47", 37 | "prettier": "^3.3.3", 38 | "tailwindcss": "^3.4.13", 39 | "typescript": "^5.2.2", 40 | "wasm-pack": "^0.13.0" 41 | }, 42 | "packageManager": "pnpm@9.7.1+sha512.faf344af2d6ca65c4c5c8c2224ea77a81a5e8859cbc4e06b1511ddce2f0151512431dd19e6aff31f2c6a8f5f2aced9bd2273e1fed7dd4de1868984059d2c4247" 43 | } 44 | -------------------------------------------------------------------------------- /res/examples/limit/too_many_constants.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | 0; 1; 2; 3; 4; 5; 6; 7; 3 | 8; 9; 10; 11; 12; 13; 14; 15; 4 | 16; 17; 18; 19; 20; 21; 22; 23; 5 | 24; 25; 26; 27; 28; 29; 30; 31; 6 | 32; 33; 34; 35; 36; 37; 38; 39; 7 | 40; 41; 42; 43; 44; 45; 46; 47; 8 | 48; 49; 50; 51; 52; 53; 54; 55; 9 | 56; 57; 58; 59; 60; 61; 62; 63; 10 | 64; 65; 66; 67; 68; 69; 70; 71; 11 | 72; 73; 74; 75; 76; 77; 78; 79; 12 | 80; 81; 82; 83; 84; 85; 86; 87; 13 | 88; 89; 90; 91; 92; 93; 94; 95; 14 | 96; 97; 98; 99; 100; 101; 102; 103; 15 | 104; 105; 106; 107; 108; 109; 110; 111; 16 | 112; 113; 114; 115; 116; 117; 118; 119; 17 | 120; 121; 122; 123; 124; 125; 126; 127; 18 | 128; 129; 130; 131; 132; 133; 134; 135; 19 | 136; 137; 138; 139; 140; 141; 142; 143; 20 | 144; 145; 146; 147; 148; 149; 150; 151; 21 | 152; 153; 154; 155; 156; 157; 158; 159; 22 | 160; 161; 162; 163; 164; 165; 166; 167; 23 | 168; 169; 170; 171; 172; 173; 174; 175; 24 | 176; 177; 178; 179; 180; 181; 182; 183; 25 | 184; 185; 186; 187; 188; 189; 190; 191; 26 | 192; 193; 194; 195; 196; 197; 198; 199; 27 | 200; 201; 202; 203; 204; 205; 206; 207; 28 | 208; 209; 210; 211; 212; 213; 214; 215; 29 | 216; 217; 218; 219; 220; 221; 222; 223; 30 | 224; 225; 226; 227; 228; 229; 230; 231; 31 | 232; 233; 234; 235; 236; 237; 238; 239; 32 | 240; 241; 242; 243; 244; 245; 246; 247; 33 | 248; 249; 250; 251; 252; 253; 254; 255; 34 | 35 | // out: OverflowError: cannot define more than 256 constants in a function 36 | "oops"; 37 | } 38 | -------------------------------------------------------------------------------- /res/benchmarks/invocation.lox: -------------------------------------------------------------------------------- 1 | // This benchmark stresses just method invocation. 2 | 3 | class Foo { 4 | method0() {} 5 | method1() {} 6 | method2() {} 7 | method3() {} 8 | method4() {} 9 | method5() {} 10 | method6() {} 11 | method7() {} 12 | method8() {} 13 | method9() {} 14 | method10() {} 15 | method11() {} 16 | method12() {} 17 | method13() {} 18 | method14() {} 19 | method15() {} 20 | method16() {} 21 | method17() {} 22 | method18() {} 23 | method19() {} 24 | method20() {} 25 | method21() {} 26 | method22() {} 27 | method23() {} 28 | method24() {} 29 | method25() {} 30 | method26() {} 31 | method27() {} 32 | method28() {} 33 | method29() {} 34 | } 35 | 36 | var foo = Foo(); 37 | var start = clock(); 38 | var i = 0; 39 | while (i < 9000000) { 40 | foo.method0(); 41 | foo.method1(); 42 | foo.method2(); 43 | foo.method3(); 44 | foo.method4(); 45 | foo.method5(); 46 | foo.method6(); 47 | foo.method7(); 48 | foo.method8(); 49 | foo.method9(); 50 | foo.method10(); 51 | foo.method11(); 52 | foo.method12(); 53 | foo.method13(); 54 | foo.method14(); 55 | foo.method15(); 56 | foo.method16(); 57 | foo.method17(); 58 | foo.method18(); 59 | foo.method19(); 60 | foo.method20(); 61 | foo.method21(); 62 | foo.method22(); 63 | foo.method23(); 64 | foo.method24(); 65 | foo.method25(); 66 | foo.method26(); 67 | foo.method27(); 68 | foo.method28(); 69 | foo.method29(); 70 | i = i + 1; 71 | } 72 | 73 | print clock() - start; 74 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | name: pages 2 | on: 3 | push: 4 | branches: ["main"] 5 | workflow_dispatch: 6 | permissions: 7 | contents: read 8 | pages: write 9 | id-token: write 10 | concurrency: 11 | group: pages 12 | cancel-in-progress: true 13 | jobs: 14 | deploy: 15 | environment: 16 | name: github-pages 17 | url: ${{ steps.deployment.outputs.page_url }} 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | - name: Setup Pages 23 | uses: actions/configure-pages@v2 24 | - name: Install pnpm 25 | uses: pnpm/action-setup@v4 26 | with: 27 | package_json_file: playground/package.json 28 | - name: Install Node 29 | uses: actions/setup-node@v3 30 | with: 31 | cache: pnpm 32 | cache-dependency-path: playground/pnpm-lock.yaml 33 | node-version: 18 34 | - name: Install Rust 35 | uses: dtolnay/rust-toolchain@1.85 36 | - name: Setup Rust cache 37 | uses: Swatinem/rust-cache@v2 38 | with: 39 | cache-directories: ./playground/rust/lox-wasm 40 | - name: Install Task 41 | uses: arduino/setup-task@v2 42 | with: 43 | version: 3.x 44 | - name: Build playground 45 | run: BASE_PATH=/loxcraft task build-playground 46 | - name: Upload artifact 47 | uses: actions/upload-pages-artifact@v3 48 | with: 49 | path: playground/out/ 50 | - name: Deploy to GitHub Pages 51 | id: deployment 52 | uses: actions/deploy-pages@v4 53 | -------------------------------------------------------------------------------- /res/benchmarks/binary_trees.lox: -------------------------------------------------------------------------------- 1 | class Tree { 2 | init(item, depth) { 3 | this.item = item; 4 | this.depth = depth; 5 | if (depth > 0) { 6 | var item2 = item + item; 7 | depth = depth - 1; 8 | this.left = Tree(item2 - 1, depth); 9 | this.right = Tree(item2, depth); 10 | } else { 11 | this.left = nil; 12 | this.right = nil; 13 | } 14 | } 15 | 16 | check() { 17 | if (this.left == nil) { 18 | return this.item; 19 | } 20 | 21 | return this.item + this.left.check() - this.right.check(); 22 | } 23 | } 24 | 25 | var minDepth = 4; 26 | var maxDepth = 16; 27 | var stretchDepth = maxDepth + 1; 28 | 29 | var start = clock(); 30 | 31 | print "stretch tree of depth:"; 32 | print stretchDepth; 33 | print "check:"; 34 | print Tree(0, stretchDepth).check(); 35 | 36 | var longLivedTree = Tree(0, maxDepth); 37 | 38 | // iterations = 2 ** maxDepth 39 | var iterations = 1; 40 | var d = 0; 41 | while (d < maxDepth) { 42 | iterations = iterations * 2; 43 | d = d + 1; 44 | } 45 | 46 | var depth = minDepth; 47 | while (depth < stretchDepth) { 48 | var check = 0; 49 | var i = 1; 50 | while (i <= iterations) { 51 | check = check + Tree(i, depth).check() + Tree(-i, depth).check(); 52 | i = i + 1; 53 | } 54 | 55 | print "num trees:"; 56 | print iterations * 2; 57 | print "depth:"; 58 | print depth; 59 | print "check:"; 60 | print check; 61 | 62 | iterations = iterations / 4; 63 | depth = depth + 2; 64 | } 65 | 66 | print "long lived tree of depth:"; 67 | print maxDepth; 68 | print "check:"; 69 | print longLivedTree.check(); 70 | print "elapsed:"; 71 | print clock() - start; 72 | -------------------------------------------------------------------------------- /res/benchmarks/method_call.lox: -------------------------------------------------------------------------------- 1 | class Toggle { 2 | init(startState) { 3 | this.state = startState; 4 | } 5 | 6 | value() { return this.state; } 7 | 8 | activate() { 9 | this.state = !this.state; 10 | return this; 11 | } 12 | } 13 | 14 | class NthToggle < Toggle { 15 | init(startState, maxCounter) { 16 | super.init(startState); 17 | this.countMax = maxCounter; 18 | this.count = 0; 19 | } 20 | 21 | activate() { 22 | this.count = this.count + 1; 23 | if (this.count >= this.countMax) { 24 | super.activate(); 25 | this.count = 0; 26 | } 27 | 28 | return this; 29 | } 30 | } 31 | 32 | var start = clock(); 33 | var n = 4000000; 34 | var val = true; 35 | var toggle = Toggle(val); 36 | 37 | for (var i = 0; i < n; i = i + 1) { 38 | val = toggle.activate().value(); 39 | val = toggle.activate().value(); 40 | val = toggle.activate().value(); 41 | val = toggle.activate().value(); 42 | val = toggle.activate().value(); 43 | val = toggle.activate().value(); 44 | val = toggle.activate().value(); 45 | val = toggle.activate().value(); 46 | val = toggle.activate().value(); 47 | val = toggle.activate().value(); 48 | } 49 | 50 | print toggle.value(); 51 | 52 | val = true; 53 | var ntoggle = NthToggle(val, 3); 54 | 55 | for (var i = 0; i < n; i = i + 1) { 56 | val = ntoggle.activate().value(); 57 | val = ntoggle.activate().value(); 58 | val = ntoggle.activate().value(); 59 | val = ntoggle.activate().value(); 60 | val = ntoggle.activate().value(); 61 | val = ntoggle.activate().value(); 62 | val = ntoggle.activate().value(); 63 | val = ntoggle.activate().value(); 64 | val = ntoggle.activate().value(); 65 | val = ntoggle.activate().value(); 66 | } 67 | 68 | print ntoggle.value(); 69 | print clock() - start; 70 | -------------------------------------------------------------------------------- /src/vm/allocator.rs: -------------------------------------------------------------------------------- 1 | use std::alloc::{GlobalAlloc, Layout}; 2 | use std::sync::atomic::{AtomicUsize, Ordering}; 3 | 4 | #[cfg(any(miri, target_family = "wasm"))] 5 | #[global_allocator] 6 | pub static GLOBAL: Allocator = Allocator::new(std::alloc::System); 7 | 8 | #[cfg(not(any(miri, target_family = "wasm")))] 9 | #[global_allocator] 10 | pub static GLOBAL: Allocator = Allocator::new(mimalloc::MiMalloc); 11 | 12 | #[derive(Debug)] 13 | pub struct Allocator { 14 | inner: T, 15 | allocated_bytes: AtomicUsize, 16 | } 17 | 18 | impl Allocator { 19 | const fn new(inner: T) -> Self { 20 | Self { inner, allocated_bytes: AtomicUsize::new(0) } 21 | } 22 | 23 | pub fn allocated_bytes(&self) -> usize { 24 | self.allocated_bytes.load(Ordering::Relaxed) 25 | } 26 | } 27 | 28 | unsafe impl GlobalAlloc for Allocator { 29 | unsafe fn alloc(&self, layout: Layout) -> *mut u8 { 30 | self.allocated_bytes.fetch_add(layout.size(), Ordering::Relaxed); 31 | unsafe { self.inner.alloc(layout) } 32 | } 33 | 34 | unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { 35 | self.allocated_bytes.fetch_sub(layout.size(), Ordering::Relaxed); 36 | unsafe { self.inner.dealloc(ptr, layout) } 37 | } 38 | 39 | unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { 40 | self.allocated_bytes.fetch_add(layout.size(), Ordering::Relaxed); 41 | unsafe { self.inner.alloc_zeroed(layout) } 42 | } 43 | 44 | unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { 45 | self.allocated_bytes.fetch_add(new_size.wrapping_sub(layout.size()), Ordering::Relaxed); 46 | unsafe { self.inner.realloc(ptr, layout, new_size) } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /playground/src/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const ScrollArea = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 17 | 18 | {children} 19 | 20 | 21 | 22 | 23 | )); 24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName; 25 | 26 | const ScrollBar = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, orientation = "vertical", ...props }, ref) => ( 30 | 43 | 44 | 45 | )); 46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName; 47 | 48 | export { ScrollArea, ScrollBar }; 49 | -------------------------------------------------------------------------------- /playground/src/components/ui/resizable.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { GripVertical } from "lucide-react"; 4 | import * as ResizablePrimitive from "react-resizable-panels"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const ResizablePanelGroup = ({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) => ( 12 | 19 | ); 20 | 21 | const ResizablePanel = ResizablePrimitive.Panel; 22 | 23 | const ResizableHandle = ({ 24 | withHandle, 25 | className, 26 | ...props 27 | }: React.ComponentProps & { 28 | withHandle?: boolean; 29 | }) => ( 30 | div]:rotate-90", 33 | className, 34 | )} 35 | {...props} 36 | > 37 | {withHandle && ( 38 |
39 | 40 |
41 | )} 42 |
43 | ); 44 | 45 | export { ResizablePanelGroup, ResizablePanel, ResizableHandle }; 46 | -------------------------------------------------------------------------------- /playground/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Slot } from "@radix-ui/react-slot"; 3 | import { cva, type VariantProps } from "class-variance-authority"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | }, 34 | ); 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean; 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button"; 45 | return ( 46 | 51 | ); 52 | }, 53 | ); 54 | Button.displayName = "Button"; 55 | 56 | export { Button, buttonVariants }; 57 | -------------------------------------------------------------------------------- /playground/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 240 10% 3.9%; 9 | --card: 0 0% 100%; 10 | --card-foreground: 240 10% 3.9%; 11 | --popover: 0 0% 100%; 12 | --popover-foreground: 240 10% 3.9%; 13 | --primary: 240 5.9% 10%; 14 | --primary-foreground: 0 0% 98%; 15 | --secondary: 240 4.8% 95.9%; 16 | --secondary-foreground: 240 5.9% 10%; 17 | --muted: 240 4.8% 95.9%; 18 | --muted-foreground: 240 3.8% 46.1%; 19 | --accent: 240 4.8% 95.9%; 20 | --accent-foreground: 240 5.9% 10%; 21 | --destructive: 0 84.2% 60.2%; 22 | --destructive-foreground: 0 0% 98%; 23 | --border: 240 5.9% 90%; 24 | --input: 240 5.9% 90%; 25 | --ring: 240 10% 3.9%; 26 | --chart-1: 12 76% 61%; 27 | --chart-2: 173 58% 39%; 28 | --chart-3: 197 37% 24%; 29 | --chart-4: 43 74% 66%; 30 | --chart-5: 27 87% 67%; 31 | --radius: 0.5rem; 32 | } 33 | .dark { 34 | --background: 240 10% 3.9%; 35 | --foreground: 0 0% 98%; 36 | --card: 240 10% 3.9%; 37 | --card-foreground: 0 0% 98%; 38 | --popover: 240 10% 3.9%; 39 | --popover-foreground: 0 0% 98%; 40 | --primary: 0 0% 98%; 41 | --primary-foreground: 240 5.9% 10%; 42 | --secondary: 240 3.7% 15.9%; 43 | --secondary-foreground: 0 0% 98%; 44 | --muted: 240 3.7% 15.9%; 45 | --muted-foreground: 240 5% 64.9%; 46 | --accent: 240 3.7% 15.9%; 47 | --accent-foreground: 0 0% 98%; 48 | --destructive: 0 62.8% 30.6%; 49 | --destructive-foreground: 0 0% 98%; 50 | --border: 240 3.7% 15.9%; 51 | --input: 240 3.7% 15.9%; 52 | --ring: 240 4.9% 83.9%; 53 | --chart-1: 220 70% 50%; 54 | --chart-2: 160 60% 45%; 55 | --chart-3: 30 80% 55%; 56 | --chart-4: 280 65% 60%; 57 | --chart-5: 340 75% 55%; 58 | } 59 | } 60 | @layer base { 61 | * { 62 | @apply border-border; 63 | } 64 | body { 65 | @apply bg-background text-foreground; 66 | } 67 | } 68 | 69 | .ace_editor { 70 | @apply border font-mono font-normal leading-normal text-sm text-white tracking-normal !important; 71 | } 72 | 73 | .ace_gutter { 74 | @apply border-r !important; 75 | } 76 | -------------------------------------------------------------------------------- /Taskfile.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | run: once 3 | 4 | tasks: 5 | build: 6 | deps: 7 | - build-playground 8 | cmd: cargo build --features=playground {{.CLI_ARGS}} 9 | build-playground: 10 | deps: 11 | - setup-playground 12 | cmds: 13 | - pnpm run build:wasm 14 | - pnpm install --prefer-frozen-lockfile 15 | - pnpm run build 16 | dir: playground 17 | 18 | fmt: 19 | deps: 20 | - fmt-loxcraft 21 | - fmt-playground 22 | - fmt-lox-wasm 23 | fmt-loxcraft: 24 | cmd: cargo +nightly fmt --all 25 | internal: true 26 | fmt-playground: 27 | deps: 28 | - setup-playground 29 | cmd: pnpm run fmt 30 | dir: playground 31 | internal: true 32 | fmt-lox-wasm: 33 | cmd: cargo +nightly fmt --all 34 | dir: playground/rust/lox-wasm 35 | internal: true 36 | 37 | install: 38 | deps: 39 | - build-playground 40 | cmd: cargo install --features=playground --force --locked --path=. 41 | 42 | lint: 43 | deps: 44 | - lint-loxcraft-clippy 45 | - lint-loxcraft-rustfmt 46 | - lint-playground 47 | - lint-lox-wasm-clippy 48 | - lint-lox-wasm-rustfmt 49 | lint-loxcraft-clippy: 50 | cmd: cargo clippy --all-features --all-targets --workspace -- --deny=warnings 51 | internal: true 52 | lint-loxcraft-rustfmt: 53 | cmd: cargo +nightly fmt --all -- --check 54 | internal: true 55 | lint-playground: 56 | deps: 57 | - setup-playground 58 | cmd: pnpm run lint 59 | dir: playground 60 | internal: true 61 | lint-lox-wasm-clippy: 62 | cmd: cargo clippy --all-features --all-targets --workspace -- --deny=warnings 63 | dir: playground/rust/lox-wasm 64 | internal: true 65 | lint-lox-wasm-rustfmt: 66 | cmd: cargo +nightly fmt --all -- --check 67 | dir: playground/rust/lox-wasm 68 | internal: true 69 | 70 | setup: 71 | deps: 72 | - setup-playground 73 | setup-playground: 74 | cmd: pnpm install --prefer-frozen-lockfile 75 | dir: playground 76 | internal: true 77 | 78 | test: 79 | cmd: cargo nextest run --features='gc-stress,gc-trace,vm-trace' --workspace {{.CLI_ARGS}} 80 | 81 | test-miri: 82 | cmd: > 83 | MIRIFLAGS='-Zmiri-disable-isolation' 84 | cargo +nightly miri nextest run --features='gc-stress,gc-trace,vm-trace' --no-default-features --workspace {{.CLI_ARGS}} 85 | -------------------------------------------------------------------------------- /src/syntax/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ast; 2 | pub mod lexer; 3 | pub mod parser; 4 | 5 | use lalrpop_util::ParseError; 6 | 7 | use crate::error::{Error, ErrorS, SyntaxError}; 8 | use crate::syntax::ast::Program; 9 | use crate::syntax::lexer::Lexer; 10 | use crate::syntax::parser::Parser; 11 | 12 | pub fn is_complete(source: &str) -> bool { 13 | let lexer = Lexer::new(source); 14 | let parser = Parser::new(); 15 | let mut errors = Vec::new(); 16 | if let Err(e) = parser.parse(&mut errors, lexer) { 17 | errors.push(e); 18 | }; 19 | !errors.iter().any(|e| matches!(e, ParseError::UnrecognizedEof { .. })) 20 | } 21 | 22 | pub fn parse(source: &str, offset: usize) -> Result> { 23 | let lexer = Lexer::new(source).map(|token| match token { 24 | Ok((l, token, r)) => Ok((l + offset, token, r + offset)), 25 | Err((e, span)) => Err((e, span.start + offset..span.end + offset)), 26 | }); 27 | let parser = Parser::new(); 28 | let mut errors = Vec::new(); 29 | 30 | let mut parser_errors = Vec::new(); 31 | let program = match parser.parse(&mut parser_errors, lexer) { 32 | Ok(program) => program, 33 | Err(err) => { 34 | parser_errors.push(err); 35 | Program::default() 36 | } 37 | }; 38 | 39 | errors.extend(parser_errors.into_iter().map(|err| match err { 40 | ParseError::ExtraToken { token: (start, _, end) } => ( 41 | Error::SyntaxError(SyntaxError::ExtraToken { 42 | token: source[start - offset..end - offset].to_string(), 43 | }), 44 | start..end, 45 | ), 46 | ParseError::InvalidToken { location } => { 47 | (Error::SyntaxError(SyntaxError::InvalidToken), location..location) 48 | } 49 | ParseError::UnrecognizedEof { location, expected } => { 50 | (Error::SyntaxError(SyntaxError::UnrecognizedEof { expected }), location..location) 51 | } 52 | ParseError::UnrecognizedToken { token: (start, _, end), expected } => ( 53 | Error::SyntaxError(SyntaxError::UnrecognizedToken { 54 | token: source[start - offset..end - offset].to_string(), 55 | expected, 56 | }), 57 | start..end, 58 | ), 59 | ParseError::User { error } => error, 60 | })); 61 | 62 | if errors.is_empty() { Ok(program) } else { Err(errors) } 63 | } 64 | -------------------------------------------------------------------------------- /res/benchmarks/string_equality.lox: -------------------------------------------------------------------------------- 1 | var a1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1"; 2 | var a2 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2"; 3 | var a3 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa3"; 4 | var a4 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa4"; 5 | var a5 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa5"; 6 | var a6 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa6"; 7 | var a7 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa7"; 8 | var a8 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa8"; 9 | 10 | var n = 15000000; 11 | 12 | fun constants() { 13 | var i = 0; 14 | while (i < n) { 15 | i = i + 1; 16 | a1; a1; a1; a2; a1; a3; a1; a4; a1; a5; a1; a6; a1; a7; a1; a8; 17 | a2; a1; a2; a2; a2; a3; a2; a4; a2; a5; a2; a6; a2; a7; a2; a8; 18 | a3; a1; a3; a2; a3; a3; a3; a4; a3; a5; a3; a6; a3; a7; a3; a8; 19 | a4; a1; a4; a2; a4; a3; a4; a4; a4; a5; a4; a6; a4; a7; a4; a8; 20 | a5; a1; a5; a2; a5; a3; a5; a4; a5; a5; a5; a6; a5; a7; a5; a8; 21 | a6; a1; a6; a2; a6; a3; a6; a4; a6; a5; a6; a6; a6; a7; a6; a8; 22 | a7; a1; a7; a2; a7; a3; a7; a4; a7; a5; a7; a6; a7; a7; a7; a8; 23 | a8; a1; a8; a2; a8; a3; a8; a4; a8; a5; a8; a6; a8; a7; a8; a8; 24 | } 25 | } 26 | 27 | fun equality() { 28 | var i = 0; 29 | while (i < n) { 30 | i = i + 1; 31 | a1 == a1; a1 == a2; a1 == a3; a1 == a4; a1 == a5; a1 == a6; a1 == a7; a1 == a8; 32 | a2 == a1; a2 == a2; a2 == a3; a2 == a4; a2 == a5; a2 == a6; a2 == a7; a2 == a8; 33 | a3 == a1; a3 == a2; a3 == a3; a3 == a4; a3 == a5; a3 == a6; a3 == a7; a3 == a8; 34 | a4 == a1; a4 == a2; a4 == a3; a4 == a4; a4 == a5; a4 == a6; a4 == a7; a4 == a8; 35 | a5 == a1; a5 == a2; a5 == a3; a5 == a4; a5 == a5; a5 == a6; a5 == a7; a5 == a8; 36 | a6 == a1; a6 == a2; a6 == a3; a6 == a4; a6 == a5; a6 == a6; a6 == a7; a6 == a8; 37 | a7 == a1; a7 == a2; a7 == a3; a7 == a4; a7 == a5; a7 == a6; a7 == a7; a7 == a8; 38 | a8 == a1; a8 == a2; a8 == a3; a8 == a4; a8 == a5; a8 == a6; a8 == a7; a8 == a8; 39 | } 40 | } 41 | 42 | var loopStart = clock(); 43 | constants(); 44 | var loopTime = clock() - loopStart; 45 | 46 | var start = clock(); 47 | equality(); 48 | var elapsed = clock() - start; 49 | 50 | print "loop"; 51 | print loopTime; 52 | print "elapsed"; 53 | print elapsed; 54 | print "equals"; 55 | print elapsed - loopTime; 56 | -------------------------------------------------------------------------------- /playground/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: [ 5 | "./app/**/*.{js,ts,jsx,tsx,mdx}", // Note the addition of the `app` directory. 6 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 8 | 9 | // Or if using `src` directory: 10 | "./src/**/*.{js,ts,jsx,tsx,mdx}", 11 | ], 12 | darkMode: ["class"], 13 | theme: { 14 | extend: { 15 | borderRadius: { 16 | lg: "var(--radius)", 17 | md: "calc(var(--radius) - 2px)", 18 | sm: "calc(var(--radius) - 4px)", 19 | }, 20 | colors: { 21 | background: "hsl(var(--background))", 22 | foreground: "hsl(var(--foreground))", 23 | card: { 24 | DEFAULT: "hsl(var(--card))", 25 | foreground: "hsl(var(--card-foreground))", 26 | }, 27 | popover: { 28 | DEFAULT: "hsl(var(--popover))", 29 | foreground: "hsl(var(--popover-foreground))", 30 | }, 31 | primary: { 32 | DEFAULT: "hsl(var(--primary))", 33 | foreground: "hsl(var(--primary-foreground))", 34 | }, 35 | secondary: { 36 | DEFAULT: "hsl(var(--secondary))", 37 | foreground: "hsl(var(--secondary-foreground))", 38 | }, 39 | muted: { 40 | DEFAULT: "hsl(var(--muted))", 41 | foreground: "hsl(var(--muted-foreground))", 42 | }, 43 | accent: { 44 | DEFAULT: "hsl(var(--accent))", 45 | foreground: "hsl(var(--accent-foreground))", 46 | }, 47 | destructive: { 48 | DEFAULT: "hsl(var(--destructive))", 49 | foreground: "hsl(var(--destructive-foreground))", 50 | }, 51 | border: "hsl(var(--border))", 52 | input: "hsl(var(--input))", 53 | ring: "hsl(var(--ring))", 54 | chart: { 55 | "1": "hsl(var(--chart-1))", 56 | "2": "hsl(var(--chart-2))", 57 | "3": "hsl(var(--chart-3))", 58 | "4": "hsl(var(--chart-4))", 59 | "5": "hsl(var(--chart-5))", 60 | }, 61 | }, 62 | }, 63 | }, 64 | plugins: [require("tailwindcss-animate")], 65 | safelist: [ 66 | "text-zinc-950", 67 | "text-blue-300", 68 | "text-lime-300", 69 | "text-red-500", 70 | "text-amber-300", 71 | "text-zinc-50", 72 | "bg-zinc-950", 73 | "bg-blue-300", 74 | "bg-lime-300", 75 | "bg-red-500", 76 | "bg-amber-300", 77 | "bg-zinc-50", 78 | ], 79 | } satisfies Config; 80 | -------------------------------------------------------------------------------- /src/cmd.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io::{self, Read, Write}; 3 | 4 | use anyhow::{Context, Result, bail}; 5 | use clap::Parser; 6 | 7 | use crate::error::ErrorS; 8 | use crate::vm::VM; 9 | 10 | #[derive(Debug, Parser)] 11 | #[command(about, author, disable_help_subcommand = true, propagate_version = true, version)] 12 | pub enum Cmd { 13 | Lsp, 14 | Playground { 15 | #[arg(long, default_value = "4000")] 16 | port: u16, 17 | }, 18 | Repl, 19 | Run { 20 | path: String, 21 | }, 22 | } 23 | 24 | impl Cmd { 25 | pub fn run(&self) -> Result<()> { 26 | #[allow(unused_variables)] 27 | match self { 28 | #[cfg(feature = "lsp")] 29 | Cmd::Lsp => crate::lsp::serve(), 30 | #[cfg(not(feature = "lsp"))] 31 | Cmd::Lsp => bail!("loxcraft was not compiled with the `lsp` feature"), 32 | 33 | #[cfg(feature = "playground")] 34 | Cmd::Playground { port } => crate::playground::serve(*port), 35 | #[cfg(not(feature = "playground"))] 36 | Cmd::Playground { .. } => { 37 | bail!("loxcraft was not compiled with the `playground` feature") 38 | } 39 | 40 | #[cfg(feature = "repl")] 41 | Cmd::Repl => crate::repl::run(), 42 | #[cfg(not(feature = "repl"))] 43 | Cmd::Repl => bail!("loxcraft was not compiled with the `repl` feature"), 44 | 45 | Cmd::Run { path } => { 46 | let source = if path == "-" { 47 | let mut source = String::new(); 48 | io::stdin() 49 | .read_to_string(&mut source) 50 | .context("could not read source from stdin")?; 51 | source 52 | } else { 53 | fs::read_to_string(path) 54 | .with_context(|| format!("could not read source from file: {path}"))? 55 | }; 56 | 57 | let mut vm = VM::default(); 58 | let stdout = &mut io::stdout().lock(); 59 | if let Err(e) = vm.run(&source, stdout) { 60 | report_err(&source, e); 61 | bail!("program exited with errors"); 62 | } 63 | Ok(()) 64 | } 65 | } 66 | } 67 | } 68 | 69 | fn report_err(source: &str, errors: Vec) { 70 | let mut buffer = termcolor::Buffer::ansi(); 71 | for err in errors { 72 | crate::error::report_error(&mut buffer, source, &err); 73 | } 74 | io::stderr().write_all(buffer.as_slice()).expect("failed to write to stderr"); 75 | } 76 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | workflow_dispatch: 7 | env: 8 | CARGO_TERM_COLOR: always 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 11 | cancel-in-progress: true 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout repository 17 | uses: actions/checkout@v2 18 | - name: Install Rust (nightly) 19 | uses: dtolnay/rust-toolchain@nightly 20 | with: 21 | components: rustfmt 22 | toolchain: nightly 23 | - name: Install Rust 24 | uses: dtolnay/rust-toolchain@1.85 25 | with: 26 | components: clippy 27 | - name: Install pnpm 28 | uses: pnpm/action-setup@v4 29 | with: 30 | package_json_file: playground/package.json 31 | - name: Install Node 32 | uses: actions/setup-node@v3 33 | with: 34 | cache: pnpm 35 | cache-dependency-path: playground/pnpm-lock.yaml 36 | node-version: 18 37 | - name: Setup Rust cache 38 | uses: Swatinem/rust-cache@v1 39 | - name: Install Task 40 | uses: arduino/setup-task@v2 41 | with: 42 | version: 3.x 43 | - name: Run linters 44 | run: task lint 45 | test-miri: 46 | runs-on: ubuntu-latest 47 | strategy: 48 | matrix: 49 | partition: [1, 2, 3, 4, 5, 6, 7, 8] 50 | steps: 51 | - name: Checkout repository 52 | uses: actions/checkout@v2 53 | - name: Install Rust 54 | uses: dtolnay/rust-toolchain@nightly 55 | with: 56 | components: miri 57 | - name: Setup Rust cache 58 | uses: Swatinem/rust-cache@v1 59 | - name: Install cargo-nextest 60 | uses: taiki-e/install-action@v2 61 | with: 62 | tool: cargo-nextest 63 | - name: Install Task 64 | uses: arduino/setup-task@v2 65 | - name: Run tests 66 | run: task test-miri -- --no-fail-fast --partition=count:${{ matrix.partition }}/8 --test-threads=num-cpus 67 | test: 68 | runs-on: ubuntu-latest 69 | steps: 70 | - name: Checkout repository 71 | uses: actions/checkout@v2 72 | - name: Install Rust 73 | uses: dtolnay/rust-toolchain@1.85 74 | - name: Setup Rust cache 75 | uses: Swatinem/rust-cache@v2 76 | - name: Install cargo-nextest 77 | uses: taiki-e/install-action@v2 78 | with: 79 | tool: cargo-nextest 80 | - name: Install Task 81 | uses: arduino/setup-task@v2 82 | - name: Run tests 83 | run: task test -- --no-fail-fast 84 | -------------------------------------------------------------------------------- /res/examples/limit/too_many_locals.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | // var v00; // First slot already taken. 3 | 4 | var v01; var v02; var v03; var v04; var v05; var v06; var v07; 5 | var v08; var v09; var v0a; var v0b; var v0c; var v0d; var v0e; var v0f; 6 | 7 | var v10; var v11; var v12; var v13; var v14; var v15; var v16; var v17; 8 | var v18; var v19; var v1a; var v1b; var v1c; var v1d; var v1e; var v1f; 9 | 10 | var v20; var v21; var v22; var v23; var v24; var v25; var v26; var v27; 11 | var v28; var v29; var v2a; var v2b; var v2c; var v2d; var v2e; var v2f; 12 | 13 | var v30; var v31; var v32; var v33; var v34; var v35; var v36; var v37; 14 | var v38; var v39; var v3a; var v3b; var v3c; var v3d; var v3e; var v3f; 15 | 16 | var v40; var v41; var v42; var v43; var v44; var v45; var v46; var v47; 17 | var v48; var v49; var v4a; var v4b; var v4c; var v4d; var v4e; var v4f; 18 | 19 | var v50; var v51; var v52; var v53; var v54; var v55; var v56; var v57; 20 | var v58; var v59; var v5a; var v5b; var v5c; var v5d; var v5e; var v5f; 21 | 22 | var v60; var v61; var v62; var v63; var v64; var v65; var v66; var v67; 23 | var v68; var v69; var v6a; var v6b; var v6c; var v6d; var v6e; var v6f; 24 | 25 | var v70; var v71; var v72; var v73; var v74; var v75; var v76; var v77; 26 | var v78; var v79; var v7a; var v7b; var v7c; var v7d; var v7e; var v7f; 27 | 28 | var v80; var v81; var v82; var v83; var v84; var v85; var v86; var v87; 29 | var v88; var v89; var v8a; var v8b; var v8c; var v8d; var v8e; var v8f; 30 | 31 | var v90; var v91; var v92; var v93; var v94; var v95; var v96; var v97; 32 | var v98; var v99; var v9a; var v9b; var v9c; var v9d; var v9e; var v9f; 33 | 34 | var va0; var va1; var va2; var va3; var va4; var va5; var va6; var va7; 35 | var va8; var va9; var vaa; var vab; var vac; var vad; var vae; var vaf; 36 | 37 | var vb0; var vb1; var vb2; var vb3; var vb4; var vb5; var vb6; var vb7; 38 | var vb8; var vb9; var vba; var vbb; var vbc; var vbd; var vbe; var vbf; 39 | 40 | var vc0; var vc1; var vc2; var vc3; var vc4; var vc5; var vc6; var vc7; 41 | var vc8; var vc9; var vca; var vcb; var vcc; var vcd; var vce; var vcf; 42 | 43 | var vd0; var vd1; var vd2; var vd3; var vd4; var vd5; var vd6; var vd7; 44 | var vd8; var vd9; var vda; var vdb; var vdc; var vdd; var vde; var vdf; 45 | 46 | var ve0; var ve1; var ve2; var ve3; var ve4; var ve5; var ve6; var ve7; 47 | var ve8; var ve9; var vea; var veb; var vec; var ved; var vee; var vef; 48 | 49 | var vf0; var vf1; var vf2; var vf3; var vf4; var vf5; var vf6; var vf7; 50 | var vf8; var vf9; var vfa; var vfb; var vfc; var vfd; var vfe; var vff; 51 | 52 | // out: OverflowError: cannot define more than 256 local variables in a function 53 | var oops; 54 | } 55 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = [ 3 | "Ajeet D'Souza <98ajeet@gmail.com>", 4 | "Kartik Sharma ", 5 | ] 6 | categories = ["development-tools"] 7 | description = "A compiler, VM, language server, and online playground for the Lox programming language" 8 | edition = "2024" 9 | keywords = [ 10 | "cli", 11 | "compiler", 12 | "interpreter", 13 | "language", 14 | "lox", 15 | "lsp", 16 | "parser", 17 | "parsing", 18 | "script", 19 | "scripting", 20 | "vm", 21 | "wasm", 22 | "webassembly", 23 | ] 24 | license = "MIT" 25 | name = "loxcraft" 26 | repository = "https://github.com/ajeetdsouza/loxcraft" 27 | rust-version = "1.85.0" 28 | version = "0.1.1" 29 | 30 | [badges] 31 | maintenance = { status = "actively-developed" } 32 | 33 | [features] 34 | default = ["lsp", "repl"] 35 | gc-off = [] 36 | gc-stress = [] 37 | gc-trace = [] 38 | lsp = ["dep:tokio", "dep:tower-lsp"] 39 | playground = ["dep:rust-embed", "dep:warp", "dep:warp-embed", "dep:webbrowser"] 40 | repl = [ 41 | "dep:dirs", 42 | "dep:nu-ansi-term", 43 | "dep:reedline", 44 | "dep:tree-sitter", 45 | "dep:tree-sitter-highlight", 46 | "dep:tree-sitter-lox", 47 | ] 48 | vm-trace = [] 49 | 50 | [dependencies] 51 | anyhow = "1.0.52" 52 | arrayvec = "0.7.2" 53 | clap = { version = "4.0.0", features = ["derive"] } 54 | codespan-reporting = "0.11.1" 55 | dirs = { version = "5.0.0", optional = true } 56 | hashbrown = { version = "0.14.5", default-features = false, features = [ 57 | "inline-more", 58 | ] } 59 | iota = "0.2.2" 60 | lalrpop-util = "0.20.2" 61 | logos = "0.12.0" 62 | nu-ansi-term = { version = "0.50.0", optional = true } 63 | reedline = { version = "0.32.0", optional = true } 64 | rust-embed = { version = "8.4.0", features = ["compression"], optional = true } 65 | rustc-hash = "1.1.0" 66 | termcolor = "1.1.3" 67 | thiserror = "1.0.34" 68 | tokio = { version = "1.17.0", features = ["io-std", "rt"], optional = true } 69 | tower-lsp = { version = "0.20.0", optional = true } 70 | tree-sitter = { version = "0.20.4", optional = true } 71 | tree-sitter-highlight = { version = "0.20.1", optional = true } 72 | tree-sitter-lox = { version = "0.1.0", optional = true } 73 | warp = { version = "0.3.7", optional = true } 74 | warp-embed = { version = "0.5.0", optional = true } 75 | webbrowser = { version = "1.0.2", optional = true } 76 | 77 | [target.'cfg(target_family = "wasm")'.dependencies] 78 | wasm-bindgen = "0.2.100" 79 | 80 | [target.'cfg(not(any(miri, target_family = "wasm")))'.dependencies] 81 | mimalloc = { version = "0.1.27", default-features = false } 82 | 83 | [build-dependencies] 84 | build-deps = "0.1.4" 85 | lalrpop = { version = "0.20.2", default-features = false } 86 | 87 | [dev-dependencies] 88 | pretty_assertions = "1.1.0" 89 | test-generator = "0.3.0" 90 | 91 | [profile.release] 92 | codegen-units = 1 93 | debug = false 94 | lto = true 95 | panic = "abort" 96 | strip = true 97 | -------------------------------------------------------------------------------- /res/benchmarks/properties.lox: -------------------------------------------------------------------------------- 1 | // This benchmark stresses both field and method lookup. 2 | 3 | class Foo { 4 | init() { 5 | this.field0 = 1; 6 | this.field1 = 1; 7 | this.field2 = 1; 8 | this.field3 = 1; 9 | this.field4 = 1; 10 | this.field5 = 1; 11 | this.field6 = 1; 12 | this.field7 = 1; 13 | this.field8 = 1; 14 | this.field9 = 1; 15 | this.field10 = 1; 16 | this.field11 = 1; 17 | this.field12 = 1; 18 | this.field13 = 1; 19 | this.field14 = 1; 20 | this.field15 = 1; 21 | this.field16 = 1; 22 | this.field17 = 1; 23 | this.field18 = 1; 24 | this.field19 = 1; 25 | this.field20 = 1; 26 | this.field21 = 1; 27 | this.field22 = 1; 28 | this.field23 = 1; 29 | this.field24 = 1; 30 | this.field25 = 1; 31 | this.field26 = 1; 32 | this.field27 = 1; 33 | this.field28 = 1; 34 | this.field29 = 1; 35 | } 36 | 37 | method0() { return this.field0; } 38 | method1() { return this.field1; } 39 | method2() { return this.field2; } 40 | method3() { return this.field3; } 41 | method4() { return this.field4; } 42 | method5() { return this.field5; } 43 | method6() { return this.field6; } 44 | method7() { return this.field7; } 45 | method8() { return this.field8; } 46 | method9() { return this.field9; } 47 | method10() { return this.field10; } 48 | method11() { return this.field11; } 49 | method12() { return this.field12; } 50 | method13() { return this.field13; } 51 | method14() { return this.field14; } 52 | method15() { return this.field15; } 53 | method16() { return this.field16; } 54 | method17() { return this.field17; } 55 | method18() { return this.field18; } 56 | method19() { return this.field19; } 57 | method20() { return this.field20; } 58 | method21() { return this.field21; } 59 | method22() { return this.field22; } 60 | method23() { return this.field23; } 61 | method24() { return this.field24; } 62 | method25() { return this.field25; } 63 | method26() { return this.field26; } 64 | method27() { return this.field27; } 65 | method28() { return this.field28; } 66 | method29() { return this.field29; } 67 | } 68 | 69 | var foo = Foo(); 70 | var start = clock(); 71 | var i = 0; 72 | while (i < 7000000) { 73 | foo.method0(); 74 | foo.method1(); 75 | foo.method2(); 76 | foo.method3(); 77 | foo.method4(); 78 | foo.method5(); 79 | foo.method6(); 80 | foo.method7(); 81 | foo.method8(); 82 | foo.method9(); 83 | foo.method10(); 84 | foo.method11(); 85 | foo.method12(); 86 | foo.method13(); 87 | foo.method14(); 88 | foo.method15(); 89 | foo.method16(); 90 | foo.method17(); 91 | foo.method18(); 92 | foo.method19(); 93 | foo.method20(); 94 | foo.method21(); 95 | foo.method22(); 96 | foo.method23(); 97 | foo.method24(); 98 | foo.method25(); 99 | foo.method26(); 100 | foo.method27(); 101 | foo.method28(); 102 | foo.method29(); 103 | i = i + 1; 104 | } 105 | 106 | print clock() - start; 107 | -------------------------------------------------------------------------------- /src/vm/op.rs: -------------------------------------------------------------------------------- 1 | use iota::iota; 2 | 3 | iota! { 4 | pub const 5 | // Reads a 1-byte constant index, and pushes the constant at that index onto 6 | // the stack. 7 | CONSTANT: u8 = iota;, 8 | // Pushes a nil value onto the stack. 9 | NIL, 10 | // Pushes a true value onto the stack. 11 | TRUE, 12 | // Pushes a false value to the stack. 13 | FALSE, 14 | // Pops a value from the stack. 15 | POP, 16 | // Reads a 1-byte stack slot, and pushes the value at that slot onto the 17 | // stack. 18 | GET_LOCAL, 19 | // Reads a 1-byte stack slot, and peeks at the value on top of the stack. 20 | // Sets the value at the stack slot to the value on top of the stack. 21 | SET_LOCAL, 22 | GET_GLOBAL, 23 | DEFINE_GLOBAL, 24 | SET_GLOBAL, 25 | GET_UPVALUE, 26 | SET_UPVALUE, 27 | GET_PROPERTY, 28 | SET_PROPERTY, 29 | GET_SUPER, 30 | // Pops 2 values from the stack, tests them for equality, and pushes the 31 | // result onto the stack. 32 | EQUAL, 33 | // Pops 2 values from the stack, tests them for inequality, and pushes the 34 | // result onto the stack. 35 | NOT_EQUAL, 36 | // Pops 2 values from the stack, tests the second for being greater than the 37 | // first, and pushes the result onto the stack. 38 | GREATER, 39 | // Pops 2 values from the stack, tests the second for being greater than or 40 | // equal to the first, and pushes the result onto the stack. 41 | GREATER_EQUAL, 42 | // Pops 2 values from the stack, tests the second for being less than the 43 | // first, and pushes the result onto the stack. 44 | LESS, 45 | // Pops 2 values from the stack, tests the second for being less than or 46 | // equal to the first, and pushes the result onto the stack. 47 | LESS_EQUAL, 48 | // Pops 2 values from the stack, adds (in case of numbers) or concatenates 49 | // (in case of strings) them, and pushes the result onto the stack. 50 | ADD, 51 | // Pops 2 numbers from the stack, subtracts the first from the second, and 52 | // pushes the result onto the stack. 53 | SUBTRACT, 54 | // Pops 2 numbers from the stack, multiplies them, and pushes the result 55 | // onto the stack. 56 | MULTIPLY, 57 | // Pops 2 numbers from the stack, divides the second by the first, and 58 | // pushes the result onto the stack. 59 | DIVIDE, 60 | // Pops a value from the stack, checks if it is "falsey", and pushes the 61 | // result onto the stack. 62 | NOT, 63 | // Pops a number from the stack, negates it, and pushes the result onto the 64 | // stack. 65 | NEGATE, 66 | // Pops a value from the stack and prints it. 67 | PRINT, 68 | // Reads a 2-byte offset, and increments the instruction pointer by that 69 | // offset. 70 | JUMP, 71 | // Reads a 2-byte offset, and peeks at the value on top of the stack. If the 72 | // value is falsey, increments the instruction pointer by that offset. 73 | JUMP_IF_FALSE, 74 | // Reads a 2-byte offset, and decrements the instruction pointer by that 75 | // offset. 76 | LOOP, 77 | CALL, 78 | INVOKE, 79 | SUPER_INVOKE, 80 | CLOSURE, 81 | CLOSE_UPVALUE, 82 | RETURN, 83 | CLASS, 84 | INHERIT, 85 | METHOD 86 | } 87 | -------------------------------------------------------------------------------- /src/lsp.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "lsp")] 2 | 3 | use anyhow::{Context, Result}; 4 | use tower_lsp::lsp_types::{ 5 | Diagnostic, DiagnosticSeverity, DidChangeTextDocumentParams, DidOpenTextDocumentParams, 6 | InitializeParams, InitializeResult, Position, Range, ServerCapabilities, ServerInfo, 7 | TextDocumentSyncKind, 8 | }; 9 | use tower_lsp::{Client, LanguageServer, LspService, Server, jsonrpc}; 10 | 11 | use crate::types::Span; 12 | use crate::vm::{Compiler, Gc}; 13 | 14 | #[derive(Debug)] 15 | struct Backend { 16 | client: Client, 17 | } 18 | 19 | impl Backend { 20 | pub fn new(client: Client) -> Self { 21 | Self { client } 22 | } 23 | 24 | pub fn get_diagnostics(&self, source: &str) -> Vec { 25 | let mut gc = Gc::default(); 26 | Compiler::compile(source, 0, &mut gc) 27 | .err() 28 | .unwrap_or_default() 29 | .iter() 30 | .map(|(err, span)| Diagnostic { 31 | range: get_range(source, span), 32 | severity: Some(DiagnosticSeverity::ERROR), 33 | message: err.to_string(), 34 | ..Default::default() 35 | }) 36 | .collect() 37 | } 38 | } 39 | 40 | #[tower_lsp::async_trait] 41 | impl LanguageServer for Backend { 42 | async fn initialize(&self, _: InitializeParams) -> jsonrpc::Result { 43 | Ok(InitializeResult { 44 | capabilities: ServerCapabilities { 45 | text_document_sync: Some(TextDocumentSyncKind::FULL.into()), 46 | ..Default::default() 47 | }, 48 | server_info: Some(ServerInfo { 49 | name: env!("CARGO_PKG_NAME").to_string(), 50 | version: Some(env!("CARGO_PKG_VERSION").to_string()), 51 | }), 52 | }) 53 | } 54 | 55 | async fn shutdown(&self) -> jsonrpc::Result<()> { 56 | Ok(()) 57 | } 58 | 59 | async fn did_open(&self, params: DidOpenTextDocumentParams) { 60 | let source = ¶ms.text_document.text; 61 | let uri = params.text_document.uri; 62 | let version = Some(params.text_document.version); 63 | let diagnostics = self.get_diagnostics(source); 64 | self.client.publish_diagnostics(uri, diagnostics, version).await; 65 | } 66 | 67 | async fn did_change(&self, params: DidChangeTextDocumentParams) { 68 | let source = ¶ms.content_changes.first().unwrap().text; 69 | let uri = params.text_document.uri; 70 | let version = Some(params.text_document.version); 71 | let diagnostics = self.get_diagnostics(source); 72 | self.client.publish_diagnostics(uri, diagnostics, version).await; 73 | } 74 | } 75 | 76 | fn get_range(source: &str, span: &Span) -> Range { 77 | Range { start: get_position(source, span.start), end: get_position(source, span.end) } 78 | } 79 | 80 | fn get_position(source: &str, idx: usize) -> Position { 81 | let before = &source[..idx]; 82 | let line = before.lines().count() - 1; 83 | let character = before.lines().last().unwrap().len(); 84 | Position { line: line as _, character: character as _ } 85 | } 86 | 87 | pub fn serve() -> Result<()> { 88 | tokio::runtime::Builder::new_current_thread() 89 | .enable_all() 90 | .build() 91 | .context("failed to start async runtime")? 92 | .block_on(serve_async()); 93 | Ok(()) 94 | } 95 | 96 | async fn serve_async() { 97 | let stdin = tokio::io::stdin(); 98 | let stdout = tokio::io::stdout(); 99 | 100 | let (service, socket) = LspService::new(Backend::new); 101 | Server::new(stdin, stdout, socket).serve(service).await; 102 | } 103 | -------------------------------------------------------------------------------- /res/examples/function/too_many_parameters.lox: -------------------------------------------------------------------------------- 1 | // 256 parameters. 2 | // out: OverflowError: cannot define more than 256 parameters in a function 3 | fun f( 4 | a1, 5 | a2, 6 | a3, 7 | a4, 8 | a5, 9 | a6, 10 | a7, 11 | a8, 12 | a9, 13 | a10, 14 | a11, 15 | a12, 16 | a13, 17 | a14, 18 | a15, 19 | a16, 20 | a17, 21 | a18, 22 | a19, 23 | a20, 24 | a21, 25 | a22, 26 | a23, 27 | a24, 28 | a25, 29 | a26, 30 | a27, 31 | a28, 32 | a29, 33 | a30, 34 | a31, 35 | a32, 36 | a33, 37 | a34, 38 | a35, 39 | a36, 40 | a37, 41 | a38, 42 | a39, 43 | a40, 44 | a41, 45 | a42, 46 | a43, 47 | a44, 48 | a45, 49 | a46, 50 | a47, 51 | a48, 52 | a49, 53 | a50, 54 | a51, 55 | a52, 56 | a53, 57 | a54, 58 | a55, 59 | a56, 60 | a57, 61 | a58, 62 | a59, 63 | a60, 64 | a61, 65 | a62, 66 | a63, 67 | a64, 68 | a65, 69 | a66, 70 | a67, 71 | a68, 72 | a69, 73 | a70, 74 | a71, 75 | a72, 76 | a73, 77 | a74, 78 | a75, 79 | a76, 80 | a77, 81 | a78, 82 | a79, 83 | a80, 84 | a81, 85 | a82, 86 | a83, 87 | a84, 88 | a85, 89 | a86, 90 | a87, 91 | a88, 92 | a89, 93 | a90, 94 | a91, 95 | a92, 96 | a93, 97 | a94, 98 | a95, 99 | a96, 100 | a97, 101 | a98, 102 | a99, 103 | a100, 104 | a101, 105 | a102, 106 | a103, 107 | a104, 108 | a105, 109 | a106, 110 | a107, 111 | a108, 112 | a109, 113 | a110, 114 | a111, 115 | a112, 116 | a113, 117 | a114, 118 | a115, 119 | a116, 120 | a117, 121 | a118, 122 | a119, 123 | a120, 124 | a121, 125 | a122, 126 | a123, 127 | a124, 128 | a125, 129 | a126, 130 | a127, 131 | a128, 132 | a129, 133 | a130, 134 | a131, 135 | a132, 136 | a133, 137 | a134, 138 | a135, 139 | a136, 140 | a137, 141 | a138, 142 | a139, 143 | a140, 144 | a141, 145 | a142, 146 | a143, 147 | a144, 148 | a145, 149 | a146, 150 | a147, 151 | a148, 152 | a149, 153 | a150, 154 | a151, 155 | a152, 156 | a153, 157 | a154, 158 | a155, 159 | a156, 160 | a157, 161 | a158, 162 | a159, 163 | a160, 164 | a161, 165 | a162, 166 | a163, 167 | a164, 168 | a165, 169 | a166, 170 | a167, 171 | a168, 172 | a169, 173 | a170, 174 | a171, 175 | a172, 176 | a173, 177 | a174, 178 | a175, 179 | a176, 180 | a177, 181 | a178, 182 | a179, 183 | a180, 184 | a181, 185 | a182, 186 | a183, 187 | a184, 188 | a185, 189 | a186, 190 | a187, 191 | a188, 192 | a189, 193 | a190, 194 | a191, 195 | a192, 196 | a193, 197 | a194, 198 | a195, 199 | a196, 200 | a197, 201 | a198, 202 | a199, 203 | a200, 204 | a201, 205 | a202, 206 | a203, 207 | a204, 208 | a205, 209 | a206, 210 | a207, 211 | a208, 212 | a209, 213 | a210, 214 | a211, 215 | a212, 216 | a213, 217 | a214, 218 | a215, 219 | a216, 220 | a217, 221 | a218, 222 | a219, 223 | a220, 224 | a221, 225 | a222, 226 | a223, 227 | a224, 228 | a225, 229 | a226, 230 | a227, 231 | a228, 232 | a229, 233 | a230, 234 | a231, 235 | a232, 236 | a233, 237 | a234, 238 | a235, 239 | a236, 240 | a237, 241 | a238, 242 | a239, 243 | a240, 244 | a241, 245 | a242, 246 | a243, 247 | a244, 248 | a245, 249 | a246, 250 | a247, 251 | a248, 252 | a249, 253 | a250, 254 | a251, 255 | a252, 256 | a253, 257 | a254, 258 | a255, a) {} 259 | -------------------------------------------------------------------------------- /res/examples/method/too_many_parameters.lox: -------------------------------------------------------------------------------- 1 | class Foo { 2 | // 256 parameters. 3 | // out: OverflowError: cannot define more than 256 parameters in a function 4 | method( 5 | a1, 6 | a2, 7 | a3, 8 | a4, 9 | a5, 10 | a6, 11 | a7, 12 | a8, 13 | a9, 14 | a10, 15 | a11, 16 | a12, 17 | a13, 18 | a14, 19 | a15, 20 | a16, 21 | a17, 22 | a18, 23 | a19, 24 | a20, 25 | a21, 26 | a22, 27 | a23, 28 | a24, 29 | a25, 30 | a26, 31 | a27, 32 | a28, 33 | a29, 34 | a30, 35 | a31, 36 | a32, 37 | a33, 38 | a34, 39 | a35, 40 | a36, 41 | a37, 42 | a38, 43 | a39, 44 | a40, 45 | a41, 46 | a42, 47 | a43, 48 | a44, 49 | a45, 50 | a46, 51 | a47, 52 | a48, 53 | a49, 54 | a50, 55 | a51, 56 | a52, 57 | a53, 58 | a54, 59 | a55, 60 | a56, 61 | a57, 62 | a58, 63 | a59, 64 | a60, 65 | a61, 66 | a62, 67 | a63, 68 | a64, 69 | a65, 70 | a66, 71 | a67, 72 | a68, 73 | a69, 74 | a70, 75 | a71, 76 | a72, 77 | a73, 78 | a74, 79 | a75, 80 | a76, 81 | a77, 82 | a78, 83 | a79, 84 | a80, 85 | a81, 86 | a82, 87 | a83, 88 | a84, 89 | a85, 90 | a86, 91 | a87, 92 | a88, 93 | a89, 94 | a90, 95 | a91, 96 | a92, 97 | a93, 98 | a94, 99 | a95, 100 | a96, 101 | a97, 102 | a98, 103 | a99, 104 | a100, 105 | a101, 106 | a102, 107 | a103, 108 | a104, 109 | a105, 110 | a106, 111 | a107, 112 | a108, 113 | a109, 114 | a110, 115 | a111, 116 | a112, 117 | a113, 118 | a114, 119 | a115, 120 | a116, 121 | a117, 122 | a118, 123 | a119, 124 | a120, 125 | a121, 126 | a122, 127 | a123, 128 | a124, 129 | a125, 130 | a126, 131 | a127, 132 | a128, 133 | a129, 134 | a130, 135 | a131, 136 | a132, 137 | a133, 138 | a134, 139 | a135, 140 | a136, 141 | a137, 142 | a138, 143 | a139, 144 | a140, 145 | a141, 146 | a142, 147 | a143, 148 | a144, 149 | a145, 150 | a146, 151 | a147, 152 | a148, 153 | a149, 154 | a150, 155 | a151, 156 | a152, 157 | a153, 158 | a154, 159 | a155, 160 | a156, 161 | a157, 162 | a158, 163 | a159, 164 | a160, 165 | a161, 166 | a162, 167 | a163, 168 | a164, 169 | a165, 170 | a166, 171 | a167, 172 | a168, 173 | a169, 174 | a170, 175 | a171, 176 | a172, 177 | a173, 178 | a174, 179 | a175, 180 | a176, 181 | a177, 182 | a178, 183 | a179, 184 | a180, 185 | a181, 186 | a182, 187 | a183, 188 | a184, 189 | a185, 190 | a186, 191 | a187, 192 | a188, 193 | a189, 194 | a190, 195 | a191, 196 | a192, 197 | a193, 198 | a194, 199 | a195, 200 | a196, 201 | a197, 202 | a198, 203 | a199, 204 | a200, 205 | a201, 206 | a202, 207 | a203, 208 | a204, 209 | a205, 210 | a206, 211 | a207, 212 | a208, 213 | a209, 214 | a210, 215 | a211, 216 | a212, 217 | a213, 218 | a214, 219 | a215, 220 | a216, 221 | a217, 222 | a218, 223 | a219, 224 | a220, 225 | a221, 226 | a222, 227 | a223, 228 | a224, 229 | a225, 230 | a226, 231 | a227, 232 | a228, 233 | a229, 234 | a230, 235 | a231, 236 | a232, 237 | a233, 238 | a234, 239 | a235, 240 | a236, 241 | a237, 242 | a238, 243 | a239, 244 | a240, 245 | a241, 246 | a242, 247 | a243, 248 | a244, 249 | a245, 250 | a246, 251 | a247, 252 | a248, 253 | a249, 254 | a250, 255 | a251, 256 | a252, 257 | a253, 258 | a254, 259 | a255, a) {} 260 | } 261 | -------------------------------------------------------------------------------- /res/examples/limit/too_many_upvalues.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | var v00; var v01; var v02; var v03; var v04; var v05; var v06; var v07; 3 | var v08; var v09; var v0a; var v0b; var v0c; var v0d; var v0e; var v0f; 4 | 5 | var v10; var v11; var v12; var v13; var v14; var v15; var v16; var v17; 6 | var v18; var v19; var v1a; var v1b; var v1c; var v1d; var v1e; var v1f; 7 | 8 | var v20; var v21; var v22; var v23; var v24; var v25; var v26; var v27; 9 | var v28; var v29; var v2a; var v2b; var v2c; var v2d; var v2e; var v2f; 10 | 11 | var v30; var v31; var v32; var v33; var v34; var v35; var v36; var v37; 12 | var v38; var v39; var v3a; var v3b; var v3c; var v3d; var v3e; var v3f; 13 | 14 | var v40; var v41; var v42; var v43; var v44; var v45; var v46; var v47; 15 | var v48; var v49; var v4a; var v4b; var v4c; var v4d; var v4e; var v4f; 16 | 17 | var v50; var v51; var v52; var v53; var v54; var v55; var v56; var v57; 18 | var v58; var v59; var v5a; var v5b; var v5c; var v5d; var v5e; var v5f; 19 | 20 | var v60; var v61; var v62; var v63; var v64; var v65; var v66; var v67; 21 | var v68; var v69; var v6a; var v6b; var v6c; var v6d; var v6e; var v6f; 22 | 23 | var v70; var v71; var v72; var v73; var v74; var v75; var v76; var v77; 24 | var v78; var v79; var v7a; var v7b; var v7c; var v7d; var v7e; var v7f; 25 | 26 | fun g() { 27 | var v80; var v81; var v82; var v83; var v84; var v85; var v86; var v87; 28 | var v88; var v89; var v8a; var v8b; var v8c; var v8d; var v8e; var v8f; 29 | 30 | var v90; var v91; var v92; var v93; var v94; var v95; var v96; var v97; 31 | var v98; var v99; var v9a; var v9b; var v9c; var v9d; var v9e; var v9f; 32 | 33 | var va0; var va1; var va2; var va3; var va4; var va5; var va6; var va7; 34 | var va8; var va9; var vaa; var vab; var vac; var vad; var vae; var vaf; 35 | 36 | var vb0; var vb1; var vb2; var vb3; var vb4; var vb5; var vb6; var vb7; 37 | var vb8; var vb9; var vba; var vbb; var vbc; var vbd; var vbe; var vbf; 38 | 39 | var vc0; var vc1; var vc2; var vc3; var vc4; var vc5; var vc6; var vc7; 40 | var vc8; var vc9; var vca; var vcb; var vcc; var vcd; var vce; var vcf; 41 | 42 | var vd0; var vd1; var vd2; var vd3; var vd4; var vd5; var vd6; var vd7; 43 | var vd8; var vd9; var vda; var vdb; var vdc; var vdd; var vde; var vdf; 44 | 45 | var ve0; var ve1; var ve2; var ve3; var ve4; var ve5; var ve6; var ve7; 46 | var ve8; var ve9; var vea; var veb; var vec; var ved; var vee; var vef; 47 | 48 | var vf0; var vf1; var vf2; var vf3; var vf4; var vf5; var vf6; var vf7; 49 | var vf8; var vf9; var vfa; var vfb; var vfc; var vfd; var vfe; var vff; 50 | 51 | var oops; 52 | 53 | fun h() { 54 | v00; v01; v02; v03; v04; v05; v06; v07; 55 | v08; v09; v0a; v0b; v0c; v0d; v0e; v0f; 56 | 57 | v10; v11; v12; v13; v14; v15; v16; v17; 58 | v18; v19; v1a; v1b; v1c; v1d; v1e; v1f; 59 | 60 | v20; v21; v22; v23; v24; v25; v26; v27; 61 | v28; v29; v2a; v2b; v2c; v2d; v2e; v2f; 62 | 63 | v30; v31; v32; v33; v34; v35; v36; v37; 64 | v38; v39; v3a; v3b; v3c; v3d; v3e; v3f; 65 | 66 | v40; v41; v42; v43; v44; v45; v46; v47; 67 | v48; v49; v4a; v4b; v4c; v4d; v4e; v4f; 68 | 69 | v50; v51; v52; v53; v54; v55; v56; v57; 70 | v58; v59; v5a; v5b; v5c; v5d; v5e; v5f; 71 | 72 | v60; v61; v62; v63; v64; v65; v66; v67; 73 | v68; v69; v6a; v6b; v6c; v6d; v6e; v6f; 74 | 75 | v70; v71; v72; v73; v74; v75; v76; v77; 76 | v78; v79; v7a; v7b; v7c; v7d; v7e; v7f; 77 | 78 | v80; v81; v82; v83; v84; v85; v86; v87; 79 | v88; v89; v8a; v8b; v8c; v8d; v8e; v8f; 80 | 81 | v90; v91; v92; v93; v94; v95; v96; v97; 82 | v98; v99; v9a; v9b; v9c; v9d; v9e; v9f; 83 | 84 | va0; va1; va2; va3; va4; va5; va6; va7; 85 | va8; va9; vaa; vab; vac; vad; vae; vaf; 86 | 87 | vb0; vb1; vb2; vb3; vb4; vb5; vb6; vb7; 88 | vb8; vb9; vba; vbb; vbc; vbd; vbe; vbf; 89 | 90 | vc0; vc1; vc2; vc3; vc4; vc5; vc6; vc7; 91 | vc8; vc9; vca; vcb; vcc; vcd; vce; vcf; 92 | 93 | vd0; vd1; vd2; vd3; vd4; vd5; vd6; vd7; 94 | vd8; vd9; vda; vdb; vdc; vdd; vde; vdf; 95 | 96 | ve0; ve1; ve2; ve3; ve4; ve5; ve6; ve7; 97 | ve8; ve9; vea; veb; vec; ved; vee; vef; 98 | 99 | vf0; vf1; vf2; vf3; vf4; vf5; vf6; vf7; 100 | vf8; vf9; vfa; vfb; vfc; vfd; vfe; vff; 101 | 102 | // out: OverflowError: cannot use more than 256 closure variables in a function 103 | oops; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /playground/rust/lox-wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display, Formatter}; 2 | use std::io::{self, Write}; 3 | 4 | use loxcraft::error::report_error; 5 | use loxcraft::vm::VM; 6 | use serde::Serialize; 7 | use termcolor::{Color, WriteColor}; 8 | use wasm_bindgen::prelude::*; 9 | 10 | #[wasm_bindgen] 11 | #[allow(non_snake_case)] 12 | pub fn loxRun(source: &str) { 13 | let writer = Output::new(); 14 | let mut writer = HtmlWriter::new(writer); 15 | match VM::default().run(source, &mut writer) { 16 | Ok(()) => postMessage(&Message::ExitSuccess.to_string()), 17 | Err(errors) => { 18 | for e in errors.iter() { 19 | report_error(&mut writer, source, e); 20 | } 21 | postMessage(&Message::ExitFailure.to_string()); 22 | } 23 | } 24 | } 25 | 26 | #[allow(dead_code)] 27 | #[derive(Debug, Serialize)] 28 | #[serde(tag = "type")] 29 | enum Message { 30 | ExitFailure, 31 | ExitSuccess, 32 | Output { text: String }, 33 | } 34 | 35 | impl Display for Message { 36 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 37 | write!(f, "{}", serde_json::to_string(self).expect("could not serialize message")) 38 | } 39 | } 40 | 41 | #[wasm_bindgen] 42 | extern "C" { 43 | #[wasm_bindgen(js_namespace = self)] 44 | fn postMessage(s: &str); 45 | } 46 | 47 | #[derive(Debug)] 48 | struct Output; 49 | 50 | impl Output { 51 | fn new() -> Self { 52 | Self 53 | } 54 | } 55 | 56 | impl Write for Output { 57 | fn write(&mut self, buf: &[u8]) -> io::Result { 58 | let text = String::from_utf8_lossy(buf).to_string(); 59 | postMessage(&Message::Output { text }.to_string()); 60 | Ok(buf.len()) 61 | } 62 | 63 | fn flush(&mut self) -> io::Result<()> { 64 | Ok(()) 65 | } 66 | } 67 | 68 | /// Provides a [`WriteColor`] implementation for HTML, using Tailwind CSS classes. 69 | #[derive(Debug)] 70 | struct HtmlWriter { 71 | writer: W, 72 | span_count: usize, 73 | } 74 | 75 | impl HtmlWriter { 76 | fn new(writer: W) -> Self { 77 | HtmlWriter { writer, span_count: 0 } 78 | } 79 | } 80 | 81 | impl Write for HtmlWriter { 82 | fn write(&mut self, buf: &[u8]) -> io::Result { 83 | let escaped = String::from_utf8_lossy(buf); 84 | let escaped = askama_escape::escape(&escaped, askama_escape::Html).to_string(); 85 | write!(self.writer, "{escaped}")?; 86 | Ok(buf.len()) 87 | } 88 | 89 | fn flush(&mut self) -> io::Result<()> { 90 | self.writer.flush() 91 | } 92 | } 93 | 94 | impl WriteColor for HtmlWriter { 95 | fn supports_color(&self) -> bool { 96 | true 97 | } 98 | 99 | fn set_color(&mut self, spec: &termcolor::ColorSpec) -> io::Result<()> { 100 | if spec.reset() { 101 | self.reset()?; 102 | } 103 | 104 | let mut classes = Vec::new(); 105 | if let Some(fg) = spec.fg() { 106 | match fg { 107 | Color::Black => classes.push("text-zinc-950"), 108 | Color::Blue => classes.push("text-blue-300"), 109 | Color::Green => classes.push("text-lime-300"), 110 | Color::Red => classes.push("text-red-500"), 111 | Color::Yellow => classes.push("text-amber-300"), 112 | _ => classes.push("text-zinc-50"), 113 | }; 114 | } 115 | if let Some(bg) = spec.bg() { 116 | match bg { 117 | Color::Black => classes.push("bg-zinc-950"), 118 | Color::Blue => classes.push("bg-blue-300"), 119 | Color::Green => classes.push("bg-lime-300"), 120 | Color::Red => classes.push("bg-red-500"), 121 | Color::Yellow => classes.push("bg-amber-300"), 122 | _ => classes.push("bg-zinc-50"), 123 | }; 124 | } 125 | if spec.bold() { 126 | classes.push("font-bold"); 127 | } 128 | if spec.dimmed() { 129 | classes.push("text-opacity-75"); 130 | } 131 | if spec.italic() { 132 | classes.push("italic"); 133 | } 134 | if spec.underline() { 135 | classes.push("underline"); 136 | } 137 | 138 | if !classes.is_empty() { 139 | write!(self.writer, r#""#, classes.join(" "))?; 140 | self.span_count += 1; 141 | } 142 | Ok(()) 143 | } 144 | 145 | fn reset(&mut self) -> io::Result<()> { 146 | for _ in 0..self.span_count { 147 | write!(self.writer, "")?; 148 | } 149 | self.span_count = 0; 150 | Ok(()) 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /res/examples/method/too_many_arguments.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = 1; 3 | // out: OverflowError: cannot use more than 256 arguments in a function 4 | true.method( 5 | a, // 1 6 | a, // 2 7 | a, // 3 8 | a, // 4 9 | a, // 5 10 | a, // 6 11 | a, // 7 12 | a, // 8 13 | a, // 9 14 | a, // 10 15 | a, // 11 16 | a, // 12 17 | a, // 13 18 | a, // 14 19 | a, // 15 20 | a, // 16 21 | a, // 17 22 | a, // 18 23 | a, // 19 24 | a, // 20 25 | a, // 21 26 | a, // 22 27 | a, // 23 28 | a, // 24 29 | a, // 25 30 | a, // 26 31 | a, // 27 32 | a, // 28 33 | a, // 29 34 | a, // 30 35 | a, // 31 36 | a, // 32 37 | a, // 33 38 | a, // 34 39 | a, // 35 40 | a, // 36 41 | a, // 37 42 | a, // 38 43 | a, // 39 44 | a, // 40 45 | a, // 41 46 | a, // 42 47 | a, // 43 48 | a, // 44 49 | a, // 45 50 | a, // 46 51 | a, // 47 52 | a, // 48 53 | a, // 49 54 | a, // 50 55 | a, // 51 56 | a, // 52 57 | a, // 53 58 | a, // 54 59 | a, // 55 60 | a, // 56 61 | a, // 57 62 | a, // 58 63 | a, // 59 64 | a, // 60 65 | a, // 61 66 | a, // 62 67 | a, // 63 68 | a, // 64 69 | a, // 65 70 | a, // 66 71 | a, // 67 72 | a, // 68 73 | a, // 69 74 | a, // 70 75 | a, // 71 76 | a, // 72 77 | a, // 73 78 | a, // 74 79 | a, // 75 80 | a, // 76 81 | a, // 77 82 | a, // 78 83 | a, // 79 84 | a, // 80 85 | a, // 81 86 | a, // 82 87 | a, // 83 88 | a, // 84 89 | a, // 85 90 | a, // 86 91 | a, // 87 92 | a, // 88 93 | a, // 89 94 | a, // 90 95 | a, // 91 96 | a, // 92 97 | a, // 93 98 | a, // 94 99 | a, // 95 100 | a, // 96 101 | a, // 97 102 | a, // 98 103 | a, // 99 104 | a, // 100 105 | a, // 101 106 | a, // 102 107 | a, // 103 108 | a, // 104 109 | a, // 105 110 | a, // 106 111 | a, // 107 112 | a, // 108 113 | a, // 109 114 | a, // 110 115 | a, // 111 116 | a, // 112 117 | a, // 113 118 | a, // 114 119 | a, // 115 120 | a, // 116 121 | a, // 117 122 | a, // 118 123 | a, // 119 124 | a, // 120 125 | a, // 121 126 | a, // 122 127 | a, // 123 128 | a, // 124 129 | a, // 125 130 | a, // 126 131 | a, // 127 132 | a, // 128 133 | a, // 129 134 | a, // 130 135 | a, // 131 136 | a, // 132 137 | a, // 133 138 | a, // 134 139 | a, // 135 140 | a, // 136 141 | a, // 137 142 | a, // 138 143 | a, // 139 144 | a, // 140 145 | a, // 141 146 | a, // 142 147 | a, // 143 148 | a, // 144 149 | a, // 145 150 | a, // 146 151 | a, // 147 152 | a, // 148 153 | a, // 149 154 | a, // 150 155 | a, // 151 156 | a, // 152 157 | a, // 153 158 | a, // 154 159 | a, // 155 160 | a, // 156 161 | a, // 157 162 | a, // 158 163 | a, // 159 164 | a, // 160 165 | a, // 161 166 | a, // 162 167 | a, // 163 168 | a, // 164 169 | a, // 165 170 | a, // 166 171 | a, // 167 172 | a, // 168 173 | a, // 169 174 | a, // 170 175 | a, // 171 176 | a, // 172 177 | a, // 173 178 | a, // 174 179 | a, // 175 180 | a, // 176 181 | a, // 177 182 | a, // 178 183 | a, // 179 184 | a, // 180 185 | a, // 181 186 | a, // 182 187 | a, // 183 188 | a, // 184 189 | a, // 185 190 | a, // 186 191 | a, // 187 192 | a, // 188 193 | a, // 189 194 | a, // 190 195 | a, // 191 196 | a, // 192 197 | a, // 193 198 | a, // 194 199 | a, // 195 200 | a, // 196 201 | a, // 197 202 | a, // 198 203 | a, // 199 204 | a, // 200 205 | a, // 201 206 | a, // 202 207 | a, // 203 208 | a, // 204 209 | a, // 205 210 | a, // 206 211 | a, // 207 212 | a, // 208 213 | a, // 209 214 | a, // 210 215 | a, // 211 216 | a, // 212 217 | a, // 213 218 | a, // 214 219 | a, // 215 220 | a, // 216 221 | a, // 217 222 | a, // 218 223 | a, // 219 224 | a, // 220 225 | a, // 221 226 | a, // 222 227 | a, // 223 228 | a, // 224 229 | a, // 225 230 | a, // 226 231 | a, // 227 232 | a, // 228 233 | a, // 229 234 | a, // 230 235 | a, // 231 236 | a, // 232 237 | a, // 233 238 | a, // 234 239 | a, // 235 240 | a, // 236 241 | a, // 237 242 | a, // 238 243 | a, // 239 244 | a, // 240 245 | a, // 241 246 | a, // 242 247 | a, // 243 248 | a, // 244 249 | a, // 245 250 | a, // 246 251 | a, // 247 252 | a, // 248 253 | a, // 249 254 | a, // 250 255 | a, // 251 256 | a, // 252 257 | a, // 253 258 | a, // 254 259 | a, // 255 260 | a); 261 | } 262 | -------------------------------------------------------------------------------- /res/examples/function/too_many_arguments.lox: -------------------------------------------------------------------------------- 1 | fun foo() {} 2 | { 3 | var a = 1; 4 | // out: OverflowError: cannot use more than 256 arguments in a function 5 | foo( 6 | a, // 1 7 | a, // 2 8 | a, // 3 9 | a, // 4 10 | a, // 5 11 | a, // 6 12 | a, // 7 13 | a, // 8 14 | a, // 9 15 | a, // 10 16 | a, // 11 17 | a, // 12 18 | a, // 13 19 | a, // 14 20 | a, // 15 21 | a, // 16 22 | a, // 17 23 | a, // 18 24 | a, // 19 25 | a, // 20 26 | a, // 21 27 | a, // 22 28 | a, // 23 29 | a, // 24 30 | a, // 25 31 | a, // 26 32 | a, // 27 33 | a, // 28 34 | a, // 29 35 | a, // 30 36 | a, // 31 37 | a, // 32 38 | a, // 33 39 | a, // 34 40 | a, // 35 41 | a, // 36 42 | a, // 37 43 | a, // 38 44 | a, // 39 45 | a, // 40 46 | a, // 41 47 | a, // 42 48 | a, // 43 49 | a, // 44 50 | a, // 45 51 | a, // 46 52 | a, // 47 53 | a, // 48 54 | a, // 49 55 | a, // 50 56 | a, // 51 57 | a, // 52 58 | a, // 53 59 | a, // 54 60 | a, // 55 61 | a, // 56 62 | a, // 57 63 | a, // 58 64 | a, // 59 65 | a, // 60 66 | a, // 61 67 | a, // 62 68 | a, // 63 69 | a, // 64 70 | a, // 65 71 | a, // 66 72 | a, // 67 73 | a, // 68 74 | a, // 69 75 | a, // 70 76 | a, // 71 77 | a, // 72 78 | a, // 73 79 | a, // 74 80 | a, // 75 81 | a, // 76 82 | a, // 77 83 | a, // 78 84 | a, // 79 85 | a, // 80 86 | a, // 81 87 | a, // 82 88 | a, // 83 89 | a, // 84 90 | a, // 85 91 | a, // 86 92 | a, // 87 93 | a, // 88 94 | a, // 89 95 | a, // 90 96 | a, // 91 97 | a, // 92 98 | a, // 93 99 | a, // 94 100 | a, // 95 101 | a, // 96 102 | a, // 97 103 | a, // 98 104 | a, // 99 105 | a, // 100 106 | a, // 101 107 | a, // 102 108 | a, // 103 109 | a, // 104 110 | a, // 105 111 | a, // 106 112 | a, // 107 113 | a, // 108 114 | a, // 109 115 | a, // 110 116 | a, // 111 117 | a, // 112 118 | a, // 113 119 | a, // 114 120 | a, // 115 121 | a, // 116 122 | a, // 117 123 | a, // 118 124 | a, // 119 125 | a, // 120 126 | a, // 121 127 | a, // 122 128 | a, // 123 129 | a, // 124 130 | a, // 125 131 | a, // 126 132 | a, // 127 133 | a, // 128 134 | a, // 129 135 | a, // 130 136 | a, // 131 137 | a, // 132 138 | a, // 133 139 | a, // 134 140 | a, // 135 141 | a, // 136 142 | a, // 137 143 | a, // 138 144 | a, // 139 145 | a, // 140 146 | a, // 141 147 | a, // 142 148 | a, // 143 149 | a, // 144 150 | a, // 145 151 | a, // 146 152 | a, // 147 153 | a, // 148 154 | a, // 149 155 | a, // 150 156 | a, // 151 157 | a, // 152 158 | a, // 153 159 | a, // 154 160 | a, // 155 161 | a, // 156 162 | a, // 157 163 | a, // 158 164 | a, // 159 165 | a, // 160 166 | a, // 161 167 | a, // 162 168 | a, // 163 169 | a, // 164 170 | a, // 165 171 | a, // 166 172 | a, // 167 173 | a, // 168 174 | a, // 169 175 | a, // 170 176 | a, // 171 177 | a, // 172 178 | a, // 173 179 | a, // 174 180 | a, // 175 181 | a, // 176 182 | a, // 177 183 | a, // 178 184 | a, // 179 185 | a, // 180 186 | a, // 181 187 | a, // 182 188 | a, // 183 189 | a, // 184 190 | a, // 185 191 | a, // 186 192 | a, // 187 193 | a, // 188 194 | a, // 189 195 | a, // 190 196 | a, // 191 197 | a, // 192 198 | a, // 193 199 | a, // 194 200 | a, // 195 201 | a, // 196 202 | a, // 197 203 | a, // 198 204 | a, // 199 205 | a, // 200 206 | a, // 201 207 | a, // 202 208 | a, // 203 209 | a, // 204 210 | a, // 205 211 | a, // 206 212 | a, // 207 213 | a, // 208 214 | a, // 209 215 | a, // 210 216 | a, // 211 217 | a, // 212 218 | a, // 213 219 | a, // 214 220 | a, // 215 221 | a, // 216 222 | a, // 217 223 | a, // 218 224 | a, // 219 225 | a, // 220 226 | a, // 221 227 | a, // 222 228 | a, // 223 229 | a, // 224 230 | a, // 225 231 | a, // 226 232 | a, // 227 233 | a, // 228 234 | a, // 229 235 | a, // 230 236 | a, // 231 237 | a, // 232 238 | a, // 233 239 | a, // 234 240 | a, // 235 241 | a, // 236 242 | a, // 237 243 | a, // 238 244 | a, // 239 245 | a, // 240 246 | a, // 241 247 | a, // 242 248 | a, // 243 249 | a, // 244 250 | a, // 245 251 | a, // 246 252 | a, // 247 253 | a, // 248 254 | a, // 249 255 | a, // 250 256 | a, // 251 257 | a, // 252 258 | a, // 253 259 | a, // 254 260 | a, // 255 261 | a); 262 | } 263 | -------------------------------------------------------------------------------- /src/syntax/lexer.rs: -------------------------------------------------------------------------------- 1 | use std::num::ParseFloatError; 2 | 3 | use logos::Logos; 4 | 5 | use crate::error::{Error, ErrorS, SyntaxError}; 6 | 7 | #[derive(Debug)] 8 | pub struct Lexer<'a> { 9 | inner: logos::Lexer<'a, Token>, 10 | pending: Option<(usize, Token, usize)>, 11 | } 12 | 13 | impl<'a> Lexer<'a> { 14 | pub fn new(source: &'a str) -> Self { 15 | Self { inner: Token::lexer(source), pending: None } 16 | } 17 | } 18 | 19 | impl Iterator for Lexer<'_> { 20 | type Item = Result<(usize, Token, usize), ErrorS>; 21 | 22 | fn next(&mut self) -> Option { 23 | if let Some(token) = self.pending.take() { 24 | return Some(Ok(token)); 25 | } 26 | 27 | match self.inner.next()? { 28 | Token::Error => { 29 | let mut span = self.inner.span(); 30 | 31 | // Check for unterminated string. 32 | if self.inner.slice().starts_with('"') { 33 | return Some(Err((Error::SyntaxError(SyntaxError::UnterminatedString), span))); 34 | } 35 | 36 | // Recover error. 37 | while let Some(token) = self.inner.next() { 38 | let span_new = self.inner.span(); 39 | if span.end == span_new.start { 40 | span.end = span_new.end; 41 | } else { 42 | self.pending = Some((span_new.start, token, span_new.end)); 43 | break; 44 | } 45 | } 46 | 47 | Some(Err(( 48 | Error::SyntaxError(SyntaxError::UnexpectedInput { 49 | token: self.inner.source()[span.start..span.end].to_string(), 50 | }), 51 | span, 52 | ))) 53 | } 54 | token => { 55 | let span = self.inner.span(); 56 | Some(Ok((span.start, token, span.end))) 57 | } 58 | } 59 | } 60 | } 61 | 62 | #[derive(Clone, Debug, Logos, PartialEq)] 63 | pub enum Token { 64 | // Single-character tokens. 65 | #[token("(")] 66 | LtParen, 67 | #[token(")")] 68 | RtParen, 69 | #[token("{")] 70 | LtBrace, 71 | #[token("}")] 72 | RtBrace, 73 | #[token(",")] 74 | Comma, 75 | #[token(".")] 76 | Dot, 77 | #[token("-")] 78 | Minus, 79 | #[token("+")] 80 | Plus, 81 | #[token(";")] 82 | Semicolon, 83 | #[token("/")] 84 | Slash, 85 | #[token("*")] 86 | Asterisk, 87 | 88 | // One or two character tokens. 89 | #[token("!")] 90 | Bang, 91 | #[token("!=")] 92 | BangEqual, 93 | #[token("=")] 94 | Equal, 95 | #[token("==")] 96 | EqualEqual, 97 | #[token(">")] 98 | Greater, 99 | #[token(">=")] 100 | GreaterEqual, 101 | #[token("<")] 102 | Less, 103 | #[token("<=")] 104 | LessEqual, 105 | 106 | // Literals. 107 | #[regex("[a-zA-Z_][a-zA-Z0-9_]*", lex_identifier)] 108 | Identifier(String), 109 | #[regex(r#""[^"]*""#, lex_string)] 110 | String(String), 111 | #[regex(r#"[0-9]+(\.[0-9]+)?"#, lex_number)] 112 | Number(f64), 113 | 114 | // Keywords. 115 | #[token("and")] 116 | And, 117 | #[token("class")] 118 | Class, 119 | #[token("else")] 120 | Else, 121 | #[token("false")] 122 | False, 123 | #[token("for")] 124 | For, 125 | #[token("fun")] 126 | Fun, 127 | #[token("if")] 128 | If, 129 | #[token("nil")] 130 | Nil, 131 | #[token("or")] 132 | Or, 133 | #[token("print")] 134 | Print, 135 | #[token("return")] 136 | Return, 137 | #[token("super")] 138 | Super, 139 | #[token("this")] 140 | This, 141 | #[token("true")] 142 | True, 143 | #[token("var")] 144 | Var, 145 | #[token("while")] 146 | While, 147 | 148 | #[regex(r"//.*", logos::skip)] 149 | #[regex(r"[ \r\n\t\f]+", logos::skip)] 150 | #[error] 151 | Error, 152 | } 153 | 154 | fn lex_number(lexer: &mut logos::Lexer) -> Result { 155 | let slice = lexer.slice(); 156 | slice.parse::() 157 | } 158 | 159 | fn lex_string(lexer: &mut logos::Lexer) -> String { 160 | let slice = lexer.slice(); 161 | slice[1..slice.len() - 1].to_string() 162 | } 163 | 164 | fn lex_identifier(lexer: &mut logos::Lexer) -> String { 165 | let slice = lexer.slice(); 166 | slice.to_string() 167 | } 168 | 169 | #[cfg(test)] 170 | mod tests { 171 | use pretty_assertions::assert_eq; 172 | 173 | use super::*; 174 | 175 | #[test] 176 | fn lex_invalid_token() { 177 | let exp = vec![ 178 | Err(( 179 | Error::SyntaxError(SyntaxError::UnexpectedInput { token: "@foo".to_string() }), 180 | 0..4, 181 | )), 182 | Ok((5, Token::Identifier("bar".to_string()), 8)), 183 | ]; 184 | let got = Lexer::new("@foo bar").collect::>(); 185 | assert_eq!(exp, got); 186 | } 187 | 188 | #[test] 189 | fn lex_unterminated_string() { 190 | let exp = vec![Err((Error::SyntaxError(SyntaxError::UnterminatedString), 0..5))]; 191 | let got = Lexer::new("\"\nfoo").collect::>(); 192 | assert_eq!(exp, got); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 5 | 6 |
7 | 8 | # loxcraft 9 | 10 | [![crates.io](https://img.shields.io/crates/v/loxcraft)](https://crates.io/crates/loxcraft) 11 | 12 | **Language tooling** for the **[Lox programming language](http://craftinginterpreters.com/)**. 13 | 14 |
15 | 16 | ## Installation 17 | 18 | ```sh 19 | cargo install loxcraft --locked 20 | ``` 21 | 22 | ## Features 23 | 24 | - [x] Bytecode compiler + garbage collected runtime 25 | - [x] Online playground, via WebAssembly ([try it out!](https://ajeetdsouza.github.io/loxcraft/)) 26 | - [x] REPL 27 | - [x] Syntax highlighting, via [tree-sitter-lox](https://github.com/ajeetdsouza/tree-sitter-lox) 28 | - [x] IDE integration, via the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) 29 | 30 | ## Screenshots 31 | 32 | ![Screenshot of REPL](https://user-images.githubusercontent.com/1777663/216910834-4ea40427-34d7-43e0-8ba0-06638dfb0fa2.png) 33 | 34 | ![Screenshot of online playground](https://github.com/user-attachments/assets/3e798ad4-a299-40e0-9d3d-41b3eeeda44b) 35 | 36 | ## Benchmarks 37 | 38 | Time taken to execute the [benchmark suite](https://github.com/ajeetdsouza/loxcraft/tree/main/res/benchmarks) (lower is better): 39 | 40 | | Benchmark | loxcraft | clox | jlox | 41 | | ----------------- | -------- | ------ | ------- | 42 | | binary_tree | 8.29s | 8.13s | 26.41s | 43 | | equality_1 | 7.17s | 7.73s | 10.01s | 44 | | equality_2 | 8.39s | 9.66s | 14.30s | 45 | | fib | 10.90s | 10.09s | 21.89s | 46 | | instantiation | 10.83s | 12.84s | 14.24s | 47 | | invocation | 9.93s | 8.93s | 15.77s | 48 | | method_call | 11.01s | 9.12s | 62.03s | 49 | | properties | 10.05s | 5.98s | 69.77s | 50 | | string_equality_1 | 7.76s | 7.66s | 34.08s | 51 | | string_equality_2 | 10.78s | 10.52s | 36.25s | 52 | | trees | 9.97s | 8.72s | 72.87s | 53 | | zoo | 10.67s | 6.18s | 100.10s | 54 | 55 | ![Benchmarks](https://user-images.githubusercontent.com/1777663/216903842-5d626770-e599-491e-8e09-83b2f956cf34.svg) 56 | 57 | Benchmarks were run with the following configuration: 58 | 59 | - Device: Apple MacBook Pro (16-inch, 2021) 60 | - Processor: M1 Pro 61 | - RAM: 16 GiB 62 | - OS: macOS Ventura 13.2 63 | - Rust: 1.66.1 64 | - Apple Clang: 14.0.0 65 | - Oracle JDK: 19.0.2 66 | 67 | ## References 68 | 69 | So you want to build your own programming language! Here's some extremely helpful resources I referred to when building `loxcraft`: 70 | 71 | - [Crafting Interpreters](https://craftinginterpreters.com/) by Bob Nystrom: this book introduces you to a teaching programming language named Lox, walks you through implementing a full-featured tree walking interpreter for in in Java, and then shows you how to build a bytecode compiler + VM for it in C. I cannot recommend this book enough. 72 | - Bob Nystrom also has a [blog](https://journal.stuffwithstuff.com/), and his articles are really well written (see his post on [Pratt parsers](https://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/) / [garbage collectors](https://journal.stuffwithstuff.com/2013/12/08/babys-first-garbage-collector/)). I'd also recommend going through the source code for [Wren](https://wren.io/), it shares a lot of code with Lox. Despite the deceptive simplicity of the implementation, it (like Lox) is incredibly fast - it's a great way to learn how to build production grade compilers in general. 73 | - [Writing an Interpreter in Go](https://interpreterbook.com/) / [Writing a Compiler in Go](https://compilerbook.com/) by Thorsten Ball is a great set of books. Since it uses Go, it piggybacks on Go's garbage collector instead of building one of its own. This simplifies the implementation, making this book a lot easier to grok - but it also means that you may have trouble porting it to a non-GC language (like Rust). 74 | - [Make a Language](https://lunacookies.github.io/lang/) by Luna Razzaghipour is a fantastic series. Notably, this book constructs its syntax tree using the same library used by [rust-analyzer](https://rust-analyzer.github.io/) ([rowan](https://github.com/rust-analyzer/rowan)). 75 | - [Simple but Powerful Pratt Parsing](https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html) by Alex Kladov (one of the main authors behind rust-analyzer) is a great tutorial on building a parser in Rust. The rest of his blog is incredible too! 76 | - [rust-langdev](https://github.com/Kixiron/rust-langdev) has a lot of libraries for building compilers in Rust. To start off, I'd suggest [logos](https://github.com/maciejhirsz/logos) for lexing, [LALRPOP](https://lalrpop.github.io/lalrpop/) / [chumsky](https://github.com/zesterer/chumsky) for parsing, and [rust-gc](https://github.com/Manishearth/rust-gc) for garbage collection. 77 | - [Learning Rust with Entirely Too Many Linked Lists](https://rust-unofficial.github.io/too-many-lists/) is a quick tutorial on unsafe Rust, which you'll need if you're building a garbage collector yourself. 78 | - If you want some inspiration for a production-grade language built in Rust, you might want to go through the source code of [Starlark](https://github.com/facebook/starlark-rust) and [Gluon](https://github.com/gluon-lang/gluon). 79 | 80 | ## Contributors 81 | 82 | - [Ajeet D'Souza](https://github.com/ajeetdsouza) 83 | - [Kartik Sharma](https://github.com/crazystylus) 84 | -------------------------------------------------------------------------------- /src/syntax/ast.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display, Formatter}; 2 | 3 | pub use crate::types::Spanned; 4 | 5 | pub type StmtS = Spanned; 6 | pub type ExprS = Spanned; 7 | 8 | #[derive(Debug, Default)] 9 | pub struct Program { 10 | pub stmts: Vec, 11 | } 12 | 13 | #[derive(Clone, Debug, PartialEq)] 14 | pub enum Stmt { 15 | Block(StmtBlock), 16 | Class(StmtClass), 17 | Expr(StmtExpr), 18 | For(Box), 19 | Fun(StmtFun), 20 | If(Box), 21 | Print(StmtPrint), 22 | Return(StmtReturn), 23 | Var(StmtVar), 24 | While(Box), 25 | Error, 26 | } 27 | 28 | #[derive(Clone, Debug, PartialEq)] 29 | pub struct StmtBlock { 30 | pub stmts: Vec, 31 | } 32 | 33 | #[derive(Clone, Debug, PartialEq)] 34 | pub struct StmtClass { 35 | pub name: String, 36 | pub super_: Option, 37 | pub methods: Vec>, 38 | } 39 | 40 | /// An expression statement evaluates an expression and discards the result. 41 | #[derive(Clone, Debug, PartialEq)] 42 | pub struct StmtExpr { 43 | pub value: ExprS, 44 | } 45 | 46 | #[derive(Clone, Debug, PartialEq)] 47 | pub struct StmtFor { 48 | pub init: Option, 49 | pub cond: Option, 50 | pub incr: Option, 51 | pub body: StmtS, 52 | } 53 | 54 | #[derive(Clone, Debug, PartialEq)] 55 | pub struct StmtFun { 56 | pub name: String, 57 | pub params: Vec, 58 | pub body: StmtBlock, 59 | } 60 | 61 | #[derive(Clone, Debug, PartialEq)] 62 | pub struct StmtIf { 63 | pub cond: ExprS, 64 | pub then: StmtS, 65 | pub else_: Option, 66 | } 67 | 68 | #[derive(Clone, Debug, PartialEq)] 69 | pub struct StmtPrint { 70 | pub value: ExprS, 71 | } 72 | 73 | #[derive(Clone, Debug, PartialEq)] 74 | pub struct StmtReturn { 75 | pub value: Option, 76 | } 77 | 78 | #[derive(Clone, Debug, PartialEq)] 79 | pub struct StmtVar { 80 | pub var: Var, 81 | pub value: Option, 82 | } 83 | 84 | #[derive(Clone, Debug, PartialEq)] 85 | pub struct StmtWhile { 86 | pub cond: ExprS, 87 | pub body: StmtS, 88 | } 89 | 90 | #[derive(Clone, Debug, PartialEq)] 91 | pub enum Expr { 92 | Assign(Box), 93 | Call(Box), 94 | Get(Box), 95 | Infix(Box), 96 | Literal(ExprLiteral), 97 | Prefix(Box), 98 | Set(Box), 99 | Super(ExprSuper), 100 | Var(ExprVar), 101 | } 102 | 103 | #[derive(Clone, Debug, PartialEq)] 104 | pub struct ExprAssign { 105 | pub var: Var, 106 | pub value: ExprS, 107 | } 108 | 109 | #[derive(Clone, Debug, PartialEq)] 110 | pub struct ExprCall { 111 | pub callee: ExprS, 112 | pub args: Vec, 113 | } 114 | 115 | #[derive(Clone, Debug, PartialEq)] 116 | pub struct ExprGet { 117 | pub object: ExprS, 118 | pub name: String, 119 | } 120 | 121 | #[derive(Clone, Debug, PartialEq)] 122 | pub enum ExprLiteral { 123 | Bool(bool), 124 | Nil, 125 | Number(f64), 126 | String(String), 127 | } 128 | 129 | #[derive(Clone, Debug, PartialEq)] 130 | pub struct ExprInfix { 131 | pub lt: ExprS, 132 | pub op: OpInfix, 133 | pub rt: ExprS, 134 | } 135 | 136 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 137 | pub enum OpInfix { 138 | Add, 139 | Subtract, 140 | Multiply, 141 | Divide, 142 | Less, 143 | LessEqual, 144 | Greater, 145 | GreaterEqual, 146 | Equal, 147 | NotEqual, 148 | LogicAnd, 149 | LogicOr, 150 | } 151 | 152 | impl Display for OpInfix { 153 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 154 | let op = match self { 155 | OpInfix::Add => "+", 156 | OpInfix::Subtract => "-", 157 | OpInfix::Multiply => "*", 158 | OpInfix::Divide => "/", 159 | OpInfix::Less => "<", 160 | OpInfix::LessEqual => "<=", 161 | OpInfix::Greater => ">", 162 | OpInfix::GreaterEqual => ">=", 163 | OpInfix::Equal => "==", 164 | OpInfix::NotEqual => "!=", 165 | OpInfix::LogicAnd => "and", 166 | OpInfix::LogicOr => "or", 167 | }; 168 | write!(f, "{op}") 169 | } 170 | } 171 | 172 | #[derive(Clone, Debug, PartialEq)] 173 | pub struct ExprPrefix { 174 | pub op: OpPrefix, 175 | pub rt: ExprS, 176 | } 177 | 178 | #[derive(Debug, Clone, Copy, Eq, PartialEq)] 179 | pub enum OpPrefix { 180 | Negate, 181 | Not, 182 | } 183 | 184 | impl Display for OpPrefix { 185 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 186 | let op = match self { 187 | OpPrefix::Negate => "-", 188 | OpPrefix::Not => "!", 189 | }; 190 | write!(f, "{op}") 191 | } 192 | } 193 | 194 | #[derive(Clone, Debug, PartialEq)] 195 | pub struct ExprSet { 196 | pub object: ExprS, 197 | pub name: String, 198 | pub value: ExprS, 199 | } 200 | 201 | #[derive(Clone, Debug, Eq, PartialEq)] 202 | pub struct ExprSuper { 203 | pub super_: Var, 204 | pub name: String, 205 | } 206 | 207 | #[derive(Clone, Debug, Eq, PartialEq)] 208 | pub struct ExprVar { 209 | pub var: Var, 210 | } 211 | 212 | #[derive(Clone, Debug, Eq, PartialEq)] 213 | pub struct Var { 214 | pub name: String, 215 | /// This field is initialized as [`None`] by the parser, and is later 216 | /// filled by the resolver. 217 | pub depth: Option, 218 | } 219 | -------------------------------------------------------------------------------- /res/examples/field/many.lox: -------------------------------------------------------------------------------- 1 | class Foo {} 2 | 3 | var foo = Foo(); 4 | fun setFields() { 5 | foo.bilberry = "bilberry"; 6 | foo.lime = "lime"; 7 | foo.elderberry = "elderberry"; 8 | foo.raspberry = "raspberry"; 9 | foo.gooseberry = "gooseberry"; 10 | foo.longan = "longan"; 11 | foo.mandarine = "mandarine"; 12 | foo.kiwifruit = "kiwifruit"; 13 | foo.orange = "orange"; 14 | foo.pomegranate = "pomegranate"; 15 | foo.tomato = "tomato"; 16 | foo.banana = "banana"; 17 | foo.juniper = "juniper"; 18 | foo.damson = "damson"; 19 | foo.blackcurrant = "blackcurrant"; 20 | foo.peach = "peach"; 21 | foo.grape = "grape"; 22 | foo.mango = "mango"; 23 | foo.redcurrant = "redcurrant"; 24 | foo.watermelon = "watermelon"; 25 | foo.plumcot = "plumcot"; 26 | foo.papaya = "papaya"; 27 | foo.cloudberry = "cloudberry"; 28 | foo.rambutan = "rambutan"; 29 | foo.salak = "salak"; 30 | foo.physalis = "physalis"; 31 | foo.huckleberry = "huckleberry"; 32 | foo.coconut = "coconut"; 33 | foo.date = "date"; 34 | foo.tamarind = "tamarind"; 35 | foo.lychee = "lychee"; 36 | foo.raisin = "raisin"; 37 | foo.apple = "apple"; 38 | foo.avocado = "avocado"; 39 | foo.nectarine = "nectarine"; 40 | foo.pomelo = "pomelo"; 41 | foo.melon = "melon"; 42 | foo.currant = "currant"; 43 | foo.plum = "plum"; 44 | foo.persimmon = "persimmon"; 45 | foo.olive = "olive"; 46 | foo.cranberry = "cranberry"; 47 | foo.boysenberry = "boysenberry"; 48 | foo.blackberry = "blackberry"; 49 | foo.passionfruit = "passionfruit"; 50 | foo.mulberry = "mulberry"; 51 | foo.marionberry = "marionberry"; 52 | foo.plantain = "plantain"; 53 | foo.lemon = "lemon"; 54 | foo.yuzu = "yuzu"; 55 | foo.loquat = "loquat"; 56 | foo.kumquat = "kumquat"; 57 | foo.salmonberry = "salmonberry"; 58 | foo.tangerine = "tangerine"; 59 | foo.durian = "durian"; 60 | foo.pear = "pear"; 61 | foo.cantaloupe = "cantaloupe"; 62 | foo.quince = "quince"; 63 | foo.guava = "guava"; 64 | foo.strawberry = "strawberry"; 65 | foo.nance = "nance"; 66 | foo.apricot = "apricot"; 67 | foo.jambul = "jambul"; 68 | foo.grapefruit = "grapefruit"; 69 | foo.clementine = "clementine"; 70 | foo.jujube = "jujube"; 71 | foo.cherry = "cherry"; 72 | foo.feijoa = "feijoa"; 73 | foo.jackfruit = "jackfruit"; 74 | foo.fig = "fig"; 75 | foo.cherimoya = "cherimoya"; 76 | foo.pineapple = "pineapple"; 77 | foo.blueberry = "blueberry"; 78 | foo.jabuticaba = "jabuticaba"; 79 | foo.miracle = "miracle"; 80 | foo.dragonfruit = "dragonfruit"; 81 | foo.satsuma = "satsuma"; 82 | foo.tamarillo = "tamarillo"; 83 | foo.honeydew = "honeydew"; 84 | } 85 | 86 | setFields(); 87 | 88 | fun printFields() { 89 | print foo.apple; // out: apple 90 | print foo.apricot; // out: apricot 91 | print foo.avocado; // out: avocado 92 | print foo.banana; // out: banana 93 | print foo.bilberry; // out: bilberry 94 | print foo.blackberry; // out: blackberry 95 | print foo.blackcurrant; // out: blackcurrant 96 | print foo.blueberry; // out: blueberry 97 | print foo.boysenberry; // out: boysenberry 98 | print foo.cantaloupe; // out: cantaloupe 99 | print foo.cherimoya; // out: cherimoya 100 | print foo.cherry; // out: cherry 101 | print foo.clementine; // out: clementine 102 | print foo.cloudberry; // out: cloudberry 103 | print foo.coconut; // out: coconut 104 | print foo.cranberry; // out: cranberry 105 | print foo.currant; // out: currant 106 | print foo.damson; // out: damson 107 | print foo.date; // out: date 108 | print foo.dragonfruit; // out: dragonfruit 109 | print foo.durian; // out: durian 110 | print foo.elderberry; // out: elderberry 111 | print foo.feijoa; // out: feijoa 112 | print foo.fig; // out: fig 113 | print foo.gooseberry; // out: gooseberry 114 | print foo.grape; // out: grape 115 | print foo.grapefruit; // out: grapefruit 116 | print foo.guava; // out: guava 117 | print foo.honeydew; // out: honeydew 118 | print foo.huckleberry; // out: huckleberry 119 | print foo.jabuticaba; // out: jabuticaba 120 | print foo.jackfruit; // out: jackfruit 121 | print foo.jambul; // out: jambul 122 | print foo.jujube; // out: jujube 123 | print foo.juniper; // out: juniper 124 | print foo.kiwifruit; // out: kiwifruit 125 | print foo.kumquat; // out: kumquat 126 | print foo.lemon; // out: lemon 127 | print foo.lime; // out: lime 128 | print foo.longan; // out: longan 129 | print foo.loquat; // out: loquat 130 | print foo.lychee; // out: lychee 131 | print foo.mandarine; // out: mandarine 132 | print foo.mango; // out: mango 133 | print foo.marionberry; // out: marionberry 134 | print foo.melon; // out: melon 135 | print foo.miracle; // out: miracle 136 | print foo.mulberry; // out: mulberry 137 | print foo.nance; // out: nance 138 | print foo.nectarine; // out: nectarine 139 | print foo.olive; // out: olive 140 | print foo.orange; // out: orange 141 | print foo.papaya; // out: papaya 142 | print foo.passionfruit; // out: passionfruit 143 | print foo.peach; // out: peach 144 | print foo.pear; // out: pear 145 | print foo.persimmon; // out: persimmon 146 | print foo.physalis; // out: physalis 147 | print foo.pineapple; // out: pineapple 148 | print foo.plantain; // out: plantain 149 | print foo.plum; // out: plum 150 | print foo.plumcot; // out: plumcot 151 | print foo.pomegranate; // out: pomegranate 152 | print foo.pomelo; // out: pomelo 153 | print foo.quince; // out: quince 154 | print foo.raisin; // out: raisin 155 | print foo.rambutan; // out: rambutan 156 | print foo.raspberry; // out: raspberry 157 | print foo.redcurrant; // out: redcurrant 158 | print foo.salak; // out: salak 159 | print foo.salmonberry; // out: salmonberry 160 | print foo.satsuma; // out: satsuma 161 | print foo.strawberry; // out: strawberry 162 | print foo.tamarillo; // out: tamarillo 163 | print foo.tamarind; // out: tamarind 164 | print foo.tangerine; // out: tangerine 165 | print foo.tomato; // out: tomato 166 | print foo.watermelon; // out: watermelon 167 | print foo.yuzu; // out: yuzu 168 | } 169 | 170 | printFields(); 171 | -------------------------------------------------------------------------------- /src/vm/gc.rs: -------------------------------------------------------------------------------- 1 | use std::hash::BuildHasherDefault; 2 | use std::mem; 3 | 4 | use hashbrown::HashMap; 5 | use hashbrown::hash_map::RawEntryMut; 6 | use rustc_hash::FxHasher; 7 | 8 | use crate::vm::object::{Object, ObjectString, ObjectType}; 9 | use crate::vm::value::Value; 10 | 11 | #[derive(Debug, Default)] 12 | pub struct Gc { 13 | strings: HashMap>, 14 | objects: Vec, 15 | gray_objects: Vec, 16 | } 17 | 18 | impl Gc { 19 | pub fn alloc(&mut self, object: impl GcAlloc) -> T { 20 | object.alloc(self) 21 | } 22 | 23 | pub fn mark(&mut self, object: impl GcMark) { 24 | object.mark(self); 25 | } 26 | 27 | pub fn trace(&mut self) { 28 | while let Some(object) = self.gray_objects.pop() { 29 | if cfg!(feature = "gc-trace") { 30 | eprintln!("blacken {}: {object}", object.type_()); 31 | } 32 | match unsafe { (*object.common).type_ } { 33 | ObjectType::BoundMethod => { 34 | let method = unsafe { object.bound_method }; 35 | self.mark(unsafe { (*method).this }); 36 | self.mark(unsafe { (*method).closure }); 37 | } 38 | ObjectType::Class => { 39 | let class = unsafe { object.class }; 40 | self.mark(unsafe { (*class).name }); 41 | for (&name, &method) in unsafe { &(*class).methods } { 42 | self.mark(name); 43 | self.mark(method); 44 | } 45 | } 46 | ObjectType::Closure => { 47 | let closure = unsafe { object.closure }; 48 | self.mark(unsafe { (*closure).function }); 49 | for &upvalue in unsafe { &(*closure).upvalues } { 50 | self.mark(upvalue); 51 | } 52 | } 53 | ObjectType::Function => { 54 | let function = unsafe { object.function }; 55 | self.mark(unsafe { (*function).name }); 56 | for constant in unsafe { &(*function).chunk.constants } { 57 | if constant.is_object() { 58 | self.mark(constant.as_object()); 59 | } 60 | } 61 | } 62 | ObjectType::Instance => { 63 | self.mark(unsafe { (*object.instance).class }); 64 | for (&name, &value) in unsafe { (*object.instance).fields.iter() } { 65 | self.mark(name); 66 | self.mark(value); 67 | } 68 | } 69 | ObjectType::Native => {} 70 | ObjectType::String => {} 71 | ObjectType::Upvalue => { 72 | let upvalue = unsafe { object.upvalue }; 73 | self.mark(unsafe { (*upvalue).closed }); 74 | } 75 | } 76 | } 77 | } 78 | 79 | pub fn sweep(&mut self) { 80 | for idx in (0..self.objects.len()).rev() { 81 | let object = *unsafe { self.objects.get_unchecked(idx) }; 82 | if !mem::take(unsafe { &mut (*object.common).is_marked }) { 83 | self.objects.swap_remove(idx); 84 | object.free(); 85 | } 86 | } 87 | 88 | self.strings.retain(|_, &mut string| { 89 | if mem::take(unsafe { &mut (*string).common.is_marked }) { 90 | true 91 | } else { 92 | let _ = unsafe { Box::from_raw(string) }; 93 | false 94 | } 95 | }); 96 | } 97 | } 98 | 99 | impl Drop for Gc { 100 | fn drop(&mut self) { 101 | for object in &self.objects { 102 | object.free(); 103 | } 104 | for &string in self.strings.values() { 105 | let _ = unsafe { Box::from_raw(string) }; 106 | } 107 | } 108 | } 109 | 110 | pub trait GcAlloc { 111 | fn alloc(self, gc: &mut Gc) -> T; 112 | } 113 | 114 | impl GcAlloc<*mut T> for T 115 | where 116 | *mut T: Into, 117 | { 118 | fn alloc(self, gc: &mut Gc) -> *mut T { 119 | let object_ptr = Box::into_raw(Box::new(self)); 120 | let object = object_ptr.into(); 121 | 122 | if cfg!(feature = "gc-trace") { 123 | eprintln!("allocate {}: {object}", object.type_()); 124 | } 125 | 126 | gc.objects.push(object); 127 | object_ptr 128 | } 129 | } 130 | 131 | impl GcAlloc<*mut ObjectString> for S 132 | where 133 | S: AsRef + Into, 134 | { 135 | fn alloc(self, gc: &mut Gc) -> *mut ObjectString { 136 | match gc.strings.raw_entry_mut().from_key(self.as_ref()) { 137 | RawEntryMut::Occupied(entry) => *entry.get(), 138 | RawEntryMut::Vacant(entry) => { 139 | let string = self.into(); 140 | if cfg!(feature = "gc-trace") { 141 | eprintln!("allocate string: {string}"); 142 | } 143 | let object = Box::into_raw(Box::new(ObjectString::new(unsafe { 144 | mem::transmute::<&str, &str>(string.as_str()) 145 | }))); 146 | entry.insert(string, object); 147 | object 148 | } 149 | } 150 | } 151 | } 152 | 153 | pub trait GcMark { 154 | fn mark(self, gc: &mut Gc); 155 | } 156 | 157 | impl GcMark for Value { 158 | fn mark(self, gc: &mut Gc) { 159 | if self.is_object() { 160 | self.as_object().mark(gc); 161 | } 162 | } 163 | } 164 | 165 | impl> GcMark for T { 166 | fn mark(self, gc: &mut Gc) { 167 | let object = self.into(); 168 | if !unsafe { (*object.common).is_marked } { 169 | if cfg!(feature = "gc-trace") { 170 | eprintln!("mark {}: {object}", object.type_()); 171 | } 172 | unsafe { (*object.common).is_marked = true }; 173 | gc.gray_objects.push(object); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /playground/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import AceEditor from "react-ace"; 5 | import { 6 | ResizableHandle, 7 | ResizablePanel, 8 | ResizablePanelGroup, 9 | } from "@/components/ui/resizable"; 10 | import { Github, Loader2, Lock, Play } from "lucide-react"; 11 | import Link from "next/link"; 12 | import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; 13 | import "ace-builds/src-noconflict/theme-tomorrow_night_bright"; 14 | import dynamic from "next/dynamic"; 15 | import React from "react"; 16 | import { create } from "zustand"; 17 | import { persist } from "zustand/middleware"; 18 | 19 | type LoxOutMessageOutput = { 20 | type: "Output"; 21 | text: string; 22 | }; 23 | 24 | type LoxOutMessageExitFailure = { 25 | type: "ExitFailure"; 26 | }; 27 | 28 | type LoxOutMessageExitSuccess = { 29 | type: "ExitSuccess"; 30 | }; 31 | 32 | type LoxOutMessage = 33 | | LoxOutMessageOutput 34 | | LoxOutMessageExitFailure 35 | | LoxOutMessageExitSuccess; 36 | 37 | type State = { 38 | editorText: string; 39 | outputText: string; 40 | worker?: Worker; 41 | workerStartTime: number; 42 | }; 43 | 44 | type Action = { 45 | setEditorText: (text: string) => void; 46 | startVM: () => void; 47 | terminateVM: () => void; 48 | isVMRunning: () => boolean; 49 | }; 50 | 51 | const useStore = create()( 52 | persist( 53 | (set, get) => ({ 54 | editorText: "", 55 | outputText: "", 56 | worker: null, 57 | workerStartTime: 0, 58 | 59 | setEditorText: (text: string) => { 60 | set({ editorText: text }); 61 | }, 62 | 63 | startVM: () => { 64 | const worker = new Worker(new URL("worker.ts", import.meta.url), { 65 | type: "module", 66 | }); 67 | worker.onmessage = (event) => { 68 | const msg = JSON.parse(event.data) as LoxOutMessage; 69 | switch (msg.type) { 70 | case "Output": { 71 | set((state) => ({ outputText: state.outputText + msg.text })); 72 | break; 73 | } 74 | case "ExitSuccess": { 75 | set((state) => { 76 | const elapsedTime = (Date.now() - state.workerStartTime) / 1000; 77 | const outputText = `${state.outputText}---\nProgram exited successfully (${elapsedTime}s).\n`; 78 | 79 | state.worker?.terminate(); 80 | 81 | return { 82 | outputText: outputText, 83 | worker: null, 84 | workerStartTime: 0, 85 | }; 86 | }); 87 | break; 88 | } 89 | case "ExitFailure": { 90 | set((state) => { 91 | const elapsedTime = (Date.now() - state.workerStartTime) / 1000; 92 | const outputText = `${state.outputText}---\nProgram exited with errors (${elapsedTime}s).\n`; 93 | 94 | state.worker?.terminate(); 95 | 96 | return { 97 | outputText: outputText, 98 | worker: null, 99 | workerStartTime: 0, 100 | }; 101 | }); 102 | break; 103 | } 104 | } 105 | }; 106 | 107 | set({ 108 | outputText: "", 109 | worker: worker, 110 | workerStartTime: Date.now(), 111 | }); 112 | worker.postMessage(get().editorText); 113 | }, 114 | 115 | terminateVM: () => { 116 | set((state) => { 117 | const elapsedTime = (Date.now() - state.workerStartTime) / 1000; 118 | const outputText = `${state.outputText}---\nProgram exited terminated (${elapsedTime}s).\n`; 119 | return { 120 | outputText: outputText, 121 | worker: null, 122 | workerStartTime: 0, 123 | }; 124 | }); 125 | }, 126 | 127 | isVMRunning: () => get().worker !== null, 128 | }), 129 | { 130 | name: "loxcraft", 131 | partialize: (state) => ({ 132 | editorText: state.editorText, 133 | outputText: state.outputText, 134 | }), 135 | }, 136 | ), 137 | ); 138 | 139 | function Page() { 140 | const { 141 | editorText, 142 | outputText, 143 | setEditorText, 144 | startVM, 145 | terminateVM, 146 | isVMRunning, 147 | } = useStore(); 148 | const isRunning = isVMRunning(); 149 | 150 | return ( 151 |
152 | 190 | 191 | 196 | 197 | 212 | 213 | 214 | 215 | 216 |
220 | 221 |
222 |
223 |
224 |
225 | ); 226 | } 227 | 228 | export default dynamic(() => Promise.resolve(Page), { 229 | ssr: false, 230 | }); 231 | -------------------------------------------------------------------------------- /src/vm/value.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Debug, Display, Formatter}; 2 | use std::mem; 3 | use std::ops::Not; 4 | 5 | use crate::vm::object::{Object, ObjectType}; 6 | use crate::vm::util; 7 | 8 | const _: () = assert!(mem::size_of::() == 8); 9 | 10 | #[derive(Clone, Copy, Eq, PartialEq)] 11 | pub struct Value(u64); 12 | 13 | impl Default for Value { 14 | fn default() -> Self { 15 | Self::NIL 16 | } 17 | } 18 | 19 | impl Debug for Value { 20 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 21 | writeln!(f, "{self}") 22 | } 23 | } 24 | 25 | impl Display for Value { 26 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 27 | match *self { 28 | Self::NIL => write!(f, "nil"), 29 | Self::TRUE => write!(f, "true"), 30 | Self::FALSE => write!(f, "false"), 31 | _ if self.is_number() => write!(f, "{}", self.as_number()), 32 | _ if self.is_object() => write!(f, "{}", self.as_object()), 33 | _ => util::unreachable(), 34 | } 35 | } 36 | } 37 | 38 | impl From for Value { 39 | fn from(value: bool) -> Self { 40 | Self(value as u64 | Self::FALSE.0) 41 | } 42 | } 43 | 44 | impl From for Value { 45 | fn from(value: f64) -> Self { 46 | Value(value.to_bits()) 47 | } 48 | } 49 | 50 | impl> From for Value { 51 | fn from(object: O) -> Self { 52 | Self((unsafe { object.into().common } as u64) | Self::SIGN_BIT | Self::QNAN) 53 | } 54 | } 55 | 56 | impl Not for Value { 57 | type Output = Self; 58 | 59 | fn not(self) -> Self::Output { 60 | Self::from(!self.to_bool()) 61 | } 62 | } 63 | 64 | impl Value { 65 | const SIGN_BIT: u64 = 0x8000000000000000; 66 | const QNAN: u64 = 0x7ffc000000000000; 67 | 68 | pub const NIL: Self = Self(Self::QNAN | 0b01); 69 | pub const FALSE: Self = Self(Self::QNAN | 0b10); 70 | pub const TRUE: Self = Self(Self::QNAN | 0b11); 71 | 72 | pub fn type_(self) -> ValueType { 73 | if self.is_nil() { 74 | ValueType::Nil 75 | } else if self.is_bool() { 76 | ValueType::Bool 77 | } else if self.is_number() { 78 | ValueType::Number 79 | } else if self.is_object() { 80 | ValueType::Object(self.as_object().type_()) 81 | } else { 82 | util::unreachable() 83 | } 84 | } 85 | 86 | pub fn is_nil(self) -> bool { 87 | self == Self::NIL 88 | } 89 | 90 | pub fn is_bool(self) -> bool { 91 | Self(self.0 | 0b01) == Self::TRUE 92 | } 93 | 94 | pub const fn is_number(self) -> bool { 95 | (self.0 & Self::QNAN) != Self::QNAN 96 | } 97 | 98 | pub const fn is_object(self) -> bool { 99 | self.0 & (Self::QNAN | Self::SIGN_BIT) == (Self::QNAN | Self::SIGN_BIT) 100 | } 101 | 102 | /// # Safety 103 | /// This is undefined behavior if the [`Value`] is not of type [`ValueType::Number`]. 104 | pub fn as_number(self) -> f64 { 105 | f64::from_bits(self.0) 106 | } 107 | 108 | /// # Safety 109 | /// This is undefined behavior if the [`Value`] is not of type [`ValueType::Object`]. 110 | pub const fn as_object(self) -> Object { 111 | Object { common: (self.0 & !(Self::SIGN_BIT | Self::QNAN)) as _ } 112 | } 113 | 114 | pub const fn to_bool(self) -> bool { 115 | !matches!(self, Self::FALSE | Self::NIL) 116 | } 117 | } 118 | 119 | #[derive(Debug, Eq, PartialEq)] 120 | pub enum ValueType { 121 | Nil, 122 | Bool, 123 | Number, 124 | Object(ObjectType), 125 | } 126 | 127 | impl Display for ValueType { 128 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 129 | match self { 130 | Self::Nil => write!(f, "nil"), 131 | Self::Bool => write!(f, "bool"), 132 | Self::Number => write!(f, "number"), 133 | Self::Object(type_) => write!(f, "{type_}"), 134 | } 135 | } 136 | } 137 | 138 | impl From for ValueType { 139 | fn from(type_: ObjectType) -> Self { 140 | Self::Object(type_) 141 | } 142 | } 143 | 144 | #[cfg(test)] 145 | mod tests { 146 | use std::ptr; 147 | 148 | use super::*; 149 | use crate::vm::object::ObjectCommon; 150 | 151 | #[test] 152 | fn convert_to_and_from_values() { 153 | let value = 0.0; 154 | assert_eq!(Value::from(value).as_number(), value); 155 | 156 | let value = f64::NAN; 157 | assert!(Value::from(value).as_number().is_nan()); 158 | 159 | let value = Object { common: ptr::null_mut() }; 160 | assert_eq!(Value::from(value).as_object(), value); 161 | } 162 | 163 | #[test] 164 | fn value_is_nil() { 165 | assert!(Value::NIL.is_nil()); 166 | assert!(!Value::from(false).is_nil()); 167 | assert!(!Value::from(true).is_nil()); 168 | assert!(!Value::from(0.0).is_nil()); 169 | assert!(!Value::from(f64::NAN).is_nil()); 170 | assert!(!Value::from(Object { common: ptr::null_mut() }).is_nil()); 171 | } 172 | 173 | #[test] 174 | fn value_is_bool() { 175 | assert!(!Value::NIL.is_bool()); 176 | assert!(Value::from(false).is_bool()); 177 | assert!(Value::from(true).is_bool()); 178 | assert!(!Value::from(0.0).is_bool()); 179 | assert!(!Value::from(f64::NAN).is_bool()); 180 | assert!(!Value::from(Object { common: ptr::null_mut() }).is_bool()); 181 | } 182 | 183 | #[test] 184 | fn value_is_number() { 185 | assert!(!Value::NIL.is_number()); 186 | assert!(!Value::from(false).is_number()); 187 | assert!(!Value::from(true).is_number()); 188 | assert!(Value::from(0.0).is_number()); 189 | assert!(Value::from(f64::NAN).is_number()); 190 | assert!(!Value::from(Object { common: ptr::null_mut() }).is_number()); 191 | } 192 | 193 | #[test] 194 | fn value_is_object() { 195 | assert!(!Value::NIL.is_object()); 196 | assert!(!Value::from(false).is_object()); 197 | assert!(!Value::from(true).is_object()); 198 | assert!(!Value::from(0.0).is_object()); 199 | assert!(!Value::from(f64::NAN).is_object()); 200 | assert!(Value::from(Object { common: ptr::null_mut() }).is_object()); 201 | } 202 | 203 | #[test] 204 | fn value_to_bool() { 205 | // Falsey 206 | assert!(!Value::NIL.to_bool()); 207 | assert!(!Value::FALSE.to_bool()); 208 | 209 | // Truthy 210 | assert!(Value::TRUE.to_bool()); 211 | assert!(Value::from(0.0).to_bool()); 212 | assert!(Value::from(f64::NAN).to_bool()); 213 | assert!(Value::from(Object { common: ptr::null_mut() }).to_bool()); 214 | } 215 | 216 | #[test] 217 | fn value_type() { 218 | assert_eq!(Value::NIL.type_(), ValueType::Nil); 219 | assert_eq!(Value::FALSE.type_(), ValueType::Bool); 220 | assert_eq!(Value::TRUE.type_(), ValueType::Bool); 221 | assert_eq!(Value::from(0.0).type_(), ValueType::Number); 222 | assert_eq!(Value::from(f64::NAN).type_(), ValueType::Number); 223 | assert_eq!( 224 | Value::from( 225 | &mut ObjectCommon { type_: ObjectType::String, is_marked: false } as *mut _ 226 | ) 227 | .type_(), 228 | ValueType::Object(ObjectType::String) 229 | ); 230 | } 231 | } 232 | --------------------------------------------------------------------------------