├── .ocamlformat ├── test ├── test_files │ ├── block │ │ ├── empty.expected │ │ ├── scope.expected │ │ ├── empty.lox │ │ └── scope.lox │ ├── comments │ │ ├── unicode.expected │ │ ├── line_at_eof.expected │ │ ├── only_line_comment.expected │ │ ├── only_line_comment.lox │ │ ├── only_line_comment_and_line.expected │ │ ├── only_line_comment_and_line.lox │ │ ├── line_at_eof.lox │ │ └── unicode.lox │ ├── for │ │ ├── fun_in_body.expected │ │ ├── return_inside.expected │ │ ├── var_in_body.expected │ │ ├── return_closure.expected │ │ ├── statement_condition.expected │ │ ├── statement_increment.expected │ │ ├── statement_initializer.expected │ │ ├── scope.expected │ │ ├── closure_in_body.expected │ │ ├── var_in_body.lox │ │ ├── fun_in_body.lox │ │ ├── statement_increment.lox │ │ ├── syntax.expected │ │ ├── return_inside.lox │ │ ├── statement_initializer.lox │ │ ├── statement_condition.lox │ │ ├── return_closure.lox │ │ ├── closure_in_body.lox │ │ ├── scope.lox │ │ └── syntax.lox │ ├── if │ │ ├── fun_in_else.expected │ │ ├── fun_in_then.expected │ │ ├── var_in_else.expected │ │ ├── var_in_then.expected │ │ ├── dangling_else.expected │ │ ├── if.expected │ │ ├── else.expected │ │ ├── truth.expected │ │ ├── var_in_then.lox │ │ ├── fun_in_then.lox │ │ ├── fun_in_else.lox │ │ ├── var_in_else.lox │ │ ├── dangling_else.lox │ │ ├── else.lox │ │ ├── truth.lox │ │ └── if.lox │ ├── nil │ │ ├── literal.expected │ │ └── literal.lox │ ├── number │ │ ├── leading_dot.expected │ │ ├── trailing_dot.expected │ │ ├── decimal_point_at_eof.expected │ │ ├── nan_equality.expected │ │ ├── literals.expected │ │ ├── leading_dot.lox │ │ ├── decimal_point_at_eof.lox │ │ ├── trailing_dot.lox │ │ ├── literals.lox │ │ └── nan_equality.lox │ ├── return │ │ ├── after_if.expected │ │ ├── after_else.expected │ │ ├── after_while.expected │ │ ├── at_top_level.expected │ │ ├── in_function.expected │ │ ├── return_nil_if_no_value.expected │ │ ├── after_if.lox │ │ ├── at_top_level.lox │ │ ├── after_while.lox │ │ ├── after_else.lox │ │ ├── in_function.lox │ │ └── return_nil_if_no_value.lox │ ├── while │ │ ├── fun_in_body.expected │ │ ├── var_in_body.expected │ │ ├── return_closure.expected │ │ ├── return_inside.expected │ │ ├── closure_in_body.expected │ │ ├── syntax.expected │ │ ├── fun_in_body.lox │ │ ├── var_in_body.lox │ │ ├── return_inside.lox │ │ ├── return_closure.lox │ │ ├── closure_in_body.lox │ │ └── syntax.lox │ ├── assignment │ │ ├── grouping.expected │ │ ├── undefined.expected │ │ ├── infix_operator.expected │ │ ├── prefix_operator.expected │ │ ├── syntax.expected │ │ ├── associativity.expected │ │ ├── local.expected │ │ ├── global.expected │ │ ├── undefined.lox │ │ ├── grouping.lox │ │ ├── prefix_operator.lox │ │ ├── infix_operator.lox │ │ ├── syntax.lox │ │ ├── global.lox │ │ ├── associativity.lox │ │ └── local.lox │ ├── function │ │ ├── empty_body.expected │ │ ├── recursion.expected │ │ ├── body_must_be_block.expected │ │ ├── extra_arguments.expected │ │ ├── local_recursion.expected │ │ ├── missing_arguments.expected │ │ ├── print.expected │ │ ├── too_many_arguments.expected │ │ ├── too_many_parameters.expected │ │ ├── local_mutual_recursion.expected │ │ ├── missing_comma_in_parameters.expected │ │ ├── mutual_recursion.expected │ │ ├── empty_body.lox │ │ ├── print.lox │ │ ├── parameters.expected │ │ ├── missing_arguments.lox │ │ ├── recursion.lox │ │ ├── extra_arguments.lox │ │ ├── body_must_be_block.lox │ │ ├── local_recursion.lox │ │ ├── missing_comma_in_parameters.lox │ │ ├── mutual_recursion.lox │ │ ├── local_mutual_recursion.lox │ │ ├── parameters.lox │ │ ├── too_many_parameters.lox │ │ └── too_many_arguments.lox │ ├── operator │ │ ├── add_bool_nil.expected │ │ ├── add_bool_num.expected │ │ ├── add_nil_nil.expected │ │ ├── add_num_nil.expected │ │ ├── add_string_nil.expected │ │ ├── divide.expected │ │ ├── negate_nonnum.expected │ │ ├── add.expected │ │ ├── add_bool_string.expected │ │ ├── divide_nonnum_num.expected │ │ ├── divide_num_nonnum.expected │ │ ├── greater_nonnum_num.expected │ │ ├── greater_num_nonnum.expected │ │ ├── less_nonnum_num.expected │ │ ├── less_num_nonnum.expected │ │ ├── multiply_nonnum_num.expected │ │ ├── multiply_num_nonnum.expected │ │ ├── subtract.expected │ │ ├── subtract_nonnum_num.expected │ │ ├── subtract_num_nonnum.expected │ │ ├── less_or_equal_nonnum_num.expected │ │ ├── less_or_equal_num_nonnum.expected │ │ ├── multiply.expected │ │ ├── negate.expected │ │ ├── greater_or_equal_nonnum_num.expected │ │ ├── greater_or_equal_num_nonnum.expected │ │ ├── negate_nonnum.lox │ │ ├── subtract.lox │ │ ├── less_nonnum_num.lox │ │ ├── less_num_nonnum.lox │ │ ├── multiply.lox │ │ ├── add.lox │ │ ├── divide.lox │ │ ├── divide_nonnum_num.lox │ │ ├── divide_num_nonnum.lox │ │ ├── greater_nonnum_num.lox │ │ ├── greater_num_nonnum.lox │ │ ├── multiply_nonnum_num.lox │ │ ├── multiply_num_nonnum.lox │ │ ├── not.expected │ │ ├── subtract_nonnum_num.lox │ │ ├── subtract_num_nonnum.lox │ │ ├── less_or_equal_nonnum_num.lox │ │ ├── less_or_equal_num_nonnum.lox │ │ ├── add_num_nil.lox │ │ ├── greater_or_equal_nonnum_num.lox │ │ ├── greater_or_equal_num_nonnum.lox │ │ ├── add_bool_nil.lox │ │ ├── add_bool_num.lox │ │ ├── add_nil_nil.lox │ │ ├── add_string_nil.lox │ │ ├── negate.lox │ │ ├── add_bool_string.lox │ │ ├── equals.expected │ │ ├── not_equals.expected │ │ ├── comparison.expected │ │ ├── not.lox │ │ ├── equals.lox │ │ ├── not_equals.lox │ │ └── comparison.lox │ ├── print │ │ ├── missing_argument.expected │ │ └── missing_argument.lox │ ├── string │ │ ├── unterminated.expected │ │ ├── error_after_multiline.expected │ │ ├── multiline.expected │ │ ├── literals.expected │ │ ├── unterminated.lox │ │ ├── multiline.lox │ │ ├── literals.lox │ │ └── error_after_multiline.lox │ ├── variable │ │ ├── use_nil_as_var.expected │ │ ├── duplicate_local.expected │ │ ├── duplicate_parameter.expected │ │ ├── redeclare_global.expected │ │ ├── redefine_global.expected │ │ ├── undefined_global.expected │ │ ├── undefined_local.expected │ │ ├── uninitialized.expected │ │ ├── use_false_as_var.expected │ │ ├── use_this_as_var.expected │ │ ├── collide_with_parameter.expected │ │ ├── in_nested_block.expected │ │ ├── unreached_undefined.expected │ │ ├── use_local_in_initializer.expected │ │ ├── early_bound.expected │ │ ├── shadow_local.expected │ │ ├── shadow_and_local.expected │ │ ├── shadow_global.expected │ │ ├── use_global_in_initializer.expected │ │ ├── in_middle_of_block.expected │ │ ├── scope_reuse_in_different_blocks.expected │ │ ├── uninitialized.lox │ │ ├── redeclare_global.lox │ │ ├── redefine_global.lox │ │ ├── use_global_in_initializer.lox │ │ ├── use_nil_as_var.lox │ │ ├── use_this_as_var.lox │ │ ├── in_nested_block.lox │ │ ├── undefined_global.lox │ │ ├── use_false_as_var.lox │ │ ├── unreached_undefined.lox │ │ ├── undefined_local.lox │ │ ├── collide_with_parameter.lox │ │ ├── shadow_global.lox │ │ ├── use_local_in_initializer.lox │ │ ├── duplicate_local.lox │ │ ├── duplicate_parameter.lox │ │ ├── shadow_and_local.lox │ │ ├── shadow_local.lox │ │ ├── scope_reuse_in_different_blocks.lox │ │ ├── early_bound.lox │ │ └── in_middle_of_block.lox │ ├── closure │ │ ├── reuse_closure_slot.expected │ │ ├── unused_closure.expected │ │ ├── unused_later_closure.expected │ │ ├── nested_closure.expected │ │ ├── open_closure_in_function.expected │ │ ├── close_over_function_parameter.expected │ │ ├── close_over_later_variable.expected │ │ ├── closed_closure_in_function.expected │ │ ├── reference_closure_multiple_times.expected │ │ ├── assign_to_shadowed_later.expected │ │ ├── shadow_closure_with_local.expected │ │ ├── assign_to_closure.expected │ │ ├── open_closure_in_function.lox │ │ ├── closed_closure_in_function.lox │ │ ├── close_over_function_parameter.lox │ │ ├── reference_closure_multiple_times.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 │ ├── _other │ │ ├── test_fibonacci.expected │ │ ├── test_simple_closure.expected │ │ ├── test_globals.expected │ │ ├── test_closure_globals.expected │ │ ├── test_closure_globals.lox │ │ ├── test_globals.lox │ │ ├── test_fibonacci.lox │ │ └── test_simple_closure.lox │ ├── bool │ │ ├── not.expected │ │ ├── not.lox │ │ ├── equality.expected │ │ └── equality.lox │ ├── logical_operator │ │ ├── or_truth.expected │ │ ├── and_truth.expected │ │ ├── and.expected │ │ ├── or.expected │ │ ├── or_truth.lox │ │ ├── and_truth.lox │ │ ├── and.lox │ │ └── or.lox │ └── make_expected.py ├── dune └── test.ml ├── .gitignore ├── lib ├── dune ├── .ocamlinit ├── lox.ml ├── value.ml ├── loxError.ml ├── environment.ml ├── scanner.ml ├── resolver.ml ├── interpreter.ml └── parser.ml ├── dune-project ├── .merlin ├── bin ├── dune └── main.ml ├── lox.opam ├── LICENSE └── README.md /.ocamlformat: -------------------------------------------------------------------------------- 1 | profile=janestreet 2 | -------------------------------------------------------------------------------- /test/test_files/block/empty.expected: -------------------------------------------------------------------------------- 1 | ok -------------------------------------------------------------------------------- /test/test_files/comments/unicode.expected: -------------------------------------------------------------------------------- 1 | ok -------------------------------------------------------------------------------- /test/test_files/for/fun_in_body.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/for/return_inside.expected: -------------------------------------------------------------------------------- 1 | i -------------------------------------------------------------------------------- /test/test_files/for/var_in_body.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/if/fun_in_else.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/if/fun_in_then.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/if/var_in_else.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/if/var_in_then.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/nil/literal.expected: -------------------------------------------------------------------------------- 1 | nil -------------------------------------------------------------------------------- /test/test_files/number/leading_dot.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/return/after_if.expected: -------------------------------------------------------------------------------- 1 | ok -------------------------------------------------------------------------------- /test/test_files/while/fun_in_body.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/while/var_in_body.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/assignment/grouping.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/assignment/undefined.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/comments/line_at_eof.expected: -------------------------------------------------------------------------------- 1 | ok -------------------------------------------------------------------------------- /test/test_files/for/return_closure.expected: -------------------------------------------------------------------------------- 1 | i -------------------------------------------------------------------------------- /test/test_files/for/statement_condition.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/for/statement_increment.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/function/empty_body.expected: -------------------------------------------------------------------------------- 1 | nil -------------------------------------------------------------------------------- /test/test_files/function/recursion.expected: -------------------------------------------------------------------------------- 1 | 21 -------------------------------------------------------------------------------- /test/test_files/if/dangling_else.expected: -------------------------------------------------------------------------------- 1 | good -------------------------------------------------------------------------------- /test/test_files/number/trailing_dot.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/add_bool_nil.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/add_bool_num.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/add_nil_nil.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/add_num_nil.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/add_string_nil.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/divide.expected: -------------------------------------------------------------------------------- 1 | 4 2 | 1 -------------------------------------------------------------------------------- /test/test_files/operator/negate_nonnum.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/print/missing_argument.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/return/after_else.expected: -------------------------------------------------------------------------------- 1 | ok -------------------------------------------------------------------------------- /test/test_files/return/after_while.expected: -------------------------------------------------------------------------------- 1 | ok -------------------------------------------------------------------------------- /test/test_files/return/at_top_level.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/return/in_function.expected: -------------------------------------------------------------------------------- 1 | ok -------------------------------------------------------------------------------- /test/test_files/string/unterminated.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/variable/use_nil_as_var.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/while/return_closure.expected: -------------------------------------------------------------------------------- 1 | i -------------------------------------------------------------------------------- /test/test_files/while/return_inside.expected: -------------------------------------------------------------------------------- 1 | i -------------------------------------------------------------------------------- /test/test_files/assignment/infix_operator.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/assignment/prefix_operator.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/assignment/syntax.expected: -------------------------------------------------------------------------------- 1 | var 2 | var -------------------------------------------------------------------------------- /test/test_files/block/scope.expected: -------------------------------------------------------------------------------- 1 | inner 2 | outer -------------------------------------------------------------------------------- /test/test_files/closure/reuse_closure_slot.expected: -------------------------------------------------------------------------------- 1 | a -------------------------------------------------------------------------------- /test/test_files/closure/unused_closure.expected: -------------------------------------------------------------------------------- 1 | ok -------------------------------------------------------------------------------- /test/test_files/comments/only_line_comment.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/for/statement_initializer.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/function/body_must_be_block.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/function/extra_arguments.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/function/local_recursion.expected: -------------------------------------------------------------------------------- 1 | 21 -------------------------------------------------------------------------------- /test/test_files/function/missing_arguments.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/function/print.expected: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/test_files/function/too_many_arguments.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/function/too_many_parameters.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/if/if.expected: -------------------------------------------------------------------------------- 1 | good 2 | block 3 | true -------------------------------------------------------------------------------- /test/test_files/number/decimal_point_at_eof.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/add.expected: -------------------------------------------------------------------------------- 1 | 579 2 | string -------------------------------------------------------------------------------- /test/test_files/operator/add_bool_string.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/divide_nonnum_num.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/divide_num_nonnum.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/greater_nonnum_num.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/greater_num_nonnum.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/less_nonnum_num.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/less_num_nonnum.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/multiply_nonnum_num.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/multiply_num_nonnum.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/subtract.expected: -------------------------------------------------------------------------------- 1 | 1 2 | 0 -------------------------------------------------------------------------------- /test/test_files/operator/subtract_nonnum_num.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/subtract_num_nonnum.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/string/error_after_multiline.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/variable/duplicate_local.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/variable/duplicate_parameter.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/variable/redeclare_global.expected: -------------------------------------------------------------------------------- 1 | nil -------------------------------------------------------------------------------- /test/test_files/variable/redefine_global.expected: -------------------------------------------------------------------------------- 1 | 2 -------------------------------------------------------------------------------- /test/test_files/variable/undefined_global.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/variable/undefined_local.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/variable/uninitialized.expected: -------------------------------------------------------------------------------- 1 | nil -------------------------------------------------------------------------------- /test/test_files/variable/use_false_as_var.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/variable/use_this_as_var.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/_other/test_fibonacci.expected: -------------------------------------------------------------------------------- 1 | 610 2 | -------------------------------------------------------------------------------- /test/test_files/bool/not.expected: -------------------------------------------------------------------------------- 1 | false 2 | true 3 | true -------------------------------------------------------------------------------- /test/test_files/closure/unused_later_closure.expected: -------------------------------------------------------------------------------- 1 | a -------------------------------------------------------------------------------- /test/test_files/comments/only_line_comment.lox: -------------------------------------------------------------------------------- 1 | // comment -------------------------------------------------------------------------------- /test/test_files/for/scope.expected: -------------------------------------------------------------------------------- 1 | 0 2 | -1 3 | after 4 | 0 -------------------------------------------------------------------------------- /test/test_files/function/local_mutual_recursion.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/if/else.expected: -------------------------------------------------------------------------------- 1 | good 2 | good 3 | block -------------------------------------------------------------------------------- /test/test_files/operator/less_or_equal_nonnum_num.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/less_or_equal_num_nonnum.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/multiply.expected: -------------------------------------------------------------------------------- 1 | 15 2 | 3.702 -------------------------------------------------------------------------------- /test/test_files/operator/negate.expected: -------------------------------------------------------------------------------- 1 | -3 2 | 3 3 | -3 -------------------------------------------------------------------------------- /test/test_files/return/return_nil_if_no_value.expected: -------------------------------------------------------------------------------- 1 | nil -------------------------------------------------------------------------------- /test/test_files/string/multiline.expected: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 -------------------------------------------------------------------------------- /test/test_files/variable/collide_with_parameter.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/variable/in_nested_block.expected: -------------------------------------------------------------------------------- 1 | outer -------------------------------------------------------------------------------- /test/test_files/variable/unreached_undefined.expected: -------------------------------------------------------------------------------- 1 | ok -------------------------------------------------------------------------------- /test/test_files/variable/use_local_in_initializer.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/_other/test_simple_closure.expected: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | -------------------------------------------------------------------------------- /test/test_files/assignment/associativity.expected: -------------------------------------------------------------------------------- 1 | c 2 | c 3 | c -------------------------------------------------------------------------------- /test/test_files/closure/nested_closure.expected: -------------------------------------------------------------------------------- 1 | a 2 | b 3 | c -------------------------------------------------------------------------------- /test/test_files/closure/open_closure_in_function.expected: -------------------------------------------------------------------------------- 1 | local -------------------------------------------------------------------------------- /test/test_files/comments/only_line_comment_and_line.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/function/missing_comma_in_parameters.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/function/mutual_recursion.expected: -------------------------------------------------------------------------------- 1 | true 2 | true -------------------------------------------------------------------------------- /test/test_files/nil/literal.lox: -------------------------------------------------------------------------------- 1 | print nil; // expect: nil 2 | -------------------------------------------------------------------------------- /test/test_files/operator/greater_or_equal_nonnum_num.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/operator/greater_or_equal_num_nonnum.expected: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/test_files/variable/early_bound.expected: -------------------------------------------------------------------------------- 1 | outer 2 | outer -------------------------------------------------------------------------------- /test/test_files/variable/shadow_local.expected: -------------------------------------------------------------------------------- 1 | shadow 2 | local -------------------------------------------------------------------------------- /test/test_files/while/closure_in_body.expected: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 -------------------------------------------------------------------------------- /test/test_files/closure/close_over_function_parameter.expected: -------------------------------------------------------------------------------- 1 | param -------------------------------------------------------------------------------- /test/test_files/closure/close_over_later_variable.expected: -------------------------------------------------------------------------------- 1 | b 2 | a -------------------------------------------------------------------------------- /test/test_files/closure/closed_closure_in_function.expected: -------------------------------------------------------------------------------- 1 | local -------------------------------------------------------------------------------- /test/test_files/string/literals.expected: -------------------------------------------------------------------------------- 1 | () 2 | a string 3 | A~¶Þॐஃ -------------------------------------------------------------------------------- /test/test_files/variable/shadow_and_local.expected: -------------------------------------------------------------------------------- 1 | outer 2 | inner -------------------------------------------------------------------------------- /test/test_files/variable/shadow_global.expected: -------------------------------------------------------------------------------- 1 | shadow 2 | global -------------------------------------------------------------------------------- /test/test_files/variable/use_global_in_initializer.expected: -------------------------------------------------------------------------------- 1 | value -------------------------------------------------------------------------------- /test/test_files/while/syntax.expected: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 0 5 | 1 6 | 2 -------------------------------------------------------------------------------- /test/dune: -------------------------------------------------------------------------------- 1 | (test 2 | (name test) 3 | (libraries alcotest lox base)) 4 | -------------------------------------------------------------------------------- /test/test_files/_other/test_globals.expected: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 1 4 | nil 5 | -------------------------------------------------------------------------------- /test/test_files/assignment/local.expected: -------------------------------------------------------------------------------- 1 | before 2 | after 3 | arg 4 | arg -------------------------------------------------------------------------------- /test/test_files/closure/reference_closure_multiple_times.expected: -------------------------------------------------------------------------------- 1 | a 2 | a -------------------------------------------------------------------------------- /test/test_files/comments/only_line_comment_and_line.lox: -------------------------------------------------------------------------------- 1 | // comment 2 | -------------------------------------------------------------------------------- /test/test_files/if/truth.expected: -------------------------------------------------------------------------------- 1 | false 2 | nil 3 | true 4 | 0 5 | empty -------------------------------------------------------------------------------- /test/test_files/_other/test_closure_globals.expected: -------------------------------------------------------------------------------- 1 | global 2 | global 3 | -------------------------------------------------------------------------------- /test/test_files/assignment/global.expected: -------------------------------------------------------------------------------- 1 | before 2 | after 3 | arg 4 | arg -------------------------------------------------------------------------------- /test/test_files/closure/assign_to_shadowed_later.expected: -------------------------------------------------------------------------------- 1 | inner 2 | assigned -------------------------------------------------------------------------------- /test/test_files/for/closure_in_body.expected: -------------------------------------------------------------------------------- 1 | 4 2 | 1 3 | 4 4 | 2 5 | 4 6 | 3 -------------------------------------------------------------------------------- /test/test_files/number/nan_equality.expected: -------------------------------------------------------------------------------- 1 | false 2 | true 3 | false 4 | true -------------------------------------------------------------------------------- /test/test_files/comments/line_at_eof.lox: -------------------------------------------------------------------------------- 1 | print "ok"; // expect: ok 2 | // comment -------------------------------------------------------------------------------- /test/test_files/logical_operator/or_truth.expected: -------------------------------------------------------------------------------- 1 | ok 2 | ok 3 | true 4 | 0 5 | s -------------------------------------------------------------------------------- /test/test_files/variable/in_middle_of_block.expected: -------------------------------------------------------------------------------- 1 | a 2 | a b 3 | a c 4 | a b d -------------------------------------------------------------------------------- /test/test_files/variable/scope_reuse_in_different_blocks.expected: -------------------------------------------------------------------------------- 1 | first 2 | second -------------------------------------------------------------------------------- /test/test_files/variable/uninitialized.lox: -------------------------------------------------------------------------------- 1 | var a; 2 | print a; // expect: nil 3 | -------------------------------------------------------------------------------- /test/test_files/closure/shadow_closure_with_local.expected: -------------------------------------------------------------------------------- 1 | closure 2 | shadow 3 | closure -------------------------------------------------------------------------------- /test/test_files/function/empty_body.lox: -------------------------------------------------------------------------------- 1 | fun f() {} 2 | print f(); // expect: nil 3 | -------------------------------------------------------------------------------- /test/test_files/function/print.lox: -------------------------------------------------------------------------------- 1 | fun foo() {} 2 | print foo; // expect: 3 | -------------------------------------------------------------------------------- /test/test_files/logical_operator/and_truth.expected: -------------------------------------------------------------------------------- 1 | false 2 | nil 3 | ok 4 | ok 5 | ok -------------------------------------------------------------------------------- /test/test_files/closure/assign_to_closure.expected: -------------------------------------------------------------------------------- 1 | local 2 | after f 3 | after f 4 | after g -------------------------------------------------------------------------------- /test/test_files/number/literals.expected: -------------------------------------------------------------------------------- 1 | 123 2 | 987654 3 | 0 4 | -0 5 | 123.456 6 | -0.001 -------------------------------------------------------------------------------- /test/test_files/function/parameters.expected: -------------------------------------------------------------------------------- 1 | 0 2 | 1 3 | 3 4 | 6 5 | 10 6 | 15 7 | 21 8 | 28 9 | 36 -------------------------------------------------------------------------------- /test/test_files/number/leading_dot.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at '.': Expect expression. 2 | .123; 3 | -------------------------------------------------------------------------------- /test/test_files/logical_operator/and.expected: -------------------------------------------------------------------------------- 1 | false 2 | 1 3 | false 4 | true 5 | 3 6 | true 7 | false -------------------------------------------------------------------------------- /test/test_files/logical_operator/or.expected: -------------------------------------------------------------------------------- 1 | 1 2 | 1 3 | true 4 | false 5 | false 6 | false 7 | true -------------------------------------------------------------------------------- /test/test_files/operator/negate_nonnum.lox: -------------------------------------------------------------------------------- 1 | -"s"; // expect runtime error: Operand must be a number. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/subtract.lox: -------------------------------------------------------------------------------- 1 | print 4 - 3; // expect: 1 2 | print 1.2 - 1.2; // expect: 0 3 | -------------------------------------------------------------------------------- /test/test_files/print/missing_argument.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at ';': Expect expression. 2 | print; 3 | -------------------------------------------------------------------------------- /test/test_files/variable/redeclare_global.lox: -------------------------------------------------------------------------------- 1 | var a = "1"; 2 | var a; 3 | print a; // expect: nil 4 | -------------------------------------------------------------------------------- /test/test_files/for/var_in_body.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at 'var': Expect expression. 2 | for (;;) var foo; 3 | -------------------------------------------------------------------------------- /test/test_files/if/var_in_then.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at 'var': Expect expression. 2 | if (true) var foo; 3 | -------------------------------------------------------------------------------- /test/test_files/operator/less_nonnum_num.lox: -------------------------------------------------------------------------------- 1 | "1" < 1; // expect runtime error: Operands must be numbers. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/less_num_nonnum.lox: -------------------------------------------------------------------------------- 1 | 1 < "1"; // expect runtime error: Operands must be numbers. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/multiply.lox: -------------------------------------------------------------------------------- 1 | print 5 * 3; // expect: 15 2 | print 12.34 * 0.3; // expect: 3.702 3 | -------------------------------------------------------------------------------- /test/test_files/variable/redefine_global.lox: -------------------------------------------------------------------------------- 1 | var a = "1"; 2 | var a = "2"; 3 | print a; // expect: 2 4 | -------------------------------------------------------------------------------- /test/test_files/for/fun_in_body.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at 'fun': Expect expression. 2 | for (;;) fun foo() {} 3 | -------------------------------------------------------------------------------- /test/test_files/if/fun_in_then.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at 'fun': Expect expression. 2 | if (true) fun foo() {} 3 | -------------------------------------------------------------------------------- /test/test_files/number/decimal_point_at_eof.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at end: Expect property name after '.'. 2 | 123. -------------------------------------------------------------------------------- /test/test_files/number/trailing_dot.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at ';': Expect property name after '.'. 2 | 123.; 3 | -------------------------------------------------------------------------------- /test/test_files/operator/add.lox: -------------------------------------------------------------------------------- 1 | print 123 + 456; // expect: 579 2 | print "str" + "ing"; // expect: string 3 | -------------------------------------------------------------------------------- /test/test_files/operator/divide.lox: -------------------------------------------------------------------------------- 1 | print 8 / 2; // expect: 4 2 | print 12.34 / 12.34; // expect: 1 3 | -------------------------------------------------------------------------------- /test/test_files/operator/divide_nonnum_num.lox: -------------------------------------------------------------------------------- 1 | "1" / 1; // expect runtime error: Operands must be numbers. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/divide_num_nonnum.lox: -------------------------------------------------------------------------------- 1 | 1 / "1"; // expect runtime error: Operands must be numbers. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/greater_nonnum_num.lox: -------------------------------------------------------------------------------- 1 | "1" > 1; // expect runtime error: Operands must be numbers. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/greater_num_nonnum.lox: -------------------------------------------------------------------------------- 1 | 1 > "1"; // expect runtime error: Operands must be numbers. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/multiply_nonnum_num.lox: -------------------------------------------------------------------------------- 1 | "1" * 1; // expect runtime error: Operands must be numbers. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/multiply_num_nonnum.lox: -------------------------------------------------------------------------------- 1 | 1 * "1"; // expect runtime error: Operands must be numbers. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/not.expected: -------------------------------------------------------------------------------- 1 | false 2 | true 3 | true 4 | false 5 | false 6 | true 7 | false 8 | false -------------------------------------------------------------------------------- /test/test_files/operator/subtract_nonnum_num.lox: -------------------------------------------------------------------------------- 1 | "1" - 1; // expect runtime error: Operands must be numbers. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/subtract_num_nonnum.lox: -------------------------------------------------------------------------------- 1 | 1 - "1"; // expect runtime error: Operands must be numbers. 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | *.log 4 | tmp/ 5 | 6 | .merlin 7 | _build/ 8 | *.install 9 | _opam/ 10 | -------------------------------------------------------------------------------- /test/test_files/assignment/undefined.lox: -------------------------------------------------------------------------------- 1 | unknown = "what"; // expect runtime error: Undefined variable 'unknown'. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/less_or_equal_nonnum_num.lox: -------------------------------------------------------------------------------- 1 | "1" <= 1; // expect runtime error: Operands must be numbers. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/less_or_equal_num_nonnum.lox: -------------------------------------------------------------------------------- 1 | 1 <= "1"; // expect runtime error: Operands must be numbers. 2 | -------------------------------------------------------------------------------- /test/test_files/return/after_if.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | if (true) return "ok"; 3 | } 4 | 5 | print f(); // expect: ok 6 | -------------------------------------------------------------------------------- /test/test_files/return/at_top_level.lox: -------------------------------------------------------------------------------- 1 | return "wat"; // Error at 'return': Cannot return from top-level code. 2 | -------------------------------------------------------------------------------- /test/test_files/string/unterminated.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error: Unterminated string. 2 | "this string has no close quote -------------------------------------------------------------------------------- /test/test_files/while/fun_in_body.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at 'fun': Expect expression. 2 | while (true) fun foo() {} 3 | -------------------------------------------------------------------------------- /test/test_files/while/var_in_body.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at 'var': Expect expression. 2 | while (true) var foo; 3 | -------------------------------------------------------------------------------- /test/test_files/assignment/grouping.lox: -------------------------------------------------------------------------------- 1 | var a = "a"; 2 | (a) = "value"; // Error at '=': Invalid assignment target. 3 | -------------------------------------------------------------------------------- /test/test_files/if/fun_in_else.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at 'fun': Expect expression. 2 | if (true) "ok"; else fun foo() {} 3 | -------------------------------------------------------------------------------- /test/test_files/if/var_in_else.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at 'var': Expect expression. 2 | if (true) "ok"; else var foo; 3 | -------------------------------------------------------------------------------- /test/test_files/operator/add_num_nil.lox: -------------------------------------------------------------------------------- 1 | 1 + nil; // expect runtime error: Operands must be two numbers or two strings. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/greater_or_equal_nonnum_num.lox: -------------------------------------------------------------------------------- 1 | "1" >= 1; // expect runtime error: Operands must be numbers. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/greater_or_equal_num_nonnum.lox: -------------------------------------------------------------------------------- 1 | 1 >= "1"; // expect runtime error: Operands must be numbers. 2 | -------------------------------------------------------------------------------- /test/test_files/variable/use_global_in_initializer.lox: -------------------------------------------------------------------------------- 1 | var a = "value"; 2 | var a = a; 3 | print a; // expect: value 4 | -------------------------------------------------------------------------------- /test/test_files/variable/use_nil_as_var.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at 'nil': Expect variable name. 2 | var nil = "value"; 3 | -------------------------------------------------------------------------------- /test/test_files/variable/use_this_as_var.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at 'this': Expect variable name. 2 | var this = "value"; 3 | -------------------------------------------------------------------------------- /test/test_files/assignment/prefix_operator.lox: -------------------------------------------------------------------------------- 1 | var a = "a"; 2 | !a = "value"; // Error at '=': Invalid assignment target. 3 | -------------------------------------------------------------------------------- /test/test_files/for/statement_increment.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at '{': Expect expression. 2 | for (var a = 1; a < 2; {}) {} 3 | -------------------------------------------------------------------------------- /test/test_files/operator/add_bool_nil.lox: -------------------------------------------------------------------------------- 1 | true + nil; // expect runtime error: Operands must be two numbers or two strings. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/add_bool_num.lox: -------------------------------------------------------------------------------- 1 | true + 123; // expect runtime error: Operands must be two numbers or two strings. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/add_nil_nil.lox: -------------------------------------------------------------------------------- 1 | nil + nil; // expect runtime error: Operands must be two numbers or two strings. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/add_string_nil.lox: -------------------------------------------------------------------------------- 1 | "s" + nil; // expect runtime error: Operands must be two numbers or two strings. 2 | -------------------------------------------------------------------------------- /test/test_files/operator/negate.lox: -------------------------------------------------------------------------------- 1 | print -(3); // expect: -3 2 | print --(3); // expect: 3 3 | print ---(3); // expect: -3 4 | -------------------------------------------------------------------------------- /test/test_files/return/after_while.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | while (true) return "ok"; 3 | } 4 | 5 | print f(); // expect: ok 6 | -------------------------------------------------------------------------------- /test/test_files/variable/in_nested_block.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = "outer"; 3 | { 4 | print a; // expect: outer 5 | } 6 | } -------------------------------------------------------------------------------- /test/test_files/variable/undefined_global.lox: -------------------------------------------------------------------------------- 1 | print notDefined; // expect runtime error: Undefined variable 'notDefined'. 2 | -------------------------------------------------------------------------------- /test/test_files/variable/use_false_as_var.lox: -------------------------------------------------------------------------------- 1 | // [line 2] Error at 'false': Expect variable name. 2 | var false = "value"; 3 | -------------------------------------------------------------------------------- /test/test_files/for/syntax.expected: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 0 5 | 1 6 | 2 7 | done 8 | 0 9 | 1 10 | 0 11 | 1 12 | 2 13 | 0 14 | 1 -------------------------------------------------------------------------------- /test/test_files/operator/add_bool_string.lox: -------------------------------------------------------------------------------- 1 | true + "s"; // expect runtime error: Operands must be two numbers or two strings. 2 | -------------------------------------------------------------------------------- /test/test_files/return/after_else.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | if (false) "no"; else return "ok"; 3 | } 4 | 5 | print f(); // expect: ok 6 | -------------------------------------------------------------------------------- /test/test_files/return/in_function.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | return "ok"; 3 | print "bad"; 4 | } 5 | 6 | print f(); // expect: ok 7 | -------------------------------------------------------------------------------- /test/test_files/string/multiline.lox: -------------------------------------------------------------------------------- 1 | var a = "1 2 | 2 3 | 3"; 4 | print a; 5 | // expect: 1 6 | // expect: 2 7 | // expect: 3 8 | -------------------------------------------------------------------------------- /test/test_files/variable/unreached_undefined.lox: -------------------------------------------------------------------------------- 1 | if (false) { 2 | print notDefined; 3 | } 4 | 5 | print "ok"; // expect: ok 6 | -------------------------------------------------------------------------------- /lib/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (public_name lox) 3 | (libraries base stdio fmt) 4 | (preprocess (pps ppx_deriving.show ppx_deriving.eq))) 5 | -------------------------------------------------------------------------------- /test/test_files/bool/not.lox: -------------------------------------------------------------------------------- 1 | print !true; // expect: false 2 | print !false; // expect: true 3 | print !!true; // expect: true 4 | -------------------------------------------------------------------------------- /test/test_files/function/missing_arguments.lox: -------------------------------------------------------------------------------- 1 | fun f(a, b) {} 2 | 3 | f(1); // expect runtime error: Expected 2 arguments but got 1. 4 | -------------------------------------------------------------------------------- /test/test_files/return/return_nil_if_no_value.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | return; 3 | print "bad"; 4 | } 5 | 6 | print f(); // expect: nil 7 | -------------------------------------------------------------------------------- /test/test_files/operator/equals.expected: -------------------------------------------------------------------------------- 1 | true 2 | true 3 | false 4 | true 5 | false 6 | true 7 | false 8 | false 9 | false 10 | false -------------------------------------------------------------------------------- /test/test_files/operator/not_equals.expected: -------------------------------------------------------------------------------- 1 | false 2 | false 3 | true 4 | false 5 | true 6 | false 7 | true 8 | true 9 | true 10 | true -------------------------------------------------------------------------------- /test/test_files/variable/undefined_local.lox: -------------------------------------------------------------------------------- 1 | { 2 | print notDefined; // expect runtime error: Undefined variable 'notDefined'. 3 | } 4 | -------------------------------------------------------------------------------- /test/test_files/assignment/infix_operator.lox: -------------------------------------------------------------------------------- 1 | var a = "a"; 2 | var b = "b"; 3 | a + b = "value"; // Error at '=': Invalid assignment target. 4 | -------------------------------------------------------------------------------- /test/test_files/block/empty.lox: -------------------------------------------------------------------------------- 1 | {} // By itself. 2 | 3 | // In a statement. 4 | if (true) {} 5 | if (false) {} else {} 6 | 7 | print "ok"; // expect: ok 8 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.0) 2 | (name lox-interpreter) 3 | (authors "Samarth Kishor") 4 | (maintainers "samarthkishor1@gmail.com") 5 | (license Unlicense) 6 | -------------------------------------------------------------------------------- /test/test_files/block/scope.lox: -------------------------------------------------------------------------------- 1 | var a = "outer"; 2 | 3 | { 4 | var a = "inner"; 5 | print a; // expect: inner 6 | } 7 | 8 | print a; // expect: outer 9 | -------------------------------------------------------------------------------- /test/test_files/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); // expect: 21 7 | -------------------------------------------------------------------------------- /test/test_files/variable/collide_with_parameter.lox: -------------------------------------------------------------------------------- 1 | fun foo(a) { 2 | var a; // Error at 'a': Variable with this name already declared in this scope. 3 | } 4 | -------------------------------------------------------------------------------- /test/test_files/closure/open_closure_in_function.lox: -------------------------------------------------------------------------------- 1 | { 2 | var local = "local"; 3 | fun f() { 4 | print local; // expect: local 5 | } 6 | f(); 7 | } 8 | -------------------------------------------------------------------------------- /test/test_files/for/return_inside.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | for (;;) { 3 | var i = "i"; 4 | return i; 5 | } 6 | } 7 | 8 | print f(); 9 | // expect: i 10 | -------------------------------------------------------------------------------- /test/test_files/variable/shadow_global.lox: -------------------------------------------------------------------------------- 1 | var a = "global"; 2 | { 3 | var a = "shadow"; 4 | print a; // expect: shadow 5 | } 6 | print a; // expect: global 7 | -------------------------------------------------------------------------------- /test/test_files/variable/use_local_in_initializer.lox: -------------------------------------------------------------------------------- 1 | var a = "outer"; 2 | { 3 | var a = a; // Error at 'a': Cannot read local variable in its own initializer. 4 | } 5 | -------------------------------------------------------------------------------- /.merlin: -------------------------------------------------------------------------------- 1 | EXCLUDE_QUERY_DIR 2 | B _build/default/.main.eobjs 3 | S . 4 | FLG -w @a-4-29-40-41-42-44-45-48-58-59-60-40 -strict-sequence -strict-formats -short-paths -keep-locs 5 | -------------------------------------------------------------------------------- /test/test_files/assignment/syntax.lox: -------------------------------------------------------------------------------- 1 | // Assignment on RHS of variable. 2 | var a = "before"; 3 | var c = a = "var"; 4 | print a; // expect: var 5 | print c; // expect: var 6 | -------------------------------------------------------------------------------- /test/test_files/variable/duplicate_local.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = "value"; 3 | var a = "other"; // Error at 'a': Variable with this name already declared in this scope. 4 | } 5 | -------------------------------------------------------------------------------- /test/test_files/while/return_inside.lox: -------------------------------------------------------------------------------- 1 | fun f() { 2 | while (true) { 3 | var i = "i"; 4 | return i; 5 | } 6 | } 7 | 8 | print f(); 9 | // expect: i 10 | -------------------------------------------------------------------------------- /test/test_files/function/extra_arguments.lox: -------------------------------------------------------------------------------- 1 | fun f(a, b) { 2 | print a; 3 | print b; 4 | } 5 | 6 | f(1, 2, 3, 4); // expect runtime error: Expected 2 arguments but got 4. 7 | -------------------------------------------------------------------------------- /test/test_files/string/literals.lox: -------------------------------------------------------------------------------- 1 | print "(" + "" + ")"; // expect: () 2 | print "a string"; // expect: a string 3 | 4 | // Non-ASCII. 5 | print "A~¶Þॐஃ"; // expect: A~¶Þॐஃ 6 | -------------------------------------------------------------------------------- /test/test_files/variable/duplicate_parameter.lox: -------------------------------------------------------------------------------- 1 | fun foo(arg, 2 | arg) { // Error at 'arg': Variable with this name already declared in this scope. 3 | "body"; 4 | } 5 | -------------------------------------------------------------------------------- /test/test_files/for/statement_initializer.lox: -------------------------------------------------------------------------------- 1 | // [line 3] Error at '{': Expect expression. 2 | // [line 3] Error at ')': Expect ';' after expression. 3 | for ({}; a < 2; a = a + 1) {} 4 | -------------------------------------------------------------------------------- /test/test_files/function/body_must_be_block.lox: -------------------------------------------------------------------------------- 1 | // [line 3] Error at '123': Expect '{' before function body. 2 | // [c line 4] Error at end: Expect '}' after block. 3 | fun f() 123; 4 | -------------------------------------------------------------------------------- /test/test_files/variable/shadow_and_local.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = "outer"; 3 | { 4 | print a; // expect: outer 5 | var a = "inner"; 6 | print a; // expect: inner 7 | } 8 | } -------------------------------------------------------------------------------- /test/test_files/variable/shadow_local.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = "local"; 3 | { 4 | var a = "shadow"; 5 | print a; // expect: shadow 6 | } 7 | print a; // expect: local 8 | } 9 | -------------------------------------------------------------------------------- /bin/dune: -------------------------------------------------------------------------------- 1 | (executables 2 | (names main) 3 | (libraries lox base stdio fmt) 4 | (preprocess (pps ppx_deriving.show))) 5 | 6 | (install 7 | (section bin) 8 | (files (main.exe as lox))) 9 | -------------------------------------------------------------------------------- /test/test_files/for/statement_condition.lox: -------------------------------------------------------------------------------- 1 | // [line 3] Error at '{': Expect expression. 2 | // [line 3] Error at ')': Expect ';' after expression. 3 | for (var a = 1; {}; a = a + 1) {} 4 | -------------------------------------------------------------------------------- /test/test_files/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); // expect: 21 8 | } 9 | -------------------------------------------------------------------------------- /test/test_files/_other/test_closure_globals.lox: -------------------------------------------------------------------------------- 1 | var a = "global"; 2 | { 3 | fun showA() { 4 | print a; 5 | } 6 | 7 | showA(); 8 | var a = "block"; 9 | showA(); 10 | } 11 | -------------------------------------------------------------------------------- /test/test_files/_other/test_globals.lox: -------------------------------------------------------------------------------- 1 | var a = 1; 2 | var b; 3 | 4 | { 5 | print a; 6 | a = a + 1; 7 | print a; 8 | var b = 42; 9 | } 10 | 11 | print a; 12 | print b; 13 | -------------------------------------------------------------------------------- /test/test_files/function/missing_comma_in_parameters.lox: -------------------------------------------------------------------------------- 1 | // [line 3] Error at 'c': Expect ')' after parameters. 2 | // [c line 4] Error at end: Expect '}' after block. 3 | fun foo(a, b c, d, e, f) {} 4 | -------------------------------------------------------------------------------- /test/test_files/_other/test_fibonacci.lox: -------------------------------------------------------------------------------- 1 | fun fibonacci(n) { 2 | if (n <= 1) { 3 | return n; 4 | } 5 | return fibonacci(n - 1) + fibonacci(n - 2); 6 | } 7 | 8 | print fibonacci(15); 9 | -------------------------------------------------------------------------------- /test/test_files/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(); // expect: i 11 | -------------------------------------------------------------------------------- /test/test_files/assignment/global.lox: -------------------------------------------------------------------------------- 1 | var a = "before"; 2 | print a; // expect: before 3 | 4 | a = "after"; 5 | print a; // expect: after 6 | 7 | print a = "arg"; // expect: arg 8 | print a; // expect: arg 9 | -------------------------------------------------------------------------------- /test/test_files/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(); // expect: local 12 | -------------------------------------------------------------------------------- /test/test_files/variable/scope_reuse_in_different_blocks.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = "first"; 3 | print a; // expect: first 4 | } 5 | 6 | { 7 | var a = "second"; 8 | print a; // expect: second 9 | } 10 | -------------------------------------------------------------------------------- /test/test_files/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(); // expect: i 11 | -------------------------------------------------------------------------------- /test/test_files/if/dangling_else.lox: -------------------------------------------------------------------------------- 1 | // A dangling else binds to the right-most if. 2 | if (true) if (false) print "bad"; else print "good"; // expect: good 3 | if (false) if (true) print "bad"; else print "bad"; 4 | -------------------------------------------------------------------------------- /test/test_files/variable/early_bound.lox: -------------------------------------------------------------------------------- 1 | var a = "outer"; 2 | { 3 | fun foo() { 4 | print a; 5 | } 6 | 7 | foo(); // expect: outer 8 | var a = "inner"; 9 | foo(); // expect: outer 10 | } 11 | -------------------------------------------------------------------------------- /test/test_files/bool/equality.expected: -------------------------------------------------------------------------------- 1 | true 2 | false 3 | false 4 | true 5 | false 6 | false 7 | false 8 | false 9 | false 10 | false 11 | true 12 | true 13 | false 14 | true 15 | true 16 | true 17 | true 18 | true -------------------------------------------------------------------------------- /test/test_files/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(); // expect: param 12 | -------------------------------------------------------------------------------- /test/test_files/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; // // expect runtime error: Undefined variable 'err'. -------------------------------------------------------------------------------- /test/test_files/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; // expect: c 8 | print b; // expect: c 9 | print c; // expect: c 10 | -------------------------------------------------------------------------------- /test/test_files/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 | // expect: a 14 | // expect: a 15 | -------------------------------------------------------------------------------- /test/test_files/assignment/local.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = "before"; 3 | print a; // expect: before 4 | 5 | a = "after"; 6 | print a; // expect: after 7 | 8 | print a = "arg"; // expect: arg 9 | print a; // expect: arg 10 | } 11 | -------------------------------------------------------------------------------- /test/test_files/number/literals.lox: -------------------------------------------------------------------------------- 1 | print 123; // expect: 123 2 | print 987654; // expect: 987654 3 | print 0; // expect: 0 4 | print -0; // expect: -0 5 | 6 | print 123.456; // expect: 123.456 7 | print -0.001; // expect: -0.001 8 | -------------------------------------------------------------------------------- /test/test_files/number/nan_equality.lox: -------------------------------------------------------------------------------- 1 | var nan = 0/0; 2 | 3 | print nan == 0; // expect: false 4 | print nan != 1; // expect: true 5 | 6 | // NaN is not equal to self. 7 | print nan == nan; // expect: false 8 | print nan != nan; // expect: true 9 | -------------------------------------------------------------------------------- /test/test_files/operator/comparison.expected: -------------------------------------------------------------------------------- 1 | true 2 | false 3 | false 4 | true 5 | true 6 | false 7 | false 8 | false 9 | true 10 | false 11 | true 12 | true 13 | false 14 | false 15 | false 16 | false 17 | true 18 | true 19 | true 20 | true -------------------------------------------------------------------------------- /test/test_files/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; // expect: inner 11 | } 12 | 13 | print a; // expect: assigned 14 | -------------------------------------------------------------------------------- /test/test_files/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 | return isEven(n - 1); 8 | } 9 | 10 | print isEven(4); // expect: true 11 | print isOdd(3); // expect: true 12 | -------------------------------------------------------------------------------- /test/test_files/_other/test_simple_closure.lox: -------------------------------------------------------------------------------- 1 | fun makeCounter() { 2 | var i = 0; 3 | fun count() { 4 | i = i + 1; 5 | print i; 6 | } 7 | 8 | return count; 9 | } 10 | 11 | var counter = makeCounter(); 12 | counter(); // "1". 13 | counter(); // "2". 14 | -------------------------------------------------------------------------------- /test/test_files/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"; // expect: ok 10 | -------------------------------------------------------------------------------- /test/test_files/variable/in_middle_of_block.lox: -------------------------------------------------------------------------------- 1 | { 2 | var a = "a"; 3 | print a; // expect: a 4 | var b = a + " b"; 5 | print b; // expect: a b 6 | var c = a + " c"; 7 | print c; // expect: a c 8 | var d = b + " d"; 9 | print d; // expect: a b d 10 | } 11 | -------------------------------------------------------------------------------- /test/test_files/function/local_mutual_recursion.lox: -------------------------------------------------------------------------------- 1 | { 2 | fun isEven(n) { 3 | if (n == 0) return true; 4 | return isOdd(n - 1); // expect runtime error: Undefined variable 'isOdd'. 5 | } 6 | 7 | fun isOdd(n) { 8 | return isEven(n - 1); 9 | } 10 | 11 | isEven(4); 12 | } -------------------------------------------------------------------------------- /test/test_files/logical_operator/or_truth.lox: -------------------------------------------------------------------------------- 1 | // False and nil are false. 2 | print false or "ok"; // expect: ok 3 | print nil or "ok"; // expect: ok 4 | 5 | // Everything else is true. 6 | print true or "ok"; // expect: true 7 | print 0 or "ok"; // expect: 0 8 | print "s" or "ok"; // expect: s 9 | -------------------------------------------------------------------------------- /test/test_files/closure/shadow_closure_with_local.lox: -------------------------------------------------------------------------------- 1 | { 2 | var foo = "closure"; 3 | fun f() { 4 | { 5 | print foo; // expect: closure 6 | var foo = "shadow"; 7 | print foo; // expect: shadow 8 | } 9 | print foo; // expect: closure 10 | } 11 | f(); 12 | } 13 | -------------------------------------------------------------------------------- /test/test_files/if/else.lox: -------------------------------------------------------------------------------- 1 | // Evaluate the 'else' expression if the condition is false. 2 | if (true) print "good"; else print "bad"; // expect: good 3 | if (false) print "bad"; else print "good"; // expect: good 4 | 5 | // Allow block body. 6 | if (false) nil; else { print "block"; } // expect: block 7 | -------------------------------------------------------------------------------- /test/test_files/logical_operator/and_truth.lox: -------------------------------------------------------------------------------- 1 | // False and nil are false. 2 | print false and "bad"; // expect: false 3 | print nil and "bad"; // expect: nil 4 | 5 | // Everything else is true. 6 | print true and "ok"; // expect: ok 7 | print 0 and "ok"; // expect: ok 8 | print "" and "ok"; // expect: ok 9 | -------------------------------------------------------------------------------- /bin/main.ml: -------------------------------------------------------------------------------- 1 | let () = 2 | let argc = Array.length Sys.argv in 3 | if argc > 2 4 | then ( 5 | print_endline "Usage: ocamlox [script]"; 6 | exit 64) 7 | else if argc = 2 8 | then ( 9 | let file_name = Sys.argv.(1) in 10 | Lox.run_file file_name) 11 | else Lox.run_prompt () 12 | ;; 13 | -------------------------------------------------------------------------------- /test/test_files/if/truth.lox: -------------------------------------------------------------------------------- 1 | // False and nil are false. 2 | if (false) print "bad"; else print "false"; // expect: false 3 | if (nil) print "bad"; else print "nil"; // expect: nil 4 | 5 | // Everything else is true. 6 | if (true) print true; // expect: true 7 | if (0) print 0; // expect: 0 8 | if ("") print "empty"; // expect: empty 9 | -------------------------------------------------------------------------------- /test/test_files/if/if.lox: -------------------------------------------------------------------------------- 1 | // Evaluate the 'then' expression if the condition is true. 2 | if (true) print "good"; // expect: good 3 | if (false) print "bad"; 4 | 5 | // Allow block body. 6 | if (true) { print "block"; } // expect: block 7 | 8 | // Assignment in if condition. 9 | var a = false; 10 | if (a = true) print a; // expect: true 11 | -------------------------------------------------------------------------------- /test/test_files/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(); // expect: a 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/test_files/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(); // expect: 1 18 | f2(); // expect: 2 19 | f3(); // expect: 3 20 | -------------------------------------------------------------------------------- /test/test_files/operator/not.lox: -------------------------------------------------------------------------------- 1 | print !true; // expect: false 2 | print !false; // expect: true 3 | print !!true; // expect: true 4 | 5 | print !123; // expect: false 6 | print !0; // expect: false 7 | 8 | print !nil; // expect: true 9 | 10 | print !""; // expect: false 11 | 12 | fun foo() {} 13 | print !foo; // expect: false 14 | -------------------------------------------------------------------------------- /test/test_files/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 | // expect: a 24 | // expect: b 25 | // expect: c 26 | -------------------------------------------------------------------------------- /test/test_files/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"; // expect: ok 14 | -------------------------------------------------------------------------------- /test/test_files/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(); // expect: 4 18 | // expect: 1 19 | f2(); // expect: 4 20 | // expect: 2 21 | f3(); // expect: 4 22 | // expect: 3 23 | -------------------------------------------------------------------------------- /test/test_files/operator/equals.lox: -------------------------------------------------------------------------------- 1 | print nil == nil; // expect: true 2 | 3 | print true == true; // expect: true 4 | print true == false; // expect: false 5 | 6 | print 1 == 1; // expect: true 7 | print 1 == 2; // expect: false 8 | 9 | print "str" == "str"; // expect: true 10 | print "str" == "ing"; // expect: false 11 | 12 | print nil == false; // expect: false 13 | print false == 0; // expect: false 14 | print 0 == "0"; // expect: false 15 | -------------------------------------------------------------------------------- /test/test_files/operator/not_equals.lox: -------------------------------------------------------------------------------- 1 | print nil != nil; // expect: false 2 | 3 | print true != true; // expect: false 4 | print true != false; // expect: true 5 | 6 | print 1 != 1; // expect: false 7 | print 1 != 2; // expect: true 8 | 9 | print "str" != "str"; // expect: false 10 | print "str" != "ing"; // expect: true 11 | 12 | print nil != false; // expect: true 13 | print false != 0; // expect: true 14 | print 0 != "0"; // expect: true 15 | -------------------------------------------------------------------------------- /test/test_files/while/syntax.lox: -------------------------------------------------------------------------------- 1 | // Single-expression body. 2 | var c = 0; 3 | while (c < 3) print c = c + 1; 4 | // expect: 1 5 | // expect: 2 6 | // expect: 3 7 | 8 | // Block body. 9 | var a = 0; 10 | while (a < 3) { 11 | print a; 12 | a = a + 1; 13 | } 14 | // expect: 0 15 | // expect: 1 16 | // expect: 2 17 | 18 | // Statement bodies. 19 | while (false) if (true) 1; else 2; 20 | while (false) while (true) 1; 21 | while (false) for (;;) 1; 22 | -------------------------------------------------------------------------------- /test/test_files/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 | // expect: local 23 | // expect: after f 24 | 25 | g(); 26 | // expect: after f 27 | // expect: after g 28 | -------------------------------------------------------------------------------- /lox.opam: -------------------------------------------------------------------------------- 1 | opam-version: "2.0" 2 | version: "1.0" 3 | maintainer: "samarthkishor1@gmail.com" 4 | authors: ["Samarth Kishor"] 5 | homepage: "https://github.com/samarthkishor/crafting-interpreters" 6 | bug-reports: "https://github.com/samarthkishor/crafting-interpreters/issues" 7 | license: "Unlicense" 8 | build: [ 9 | ["dune" "build" "-p" name "-j" jobs] 10 | ] 11 | depends: [ 12 | "dune" {>= "2.0"} 13 | "ocaml" 14 | "base" 15 | "ppx_deriving" 16 | "fmt" 17 | "alcotest" {test} 18 | ] 19 | -------------------------------------------------------------------------------- /test/test_files/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; // expect: b 11 | print a; // expect: a 12 | } 13 | g(); 14 | } 15 | f(); 16 | -------------------------------------------------------------------------------- /lib/.ocamlinit: -------------------------------------------------------------------------------- 1 | #use "topfind";; 2 | #require "base";; 3 | #require "ppx_deriving.show";; 4 | #require "ppx_deriving.eq";; 5 | 6 | (* hack to import all files as modules into utop *) 7 | #mod_use "value.ml";; 8 | #mod_use "loxError.ml";; 9 | #mod_use "scanner.ml";; 10 | #mod_use "parser.ml";; 11 | #mod_use "environment.ml";; 12 | #mod_use "resolver.ml";; 13 | #mod_use "interpreter.ml";; 14 | 15 | (* pretty-print when tracing *) 16 | #install_printer Environment.pp;; 17 | #install_printer Resolver.pp;; 18 | #install_printer Resolver.Scopes.pp;; 19 | #install_printer Resolver.Depths.pp;; 20 | -------------------------------------------------------------------------------- /test/test_files/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; // expect: 0 7 | 8 | // Loop body is in second inner scope. 9 | var i = -1; 10 | print i; // expect: -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; // expect: after 21 | 22 | // Can reuse an existing variable. 23 | for (i = 0; i < 1; i = i + 1) { 24 | print i; // expect: 0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /test/test_files/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; // expect: false 5 | print true and 1; // expect: 1 6 | print 1 and 2 and false; // expect: false 7 | 8 | // Return the last argument if all are true. 9 | print 1 and true; // expect: true 10 | print 1 and 2 and 3; // expect: 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; // expect: true 19 | print b; // expect: false 20 | -------------------------------------------------------------------------------- /test/test_files/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; // expect: 1 5 | print false or 1; // expect: 1 6 | print false or false or true; // expect: true 7 | 8 | // Return the last argument if all are false. 9 | print false or false; // expect: false 10 | print false or false or false; // expect: 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; // expect: false 19 | print b; // expect: true 20 | -------------------------------------------------------------------------------- /test/test_files/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(); // expect: a 28 | } 29 | -------------------------------------------------------------------------------- /test/test_files/operator/comparison.lox: -------------------------------------------------------------------------------- 1 | print 1 < 2; // expect: true 2 | print 2 < 2; // expect: false 3 | print 2 < 1; // expect: false 4 | 5 | print 1 <= 2; // expect: true 6 | print 2 <= 2; // expect: true 7 | print 2 <= 1; // expect: false 8 | 9 | print 1 > 2; // expect: false 10 | print 2 > 2; // expect: false 11 | print 2 > 1; // expect: true 12 | 13 | print 1 >= 2; // expect: false 14 | print 2 >= 2; // expect: true 15 | print 2 >= 1; // expect: true 16 | 17 | // Zero and negative zero compare the same. 18 | print 0 < -0; // expect: false 19 | print -0 < 0; // expect: false 20 | print 0 > -0; // expect: false 21 | print -0 > 0; // expect: false 22 | print 0 <= -0; // expect: true 23 | print -0 <= 0; // expect: true 24 | print 0 >= -0; // expect: true 25 | print -0 >= 0; // expect: true 26 | -------------------------------------------------------------------------------- /test/test_files/function/parameters.lox: -------------------------------------------------------------------------------- 1 | fun f0() { return 0; } 2 | print f0(); // expect: 0 3 | 4 | fun f1(a) { return a; } 5 | print f1(1); // expect: 1 6 | 7 | fun f2(a, b) { return a + b; } 8 | print f2(1, 2); // expect: 3 9 | 10 | fun f3(a, b, c) { return a + b + c; } 11 | print f3(1, 2, 3); // expect: 6 12 | 13 | fun f4(a, b, c, d) { return a + b + c + d; } 14 | print f4(1, 2, 3, 4); // expect: 10 15 | 16 | fun f5(a, b, c, d, e) { return a + b + c + d + e; } 17 | print f5(1, 2, 3, 4, 5); // expect: 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); // expect: 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); // expect: 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); // expect: 36 27 | -------------------------------------------------------------------------------- /test/test_files/bool/equality.lox: -------------------------------------------------------------------------------- 1 | print true == true; // expect: true 2 | print true == false; // expect: false 3 | print false == true; // expect: false 4 | print false == false; // expect: true 5 | 6 | // Not equal to other types. 7 | print true == 1; // expect: false 8 | print false == 0; // expect: false 9 | print true == "true"; // expect: false 10 | print false == "false"; // expect: false 11 | print false == ""; // expect: false 12 | 13 | print true != true; // expect: false 14 | print true != false; // expect: true 15 | print false != true; // expect: true 16 | print false != false; // expect: false 17 | 18 | // Not equal to other types. 19 | print true != 1; // expect: true 20 | print false != 0; // expect: true 21 | print true != "true"; // expect: true 22 | print false != "false"; // expect: true 23 | print false != ""; // expect: true 24 | -------------------------------------------------------------------------------- /test/test_files/for/syntax.lox: -------------------------------------------------------------------------------- 1 | // Single-expression body. 2 | for (var c = 0; c < 3;) print c = c + 1; 3 | // expect: 1 4 | // expect: 2 5 | // expect: 3 6 | 7 | // Block body. 8 | for (var a = 0; a < 3; a = a + 1) { 9 | print a; 10 | } 11 | // expect: 0 12 | // expect: 1 13 | // expect: 2 14 | 15 | // No clauses. 16 | fun foo() { 17 | for (;;) return "done"; 18 | } 19 | print foo(); // expect: done 20 | 21 | // No variable. 22 | var i = 0; 23 | for (; i < 2; i = i + 1) print i; 24 | // expect: 0 25 | // expect: 1 26 | 27 | // No condition. 28 | fun bar() { 29 | for (var i = 0;; i = i + 1) { 30 | print i; 31 | if (i >= 2) return; 32 | } 33 | } 34 | bar(); 35 | // expect: 0 36 | // expect: 1 37 | // expect: 2 38 | 39 | // No increment. 40 | for (var i = 0; i < 2;) { 41 | print i; 42 | i = i + 1; 43 | } 44 | // expect: 0 45 | // expect: 1 46 | 47 | // Statement bodies. 48 | for (; false;) if (true) 1; else 2; 49 | for (; false;) while (true) 1; 50 | for (; false;) for (;;) 1; 51 | -------------------------------------------------------------------------------- /test/test_files/make_expected.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | expected_regex = re.compile(r".*//\s*expect:\s*(.*)") 5 | 6 | for dir_name, sub_dirs, files in os.walk("."): 7 | if dir_name == "./_other": 8 | continue 9 | print(dir_name + ":") 10 | for filename in files: 11 | if filename == ".DS_Store" or "lox" not in filename: 12 | continue 13 | filename = filename.replace(".lox", "") 14 | expectations = [] 15 | with open(f"{dir_name}/{filename}.lox", "r") as f: 16 | for line in f: 17 | match = expected_regex.match(line) 18 | if match: 19 | m = match.group(1) 20 | if m != "" and m != "\n": 21 | expectations.append(m) 22 | if expectations != []: 23 | with open(f"{dir_name}/{filename}.expected", "w") as f: 24 | f.write("\n".join(expectations)) 25 | print(f'"{filename}";') # I just copy paste the output into `test.ml` 26 | -------------------------------------------------------------------------------- /lib/lox.ml: -------------------------------------------------------------------------------- 1 | open Base 2 | 3 | (* Modules to expose from the Lox library *) 4 | module Value = Value 5 | module Error = Error 6 | module Scanner = Scanner 7 | module Parser = Parser 8 | module Resolver = Resolver 9 | module Interpreter = Interpreter 10 | 11 | let run source = 12 | Scanner.make_scanner source 13 | |> Scanner.scan_tokens 14 | |> Parser.make_parser 15 | |> Parser.parse 16 | |> Resolver.make_resolver 17 | |> Resolver.resolve 18 | |> Interpreter.make_interpreter 19 | ;; 20 | 21 | let read_file file_name = String.concat (Stdio.In_channel.read_lines file_name) ~sep:"\n" 22 | 23 | let run_prompt () = 24 | while true do 25 | Stdio.printf "> "; 26 | Stdio.Out_channel.flush Stdio.stdout; 27 | let line = Stdio.In_channel.input_line Stdio.stdin in 28 | match line with 29 | | None -> () 30 | | Some l -> run l 31 | done 32 | ;; 33 | 34 | let run_file file_name = 35 | let file_contents = read_file file_name in 36 | run file_contents; 37 | if !LoxError.had_error then Caml.exit 65; 38 | if !LoxError.had_runtime_error then Caml.exit 70 39 | ;; 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /lib/value.ml: -------------------------------------------------------------------------------- 1 | open Base 2 | 3 | type t = 4 | | LoxBool of bool 5 | | LoxInt of int 6 | | LoxNumber of float 7 | | LoxString of string 8 | | LoxNil 9 | | LoxFunction of lox_function 10 | 11 | and lox_function = 12 | { arity : int 13 | ; name : string 14 | ; callable : t list -> t [@equal fun _ _ -> false] (* no equality for functions *) 15 | } 16 | [@@deriving show { with_path = false }, eq] 17 | 18 | type eval_type = 19 | | Bool 20 | | Number 21 | | String 22 | | Nil 23 | | Function 24 | [@@deriving eq] 25 | 26 | exception 27 | TypeError of 28 | { observed_type : eval_type 29 | ; expected_type : eval_type 30 | } 31 | 32 | let to_string value = 33 | match value with 34 | | LoxBool b -> Bool.to_string b 35 | | LoxInt i -> Printf.sprintf "%d" i 36 | | LoxNumber n -> Printf.sprintf "%g" n 37 | | LoxString s -> s 38 | | LoxNil -> "nil" 39 | | LoxFunction f -> Printf.sprintf "" f.name 40 | ;; 41 | 42 | let string_of_eval_type eval_type = 43 | match eval_type with 44 | | Bool -> "Bool" 45 | | Number -> "Number" 46 | | String -> "String" 47 | | Nil -> "Nil" 48 | | Function -> "Function" 49 | ;; 50 | 51 | let type_of value = 52 | match value with 53 | | LoxBool _ -> Bool 54 | | LoxNumber _ | LoxInt _ -> Number 55 | | LoxString _ -> String 56 | | LoxNil -> Nil 57 | | LoxFunction _ -> Function 58 | ;; 59 | 60 | let call value args = 61 | match value with 62 | | LoxFunction f -> f.callable args 63 | | value -> 64 | raise @@ TypeError { observed_type = type_of value; expected_type = Function } 65 | ;; 66 | -------------------------------------------------------------------------------- /lib/loxError.ml: -------------------------------------------------------------------------------- 1 | open Base 2 | 3 | (* NOTE can't use Scanner here because of cyclic dependencies *) 4 | 5 | type parse_error = 6 | { line : int 7 | ; lexeme : string 8 | ; message : string 9 | } 10 | 11 | type runtime_error = 12 | { where : int 13 | ; message : string 14 | } 15 | 16 | type type_error = 17 | { observed_type : Value.eval_type 18 | ; expected_type : Value.eval_type 19 | } 20 | 21 | type error = 22 | | RuntimeError of runtime_error 23 | | TypeError of type_error 24 | 25 | exception ParseError of parse_error 26 | exception RuntimeError of runtime_error 27 | exception TypeError of type_error 28 | 29 | let had_error = ref false 30 | let had_runtime_error = ref false 31 | 32 | let report line where message = 33 | Stdio.eprintf "[line %d] Error %s: %s\n" line where message; 34 | Stdio.Out_channel.flush Stdio.stderr; 35 | had_error := true 36 | ;; 37 | 38 | let report_parse_error error = 39 | let { line; lexeme; message } = error in 40 | if String.equal lexeme "" (* for Eof *) 41 | then report line "at end" message 42 | else report line ("at '" ^ lexeme ^ "'") message 43 | ;; 44 | 45 | let report_runtime_error (error : error) = 46 | match error with 47 | | TypeError e -> 48 | Stdio.eprintf 49 | "TypeError: observed type %s but expected type %s\n" 50 | (Value.string_of_eval_type e.observed_type) 51 | (Value.string_of_eval_type e.expected_type); 52 | Stdio.Out_channel.flush Stdio.stderr; 53 | had_runtime_error := true 54 | | RuntimeError e -> 55 | Stdio.eprintf "[line %d] RuntimeError: %s\n" e.where e.message; 56 | Stdio.Out_channel.flush Stdio.stderr; 57 | had_runtime_error := true 58 | ;; 59 | 60 | let error line message = report line "" message 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crafting Interpreters 2 | 3 | This is a tree-walking interpreter for a subset of the Lox programming language 4 | from the book [Crafting Interpreters](https://craftinginterpreters.com/). The 5 | book uses Java but I'm using a mix of imperative- and functional-style OCaml to 6 | implement the interpreter. I stuck to the same implementation of Lox as the book 7 | but decided not to add classes and objects. 8 | 9 | ## Usage 10 | 11 | Run `dune build @install` to compile the OCaml code. You might need to install 12 | the relevant dependencies via `opam`. 13 | 14 | Run `dune exec ./bin/main.exe` from the root directory to launch the interpreter. 15 | 16 | Tests are executed by running `dune runtest`. This implementation passes 72/82 17 | of the relevant Lox tests (I removed the ones that involved classes) and for 18 | now only fails the tests related to lexical closures. 19 | 20 | ## Why OCaml? 21 | 22 | ### Advantages 23 | 24 | - Interactive development with a REPL (`utop`). I can write a single function and 25 | send it to `utop` which compiles the function and allows me to test it. This 26 | makes it a lot easier to write code incrementally, and using pure functions 27 | allows me to compose these functions in the REPL and see how they work. 28 | - Powerful type system. OCaml's type system is fantastic and pattern matching 29 | lends itself well to the task of writing an interpreter because the compiler 30 | makes sure the matches are exhaustive. Optional types are helpful because 31 | the compiler forces you to handle the None case, making it impossible to 32 | run into null-related exceptions at runtime. 33 | - Editor integration. OCaml's integration with Emacs is amazing with Merlin 34 | acting as a language server to provide autocompletion support, type hints in 35 | the minibuffer, and lots of other nice IDE-like features. I'm not sure if 36 | support for other editors is that great (Merlin should make the experience 37 | pretty consistent in theory) but I'm guessing most people in the OCaml 38 | community use Emacs or Vim anyways. 39 | - Strange syntax for imperative code. This might seem like more of a drawback 40 | but honestly I like how imperative style code tends to stick out in ML 41 | languages and it encourages a more functional style. 42 | 43 | ### Disadvantages 44 | 45 | - Debugging. Even though Emacs integrates reasonably well with `ocamldebug`, 46 | debugger support is pretty lackluster and requires an executable. I tend to 47 | mostly use print statement debugging and resort to `#trace` when I need it. 48 | `ppx-deriving.show` helps with pretty-printing user-defined types. 49 | - Error messages. When my code encounters a run-time exception (which happens a 50 | lot since this is a new language for me), it just prints the exception and the 51 | name of the function that threw the exception. Something like a stack trace 52 | would be a lot more helpful. 53 | -------------------------------------------------------------------------------- /lib/environment.ml: -------------------------------------------------------------------------------- 1 | open Base 2 | 3 | module Values = struct 4 | type t = (string, Value.t) Hashtbl.t 5 | 6 | let copy values = Hashtbl.copy values 7 | 8 | let pp ppf values = 9 | Caml.Format.open_hovbox 1; 10 | Caml.Format.print_cut (); 11 | Hashtbl.iteri values ~f:(fun ~key ~data -> 12 | Caml.Format.fprintf ppf "@[%s: %s@ @]" key (Value.to_string data)); 13 | Caml.Format.close_box () 14 | ;; 15 | end 16 | 17 | type t = 18 | { values : Values.t 19 | ; mutable enclosing : t option 20 | } 21 | [@@deriving show { with_path = false }] 22 | 23 | let init ?enclosing () = 24 | { values = Hashtbl.create ~growth_allowed:true ~size:32 (module String); enclosing } 25 | ;; 26 | 27 | let rec get_value environment (name : Scanner.token) = 28 | match Hashtbl.find environment.values name.lexeme with 29 | | None -> 30 | (match environment.enclosing with 31 | | None -> 32 | raise 33 | @@ LoxError.RuntimeError 34 | { where = name.line; message = "Undefined variable '" ^ name.lexeme ^ "'." } 35 | | Some enclosing -> get_value enclosing name) 36 | | Some value -> value 37 | ;; 38 | 39 | let define environment name value = 40 | match Hashtbl.add environment.values ~key:name ~data:value with 41 | | `Ok -> () 42 | | `Duplicate -> Hashtbl.set environment.values ~key:name ~data:value 43 | ;; 44 | 45 | let rec assign environment (name : Scanner.token) value = 46 | match Hashtbl.find environment.values name.lexeme with 47 | | None -> 48 | (match environment.enclosing with 49 | | None -> 50 | raise 51 | @@ LoxError.RuntimeError 52 | { where = name.line; message = "Undefined variable '" ^ name.lexeme ^ "'." } 53 | | Some enclosing -> assign enclosing name value) 54 | | Some _ -> 55 | (match Hashtbl.add environment.values ~key:name.lexeme ~data:value with 56 | | `Ok -> () 57 | | `Duplicate -> Hashtbl.set environment.values ~key:name.lexeme ~data:value); 58 | value 59 | ;; 60 | 61 | let copy environment = 62 | { values = Values.copy environment.values 63 | ; enclosing = 64 | (match environment.enclosing with 65 | | None -> None 66 | | Some e -> Some { e with values = Values.copy e.values }) 67 | } 68 | ;; 69 | 70 | let rec ancestor environment distance = 71 | if distance = 0 72 | then Some environment 73 | else ( 74 | match environment.enclosing with 75 | | None -> None 76 | | Some env -> ancestor env (distance - 1)) 77 | ;; 78 | 79 | let get_at_distance environment distance (name : Scanner.token) = 80 | match ancestor environment distance with 81 | | None -> 82 | raise 83 | @@ LoxError.RuntimeError 84 | { where = name.line 85 | ; message = 86 | "Error in resolution phase when getting the value of variable '" 87 | ^ name.lexeme 88 | ^ "'." 89 | } 90 | | Some env -> get_value env name 91 | ;; 92 | 93 | let assign_at_distance environment (name : Scanner.token) value distance = 94 | match ancestor environment distance with 95 | | None -> 96 | raise 97 | @@ LoxError.RuntimeError 98 | { where = name.line 99 | ; message = 100 | "Error in resolution phase assigning a value to variable '" 101 | ^ name.lexeme 102 | ^ "'." 103 | } 104 | | Some env -> 105 | (match Hashtbl.add env.values ~key:name.lexeme ~data:value with 106 | | `Ok -> () 107 | | `Duplicate -> Hashtbl.set env.values ~key:name.lexeme ~data:value); 108 | value 109 | ;; 110 | -------------------------------------------------------------------------------- /test/test_files/function/too_many_parameters.lox: -------------------------------------------------------------------------------- 1 | // 256 parameters. 2 | fun f( 3 | a1, 4 | a2, 5 | a3, 6 | a4, 7 | a5, 8 | a6, 9 | a7, 10 | a8, 11 | a9, 12 | a10, 13 | a11, 14 | a12, 15 | a13, 16 | a14, 17 | a15, 18 | a16, 19 | a17, 20 | a18, 21 | a19, 22 | a20, 23 | a21, 24 | a22, 25 | a23, 26 | a24, 27 | a25, 28 | a26, 29 | a27, 30 | a28, 31 | a29, 32 | a30, 33 | a31, 34 | a32, 35 | a33, 36 | a34, 37 | a35, 38 | a36, 39 | a37, 40 | a38, 41 | a39, 42 | a40, 43 | a41, 44 | a42, 45 | a43, 46 | a44, 47 | a45, 48 | a46, 49 | a47, 50 | a48, 51 | a49, 52 | a50, 53 | a51, 54 | a52, 55 | a53, 56 | a54, 57 | a55, 58 | a56, 59 | a57, 60 | a58, 61 | a59, 62 | a60, 63 | a61, 64 | a62, 65 | a63, 66 | a64, 67 | a65, 68 | a66, 69 | a67, 70 | a68, 71 | a69, 72 | a70, 73 | a71, 74 | a72, 75 | a73, 76 | a74, 77 | a75, 78 | a76, 79 | a77, 80 | a78, 81 | a79, 82 | a80, 83 | a81, 84 | a82, 85 | a83, 86 | a84, 87 | a85, 88 | a86, 89 | a87, 90 | a88, 91 | a89, 92 | a90, 93 | a91, 94 | a92, 95 | a93, 96 | a94, 97 | a95, 98 | a96, 99 | a97, 100 | a98, 101 | a99, 102 | a100, 103 | a101, 104 | a102, 105 | a103, 106 | a104, 107 | a105, 108 | a106, 109 | a107, 110 | a108, 111 | a109, 112 | a110, 113 | a111, 114 | a112, 115 | a113, 116 | a114, 117 | a115, 118 | a116, 119 | a117, 120 | a118, 121 | a119, 122 | a120, 123 | a121, 124 | a122, 125 | a123, 126 | a124, 127 | a125, 128 | a126, 129 | a127, 130 | a128, 131 | a129, 132 | a130, 133 | a131, 134 | a132, 135 | a133, 136 | a134, 137 | a135, 138 | a136, 139 | a137, 140 | a138, 141 | a139, 142 | a140, 143 | a141, 144 | a142, 145 | a143, 146 | a144, 147 | a145, 148 | a146, 149 | a147, 150 | a148, 151 | a149, 152 | a150, 153 | a151, 154 | a152, 155 | a153, 156 | a154, 157 | a155, 158 | a156, 159 | a157, 160 | a158, 161 | a159, 162 | a160, 163 | a161, 164 | a162, 165 | a163, 166 | a164, 167 | a165, 168 | a166, 169 | a167, 170 | a168, 171 | a169, 172 | a170, 173 | a171, 174 | a172, 175 | a173, 176 | a174, 177 | a175, 178 | a176, 179 | a177, 180 | a178, 181 | a179, 182 | a180, 183 | a181, 184 | a182, 185 | a183, 186 | a184, 187 | a185, 188 | a186, 189 | a187, 190 | a188, 191 | a189, 192 | a190, 193 | a191, 194 | a192, 195 | a193, 196 | a194, 197 | a195, 198 | a196, 199 | a197, 200 | a198, 201 | a199, 202 | a200, 203 | a201, 204 | a202, 205 | a203, 206 | a204, 207 | a205, 208 | a206, 209 | a207, 210 | a208, 211 | a209, 212 | a210, 213 | a211, 214 | a212, 215 | a213, 216 | a214, 217 | a215, 218 | a216, 219 | a217, 220 | a218, 221 | a219, 222 | a220, 223 | a221, 224 | a222, 225 | a223, 226 | a224, 227 | a225, 228 | a226, 229 | a227, 230 | a228, 231 | a229, 232 | a230, 233 | a231, 234 | a232, 235 | a233, 236 | a234, 237 | a235, 238 | a236, 239 | a237, 240 | a238, 241 | a239, 242 | a240, 243 | a241, 244 | a242, 245 | a243, 246 | a244, 247 | a245, 248 | a246, 249 | a247, 250 | a248, 251 | a249, 252 | a250, 253 | a251, 254 | a252, 255 | a253, 256 | a254, 257 | a255, a) {} // Error at 'a': Cannot have more than 255 parameters. 258 | -------------------------------------------------------------------------------- /test/test_files/function/too_many_arguments.lox: -------------------------------------------------------------------------------- 1 | fun foo() {} 2 | { 3 | var a = 1; 4 | foo( 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); // Error at 'a': Cannot have more than 255 arguments. 261 | } 262 | -------------------------------------------------------------------------------- /lib/scanner.ml: -------------------------------------------------------------------------------- 1 | open Base 2 | 3 | type token_type = 4 | (* Single character tokens *) 5 | | LeftParen 6 | | RightParen 7 | | LeftBrace 8 | | RightBrace 9 | | Comma 10 | | Dot 11 | | Minus 12 | | Plus 13 | | Semicolon 14 | | Slash 15 | | Star 16 | (* One or two character tokens *) 17 | | Bang 18 | | BangEqual 19 | | Equal 20 | | EqualEqual 21 | | Greater 22 | | GreaterEqual 23 | | Less 24 | | LessEqual 25 | (* Literals *) 26 | | Identifier 27 | | String 28 | | Number 29 | (* Keywords *) 30 | | And 31 | | Class 32 | | Else 33 | | False 34 | | Fun 35 | | For 36 | | If 37 | | Nil 38 | | Or 39 | | Print 40 | | Return 41 | | Super 42 | | This 43 | | True 44 | | Var 45 | | While 46 | | Eof 47 | [@@deriving eq, show { with_path = false }] 48 | 49 | type token = 50 | { token_type : token_type 51 | ; lexeme : string 52 | ; literal : Value.t 53 | ; line : int 54 | } 55 | [@@deriving show { with_path = false }] 56 | 57 | type scanner = 58 | { source : string 59 | ; tokens : token list 60 | ; start : int 61 | ; current : int 62 | ; line : int 63 | } 64 | 65 | let keywords = 66 | [ "and", And 67 | ; "class", Class 68 | ; "else", Else 69 | ; "false", False 70 | ; "fun", Fun 71 | ; "for", For 72 | ; "if", If 73 | ; "nil", Nil 74 | ; "or", Or 75 | ; "print", Print 76 | ; "return", Return 77 | ; "super", Super 78 | ; "this", This 79 | ; "true", True 80 | ; "var", Var 81 | ; "while", While 82 | ] 83 | ;; 84 | 85 | let make_scanner source = { source; tokens = []; start = 0; current = 0; line = 1 } 86 | let is_at_end scanner = scanner.current >= String.length scanner.source 87 | let advance_scanner scanner = { scanner with current = scanner.current + 1 } 88 | 89 | let get_char scanner = 90 | if scanner.current > String.length scanner.source 91 | then None 92 | else Some scanner.source.[scanner.current - 1] 93 | ;; 94 | 95 | let get_lexeme scanner = 96 | (* NOTE String.sub is weird in OCaml... see documentation *) 97 | String.sub scanner.source ~pos:scanner.start ~len:(scanner.current - scanner.start) 98 | ;; 99 | 100 | let add_token scanner token_type = 101 | let token = 102 | { token_type 103 | ; lexeme = get_lexeme scanner 104 | ; literal = Value.LoxNil 105 | ; line = scanner.line 106 | } 107 | in 108 | { scanner with tokens = token :: scanner.tokens } 109 | ;; 110 | 111 | let add_token_with_literal scanner token_type literal = 112 | let token = { token_type; lexeme = get_lexeme scanner; literal; line = scanner.line } in 113 | { scanner with tokens = token :: scanner.tokens } 114 | ;; 115 | 116 | let add_double_token scanner double_token single_token = 117 | match scanner |> advance_scanner |> get_char with 118 | | None -> add_token scanner single_token 119 | | Some c -> 120 | if not (Char.equal c '=') 121 | then add_token scanner single_token 122 | else add_token (advance_scanner scanner) double_token 123 | ;; 124 | 125 | let peek scanner = 126 | if scanner.current >= String.length scanner.source 127 | then '\x00' 128 | else scanner.source.[scanner.current] 129 | ;; 130 | 131 | let add_comment scanner = 132 | match scanner |> advance_scanner |> get_char with 133 | | None -> add_token scanner Slash 134 | | Some c -> 135 | if Char.equal c '/' 136 | then ( 137 | let rec comment_out scanner = 138 | if is_at_end scanner || Char.equal (peek scanner) '\n' 139 | then scanner 140 | else scanner |> advance_scanner |> comment_out 141 | in 142 | comment_out scanner) 143 | else add_token scanner Slash 144 | ;; 145 | 146 | let rec consume_string scanner = 147 | if Char.equal (peek scanner) '"' && not (is_at_end scanner) 148 | then ( 149 | let scanner = advance_scanner scanner in 150 | let literal = 151 | Value.LoxString 152 | (String.sub 153 | scanner.source 154 | ~pos:(scanner.start + 1) 155 | ~len:(scanner.current - scanner.start - 2)) 156 | in 157 | add_token_with_literal scanner String literal) 158 | else if is_at_end scanner 159 | then ( 160 | LoxError.error scanner.line "Unterminated String."; 161 | scanner) 162 | else scanner |> advance_scanner |> consume_string 163 | ;; 164 | 165 | let is_digit c = Char.( >= ) c '0' && Char.( <= ) c '9' 166 | 167 | let is_alpha c = 168 | (Char.( >= ) c 'a' && Char.( <= ) c 'z') 169 | || (Char.( >= ) c 'A' && Char.( <= ) c 'z') 170 | || Char.equal c '_' 171 | ;; 172 | 173 | let is_alphanumeric c = is_digit c || is_alpha c 174 | 175 | let rec number scanner = 176 | if not (is_digit (peek scanner) || Char.equal (peek scanner) '.') 177 | then ( 178 | let value = get_lexeme scanner in 179 | let num = 180 | try Some (Float.of_string value) with 181 | | _ -> None 182 | in 183 | match num with 184 | | None -> 185 | LoxError.error scanner.line "Invalid Number."; 186 | scanner 187 | | Some n -> add_token_with_literal scanner Number (Value.LoxNumber n)) 188 | else scanner |> advance_scanner |> number 189 | ;; 190 | 191 | let rec identifier scanner = 192 | if not (is_alphanumeric (peek scanner)) 193 | then ( 194 | let text = get_lexeme scanner in 195 | let token_type = 196 | match List.Assoc.find keywords ~equal:String.equal text with 197 | | None -> Identifier 198 | | Some t -> t 199 | in 200 | add_token scanner token_type) 201 | else scanner |> advance_scanner |> identifier 202 | ;; 203 | 204 | let scan_token scanner = 205 | let scanner = advance_scanner scanner in 206 | match get_char scanner with 207 | | None -> scanner 208 | | Some c -> 209 | (match c with 210 | | '(' -> add_token scanner LeftParen 211 | | ')' -> add_token scanner RightParen 212 | | '{' -> add_token scanner LeftBrace 213 | | '}' -> add_token scanner RightBrace 214 | | ',' -> add_token scanner Comma 215 | | '.' -> add_token scanner Dot 216 | | '-' -> add_token scanner Minus 217 | | '+' -> add_token scanner Plus 218 | | ';' -> add_token scanner Semicolon 219 | | '*' -> add_token scanner Star 220 | | '!' -> add_double_token scanner BangEqual Bang 221 | | '=' -> add_double_token scanner EqualEqual Equal 222 | | '<' -> add_double_token scanner LessEqual Less 223 | | '>' -> add_double_token scanner GreaterEqual Greater 224 | | '/' -> add_comment scanner 225 | | ' ' | '\r' | '\t' -> scanner 226 | | '\n' -> { scanner with line = scanner.line + 1 } 227 | | '"' -> consume_string scanner 228 | | c when is_digit c -> number scanner 229 | | c when is_alpha c -> identifier scanner 230 | | _ -> 231 | LoxError.error scanner.line "Unexpected Character."; 232 | scanner) 233 | ;; 234 | 235 | let rec scan_tokens scanner = 236 | if is_at_end scanner 237 | then ( 238 | let token = 239 | { token_type = Eof; lexeme = ""; literal = Value.LoxNil; line = scanner.line } 240 | in 241 | List.rev (token :: scanner.tokens)) 242 | else ( 243 | let scanner = { scanner with start = scanner.current } in 244 | scan_tokens (scan_token scanner)) 245 | ;; 246 | -------------------------------------------------------------------------------- /lib/resolver.ml: -------------------------------------------------------------------------------- 1 | open Base 2 | 3 | module Scopes = struct 4 | type var_status = 5 | | Declare (** the variable is in the innermost scope and uninitialized *) 6 | | Define (** the variable is initialized to a value *) 7 | 8 | type t = (string, var_status) Hashtbl.t Stack.t 9 | 10 | let empty (scopes : t) = Stack.length scopes = 0 11 | let to_list (scopes : t) = scopes |> Stack.to_list |> List.rev 12 | 13 | let pp ppf scopes = 14 | Caml.Format.open_hovbox 1; 15 | Caml.Format.print_cut (); 16 | if empty scopes 17 | then Caml.Format.fprintf ppf "@[{}@]" 18 | else 19 | Stack.iter scopes ~f:(fun scope -> 20 | if Hashtbl.length scope = 0 21 | then Caml.Format.fprintf ppf "@[{}@]" 22 | else ( 23 | Caml.Format.fprintf ppf "@[{@ @]"; 24 | Hashtbl.iteri scope ~f:(fun ~key ~data -> 25 | Caml.Format.fprintf 26 | ppf 27 | "@[%s: %s,@ @]" 28 | key 29 | (match data with 30 | | Declare -> "declared" 31 | | Define -> "defined")); 32 | Caml.Format.fprintf ppf "@[}@]")); 33 | Caml.Format.close_box () 34 | ;; 35 | end 36 | 37 | module Depths = struct 38 | type t = (string, int) Hashtbl.t 39 | 40 | let pp ppf values = 41 | Caml.Format.open_hovbox 1; 42 | Caml.Format.print_cut (); 43 | if Hashtbl.length values = 0 44 | then Caml.Format.fprintf ppf "@[{}@]" 45 | else ( 46 | Caml.Format.fprintf ppf "@[{@ @]"; 47 | Hashtbl.iteri values ~f:(fun ~key ~data -> 48 | Caml.Format.fprintf ppf "@[%s: %d,@ @]" key data); 49 | Caml.Format.fprintf ppf "@[}@]"); 50 | Caml.Format.close_box () 51 | ;; 52 | end 53 | 54 | type function_type = 55 | | No_function 56 | | Some_function 57 | [@@deriving show { with_path = false }] 58 | 59 | type t = 60 | { statements : Parser.statement list 61 | ; scopes : Scopes.t 62 | ; depths : Depths.t 63 | ; current_function : function_type 64 | ; parsed_statements : Parser.statement list [@opaque] 65 | } 66 | [@@deriving show { with_path = false }] 67 | 68 | let make_resolver statements = 69 | { statements 70 | ; scopes = Stack.create () 71 | ; depths = Hashtbl.create ~growth_allowed:true ~size:32 (module String) 72 | ; current_function = No_function 73 | ; parsed_statements = statements 74 | } 75 | ;; 76 | 77 | let begin_scope resolver = 78 | let () = 79 | Stack.push 80 | resolver.scopes 81 | (Hashtbl.create ~growth_allowed:true ~size:16 (module String)) 82 | in 83 | resolver 84 | ;; 85 | 86 | let end_scope resolver = 87 | let () = 88 | match Stack.pop resolver.scopes with 89 | | None | Some _ -> () 90 | in 91 | resolver 92 | ;; 93 | 94 | (** Update the variable's status as Declared or Defined within the current scope *) 95 | let add_variable (name : Scanner.token) (status : Scopes.var_status) resolver = 96 | (* pop the scope, update it, and push it back *) 97 | match Stack.pop resolver.scopes with 98 | | None -> resolver 99 | | Some scope -> 100 | let new_scope = 101 | match Hashtbl.add scope ~key:name.lexeme ~data:status with 102 | | `Duplicate -> 103 | Hashtbl.set scope ~key:name.lexeme ~data:status; 104 | scope 105 | | `Ok -> scope 106 | in 107 | let () = Stack.push resolver.scopes new_scope in 108 | resolver 109 | ;; 110 | 111 | (** Resolve the depths of the variables seen so far, keeping track of the scope 112 | that each variable is in. *) 113 | let resolve_local resolver (var : Scanner.token) = 114 | let scope_count = 115 | (* iterate over the scopes in reverse order (bottom-first) *) 116 | List.fold_until 117 | (Scopes.to_list resolver.scopes) 118 | ~init:0 119 | ~f:(fun count scope -> 120 | match Hashtbl.find scope var.lexeme with 121 | | None -> Continue (count + 1) 122 | | Some _ -> Stop count) 123 | ~finish:(fun count -> count) 124 | in 125 | match Hashtbl.add resolver.depths ~key:var.lexeme ~data:scope_count with 126 | | `Duplicate -> 127 | Hashtbl.set resolver.depths ~key:var.lexeme ~data:scope_count; 128 | resolver 129 | | `Ok -> resolver 130 | ;; 131 | 132 | let rec resolve resolver = 133 | try List.fold resolver.statements ~init:resolver ~f:resolve_statement with 134 | | LoxError.RuntimeError error -> 135 | LoxError.report_runtime_error (LoxError.RuntimeError error); 136 | { resolver with parsed_statements = [] } 137 | 138 | and define_function 139 | (func : Parser.function_declaration) 140 | (function_type : function_type) 141 | resolver 142 | = 143 | let enclosing_function = resolver.current_function in 144 | let resolver = 145 | List.fold func.params ~init:(begin_scope resolver) ~f:(fun acc_resolver param -> 146 | acc_resolver |> add_variable param Declare |> add_variable param Define) 147 | in 148 | let new_resolver = 149 | resolve { resolver with statements = func.fun_body; current_function = function_type } 150 | in 151 | { new_resolver with current_function = enclosing_function } |> end_scope 152 | 153 | and resolve_statement resolver statement = 154 | match statement with 155 | | Block block_statements -> 156 | { resolver with statements = block_statements } |> begin_scope |> resolve |> end_scope 157 | | VarDeclaration v -> 158 | let new_resolver = add_variable v.name Declare resolver in 159 | let var_initializer = 160 | (* None if it's LoxNil, else it's Some expression *) 161 | (* TODO make literal variable an option type *) 162 | match v.init with 163 | | Literal l -> 164 | if Value.equal_eval_type (Value.type_of l.value) (Value.type_of LoxNil) 165 | then None 166 | else Some v.init 167 | | i -> Some i 168 | in 169 | (match var_initializer with 170 | | None -> add_variable v.name Define new_resolver 171 | | Some i -> 172 | resolve { new_resolver with statements = [ Parser.Expression i ] } 173 | |> add_variable v.name Define) 174 | | Expression expr -> 175 | (match expr with 176 | | Variable var -> 177 | (match Stack.top resolver.scopes with 178 | | None -> resolve_local resolver var 179 | | Some scope -> 180 | (match Hashtbl.find scope var.lexeme with 181 | | None -> resolve_local resolver var 182 | | Some status -> 183 | (match status with 184 | | Declare -> 185 | raise 186 | @@ LoxError.RuntimeError 187 | { where = var.line 188 | ; message = "Cannot read local variable in its own initializer." 189 | } 190 | | Define -> resolve_local resolver var))) 191 | | Assign expr -> 192 | let new_resolver = 193 | resolve { resolver with statements = [ Parser.Expression expr.assign_value ] } 194 | in 195 | let new_resolver = resolve_local new_resolver expr.name in 196 | new_resolver 197 | | Literal _ -> resolver 198 | | Binary expr -> 199 | let new_resolver = 200 | resolve { resolver with statements = [ Parser.Expression expr.left ] } 201 | in 202 | resolve { new_resolver with statements = [ Parser.Expression expr.right ] } 203 | | Call expr -> 204 | let new_resolver = 205 | resolve { resolver with statements = [ Parser.Expression expr.callee ] } 206 | in 207 | List.fold expr.arguments ~init:new_resolver ~f:(fun acc_resolver arg -> 208 | resolve { acc_resolver with statements = [ Parser.Expression arg ] }) 209 | | Grouping expr -> 210 | resolve { resolver with statements = [ Parser.Expression expr.expression ] } 211 | | Logical expr -> 212 | let new_resolver = 213 | resolve { resolver with statements = [ Parser.Expression expr.logical_left ] } 214 | in 215 | resolve { new_resolver with statements = [ Parser.Expression expr.logical_right ] } 216 | | Unary expr -> 217 | resolve { resolver with statements = [ Parser.Expression expr.operand ] }) 218 | | FunctionDeclaration d -> 219 | resolver 220 | |> add_variable d.fun_name Declare 221 | |> add_variable d.fun_name Define 222 | |> define_function d Some_function 223 | | IfStatement s -> 224 | let new_resolver = 225 | resolve { resolver with statements = [ Parser.Expression s.condition ] } 226 | in 227 | let new_resolver = resolve { new_resolver with statements = [ s.then_branch ] } in 228 | (match s.else_branch with 229 | | None -> new_resolver 230 | | Some b -> resolve { new_resolver with statements = [ b ] }) 231 | | Print p -> resolve { resolver with statements = [ Parser.Expression p ] } 232 | | ReturnStatement r -> 233 | (match resolver.current_function with 234 | | No_function -> 235 | raise 236 | @@ LoxError.RuntimeError 237 | { where = r.keyword.line; message = "Cannot return from top-level code." } 238 | | Some_function -> 239 | (match r.value with 240 | | None -> resolver 241 | | Some e -> resolve { resolver with statements = [ Parser.Expression e ] })) 242 | | WhileStatement s -> 243 | let new_resolver = 244 | resolve { resolver with statements = [ Parser.Expression s.while_condition ] } 245 | in 246 | resolve { new_resolver with statements = [ s.body ] } 247 | ;; 248 | -------------------------------------------------------------------------------- /lib/interpreter.ml: -------------------------------------------------------------------------------- 1 | open Base 2 | 3 | type t = 4 | { mutable state_env : Environment.t 5 | ; mutable globals : Environment.t 6 | ; locals : Resolver.Depths.t 7 | } 8 | [@@deriving show { with_path = false }] 9 | 10 | exception Return of Value.t 11 | 12 | let is_truthy value = 13 | match value with 14 | | Value.LoxNil -> false 15 | | Value.LoxBool b -> b 16 | | _ -> true 17 | ;; 18 | 19 | let is_equal left right = 20 | let open Value in 21 | match left, right with 22 | | LoxNil, LoxNil -> true 23 | | LoxNil, _ -> false 24 | | LoxBool l, LoxBool r -> Bool.( = ) l r 25 | | LoxNumber l, LoxNumber r -> Float.( = ) l r 26 | | LoxString l, LoxString r -> String.equal l r 27 | | l, r -> Value.equal_eval_type (Value.type_of l) (Value.type_of r) 28 | ;; 29 | 30 | let binary_arithmetic operator left right = 31 | let open Value in 32 | match left, right with 33 | | LoxNumber l, LoxNumber r -> LoxNumber (operator l r) 34 | | LoxNumber _, v -> 35 | raise @@ LoxError.TypeError { observed_type = type_of v; expected_type = Number } 36 | | v, LoxNumber _ -> 37 | raise @@ LoxError.TypeError { observed_type = type_of v; expected_type = Number } 38 | | v, _ -> 39 | raise @@ LoxError.TypeError { observed_type = type_of v; expected_type = Number } 40 | ;; 41 | 42 | let binary_comparison operator left right = 43 | let open Value in 44 | match left, right with 45 | | LoxNumber l, LoxNumber r -> LoxBool (operator l r) 46 | | LoxNumber _, v -> 47 | raise @@ LoxError.TypeError { observed_type = type_of v; expected_type = Number } 48 | | v, LoxNumber _ -> 49 | raise @@ LoxError.TypeError { observed_type = type_of v; expected_type = Number } 50 | | v, _ -> 51 | raise @@ LoxError.TypeError { observed_type = type_of v; expected_type = Number } 52 | ;; 53 | 54 | let look_up_variable state (token : Scanner.token) = 55 | match Hashtbl.find state.locals token.lexeme with 56 | | None -> Environment.get_value state.state_env token 57 | | Some distance -> Environment.get_at_distance state.state_env distance token 58 | ;; 59 | 60 | let rec evaluate state (expr : Parser.expr) = 61 | match expr with 62 | | Literal e -> e.value 63 | | Grouping e -> evaluate state e.expression 64 | | Unary e -> 65 | let right = evaluate state e.operand in 66 | (match e.unary_operator.token_type with 67 | | Minus -> 68 | LoxNumber 69 | (match right with 70 | | LoxNumber n -> -.n 71 | | value -> 72 | raise 73 | @@ LoxError.TypeError 74 | { observed_type = Value.type_of value; expected_type = Number }) 75 | | Bang -> LoxBool (not (is_truthy right)) 76 | | _ -> LoxNil) 77 | | Binary e -> 78 | let left = evaluate state e.left in 79 | let right = evaluate state e.right in 80 | (match e.binary_operator.token_type with 81 | | Minus -> binary_arithmetic ( -. ) left right 82 | | Slash -> binary_arithmetic ( /. ) left right 83 | | Star -> binary_arithmetic ( *. ) left right 84 | | Plus -> 85 | (match left, right with 86 | | LoxNumber _, LoxNumber _ -> binary_arithmetic ( +. ) left right 87 | | LoxString l, LoxString r -> LoxString (l ^ r) 88 | | LoxString _, v | v, LoxString _ -> 89 | raise 90 | @@ LoxError.TypeError { observed_type = Value.type_of v; expected_type = String } 91 | | v, _ -> 92 | raise 93 | @@ LoxError.TypeError { observed_type = Value.type_of v; expected_type = String }) 94 | | Greater -> binary_comparison Float.( > ) left right 95 | | GreaterEqual -> binary_comparison Float.( >= ) left right 96 | | Less -> binary_comparison Float.( < ) left right 97 | | LessEqual -> binary_comparison Float.( <= ) left right 98 | | BangEqual -> LoxBool (not (is_equal left right)) 99 | | EqualEqual -> LoxBool (is_equal left right) 100 | | _ -> LoxNil) 101 | | Call c -> 102 | let callee = evaluate state c.callee in 103 | let evaluated_args = List.map ~f:(fun arg -> evaluate state arg) c.arguments in 104 | (match callee with 105 | | Value.LoxFunction f -> 106 | if List.length evaluated_args = f.arity 107 | then Value.call callee evaluated_args 108 | else 109 | raise 110 | @@ LoxError.RuntimeError 111 | { where = c.paren.line 112 | ; message = 113 | Printf.sprintf 114 | "Expected %d arguments but got %d." 115 | f.arity 116 | (List.length evaluated_args) 117 | } 118 | | _ -> 119 | raise 120 | @@ LoxError.RuntimeError 121 | { where = c.paren.line; message = "Can only call functions." }) 122 | | Variable token -> look_up_variable state token 123 | | Assign expr -> 124 | let value = evaluate state expr.assign_value in 125 | (match Hashtbl.find state.locals (Parser.string_of_expr expr.assign_value) with 126 | | None -> Environment.assign state.state_env expr.name value 127 | | Some distance -> 128 | Environment.assign_at_distance state.state_env expr.name value distance) 129 | | Logical expr -> 130 | let left = evaluate state expr.logical_left in 131 | (* short-circuit `or` if left evaluates to true *) 132 | if Scanner.equal_token_type expr.operator.token_type Scanner.Or 133 | then 134 | if is_truthy left 135 | then left 136 | else 137 | evaluate state expr.logical_right 138 | (* short-circuit `and` if left evaluates to false *) 139 | else if not (is_truthy left) 140 | then left 141 | else evaluate state expr.logical_right 142 | ;; 143 | 144 | let rec evaluate_statement (state : t) (statement : Parser.statement) = 145 | match statement with 146 | | Expression expression -> ignore (evaluate state expression) 147 | | IfStatement s -> 148 | if is_truthy (evaluate state s.condition) 149 | then evaluate_statement state s.then_branch 150 | else ( 151 | match s.else_branch with 152 | | None -> () 153 | | Some branch -> evaluate_statement state branch) 154 | | Print expression -> 155 | evaluate state expression |> Value.to_string |> Stdio.printf "%s\n" 156 | | ReturnStatement s -> 157 | let return_value = 158 | match s.value with 159 | | None -> Value.LoxNil 160 | | Some e -> evaluate state e 161 | in 162 | raise @@ Return return_value 163 | | FunctionDeclaration f -> 164 | let env = Environment.init ~enclosing:state.state_env () in 165 | let func_state : Environment.t = 166 | { values = state.state_env.values; enclosing = env.enclosing } 167 | in 168 | let call_func args = 169 | let new_env = 170 | match func_state.enclosing with 171 | | None -> Environment.init () 172 | | Some enclosing -> Environment.init ~enclosing () 173 | in 174 | (match 175 | List.iter2 176 | ~f:(fun (param : Scanner.token) arg -> 177 | Environment.define new_env param.lexeme arg) 178 | f.params 179 | args 180 | with 181 | | Ok _ -> () 182 | | Unequal_lengths -> 183 | raise 184 | @@ LoxError.RuntimeError 185 | { where = f.fun_name.line 186 | ; message = "Unequal number of arguments and parameters to function." 187 | }); 188 | try 189 | interpret { state with state_env = new_env } f.fun_body; 190 | Value.LoxNil 191 | with 192 | | Return value -> value 193 | in 194 | Environment.define 195 | func_state 196 | f.fun_name.lexeme 197 | (Value.LoxFunction 198 | { name = f.fun_name.lexeme; arity = List.length f.params; callable = call_func }) 199 | | VarDeclaration d -> 200 | let value = 201 | match d.init with 202 | | Literal l -> 203 | if Value.equal_eval_type (Value.type_of l.value) (Value.type_of LoxNil) 204 | then Value.LoxNil 205 | else evaluate state d.init 206 | | _ -> evaluate state d.init 207 | in 208 | Environment.define state.state_env d.name.lexeme value 209 | | WhileStatement s -> 210 | while is_truthy (evaluate state s.while_condition) do 211 | evaluate_statement state s.body 212 | done 213 | | Block block_statements -> 214 | (* need to copy the Environment because it's passed by reference *) 215 | let previous_environment = Environment.copy state.state_env in 216 | let new_environment = Environment.init ~enclosing:state.state_env () in 217 | (try interpret { state with state_env = new_environment } block_statements with 218 | | error -> 219 | (* restore the previous environment even if there was an error *) 220 | state.state_env <- previous_environment; 221 | raise error); 222 | state.state_env <- previous_environment 223 | 224 | and interpret state (statements : Parser.statement list) = 225 | try List.iter ~f:(fun statement -> evaluate_statement state statement) statements with 226 | | LoxError.TypeError error -> LoxError.report_runtime_error (LoxError.TypeError error) 227 | | LoxError.RuntimeError error -> 228 | LoxError.report_runtime_error (LoxError.RuntimeError error) 229 | ;; 230 | 231 | let make_interpreter (resolver : Resolver.t) = 232 | let state = 233 | { state_env = Environment.init () 234 | ; globals = Environment.init () 235 | ; locals = resolver.depths 236 | } 237 | in 238 | interpret state resolver.parsed_statements 239 | ;; 240 | -------------------------------------------------------------------------------- /test/test.ml: -------------------------------------------------------------------------------- 1 | (* Scanner tests *) 2 | 3 | let test_scanner_basic () = 4 | let open Lox.Scanner in 5 | Alcotest.(check (list string)) 6 | "same lists" 7 | [ "var"; "i"; ";"; "" ] 8 | (make_scanner "var i;" |> scan_tokens |> List.map (fun t -> t.lexeme)) 9 | ;; 10 | 11 | let test_scanner_advanced () = 12 | let open Lox.Scanner in 13 | Alcotest.(check (list string)) 14 | "same lists" 15 | [ "var" 16 | ; "j" 17 | ; ";" 18 | ; "for" 19 | ; "(" 20 | ; "var" 21 | ; "i" 22 | ; "=" 23 | ; "0" 24 | ; ";" 25 | ; "i" 26 | ; "<" 27 | ; "5" 28 | ; ";" 29 | ; "i" 30 | ; "=" 31 | ; "i" 32 | ; "+" 33 | ; "1" 34 | ; ")" 35 | ; "{" 36 | ; "if" 37 | ; "(" 38 | ; "i" 39 | ; "<" 40 | ; "2" 41 | ; ")" 42 | ; "print" 43 | ; "i" 44 | ; ";" 45 | ; "}" 46 | ; "" 47 | ] 48 | (make_scanner "var j; for (var i = 0; i < 5; i = i + 1) { if (i < 2) print i; }" 49 | |> scan_tokens 50 | |> List.map (fun t -> t.lexeme)) 51 | ;; 52 | 53 | (* Parser tests *) 54 | 55 | let test_parser_while_loop () = 56 | let open Lox in 57 | Alcotest.(check (list string)) 58 | "same lists" 59 | [ "var i = 1;"; "while (i < 5) print i;" ] 60 | (Scanner.make_scanner "var i = 1; while (i < 5) print i;" 61 | |> Scanner.scan_tokens 62 | |> Parser.make_parser 63 | |> Parser.parse 64 | |> List.map Parser.string_of_statement) 65 | ;; 66 | 67 | let test_parser_for_loop_simple () = 68 | let open Lox in 69 | Alcotest.(check (list string)) 70 | "same lists" 71 | (* only one item in list because the for loop gets de-sugared into a block *) 72 | [ "{var i = 0; while (i < 5) {print i; i = (i + 1);}}" ] 73 | (Scanner.make_scanner "for (var i = 0; i < 5; i = i + 1) { print i; }" 74 | |> Scanner.scan_tokens 75 | |> Parser.make_parser 76 | |> Parser.parse 77 | |> List.map Parser.string_of_statement) 78 | ;; 79 | 80 | let test_parser_for_loop_no_declaration_increment () = 81 | let open Lox in 82 | Alcotest.(check (list string)) 83 | "same lists" 84 | [ "var i = 1;"; "while (i < 5) {print i; i = (i + 1);}" ] 85 | (Scanner.make_scanner "var i = 1; for (; i < 5; ) { print i; i = i + 1; }" 86 | |> Scanner.scan_tokens 87 | |> Parser.make_parser 88 | |> Parser.parse 89 | |> List.map Parser.string_of_statement) 90 | ;; 91 | 92 | let test_parser_for_loop_infinite () = 93 | let open Lox in 94 | Alcotest.(check (list string)) 95 | "same lists" 96 | [ "while (true) print (1 == 1);" ] 97 | (Scanner.make_scanner "for (;;) print 1 == 1;" 98 | |> Scanner.scan_tokens 99 | |> Parser.make_parser 100 | |> Parser.parse 101 | |> List.map Parser.string_of_statement) 102 | ;; 103 | 104 | let test_parser_function_no_args () = 105 | let open Lox in 106 | Alcotest.(check (list string)) 107 | "same lists" 108 | [ "test();" ] 109 | (Scanner.make_scanner "test();" 110 | |> Scanner.scan_tokens 111 | |> Parser.make_parser 112 | |> Parser.parse 113 | |> List.map Parser.string_of_statement) 114 | ;; 115 | 116 | let test_parser_function_multiple_args () = 117 | let open Lox in 118 | Alcotest.(check (list string)) 119 | "same lists" 120 | [ "test(v, (1 + 1), 5);" ] 121 | (Scanner.make_scanner "test(v, 1 + 1, 5);" 122 | |> Scanner.scan_tokens 123 | |> Parser.make_parser 124 | |> Parser.parse 125 | |> List.map Parser.string_of_statement) 126 | ;; 127 | 128 | let test_parser_function_declaration_no_params () = 129 | let open Lox in 130 | Alcotest.(check (list string)) 131 | "same lists" 132 | [ "fun test() {do_something();}" ] 133 | (Scanner.make_scanner "fun test() { do_something(); }" 134 | |> Scanner.scan_tokens 135 | |> Parser.make_parser 136 | |> Parser.parse 137 | |> List.map Parser.string_of_statement) 138 | ;; 139 | 140 | let test_parser_function_declaration_multiple_params () = 141 | let open Lox in 142 | Alcotest.(check (list string)) 143 | "same lists" 144 | [ "fun test(v1, v2, v3) {print v1; print v2; print (v1 + v3);}" ] 145 | (Scanner.make_scanner "fun test(v1, v2, v3) { print v1; print v2; print v1 + v3; }" 146 | |> Scanner.scan_tokens 147 | |> Parser.make_parser 148 | |> Parser.parse 149 | |> List.map Parser.string_of_statement) 150 | ;; 151 | 152 | (* Interpreter tests *) 153 | 154 | (* Utility function to execute a Unix command *) 155 | (* Source: https://rosettacode.org/wiki/Execute_a_system_command#OCaml *) 156 | let string_of_unix_command cmd = 157 | let ic, oc = Unix.open_process cmd in 158 | let buf = Buffer.create 16 in 159 | (try 160 | while true do 161 | Buffer.add_channel buf ic 1 162 | done 163 | with 164 | | End_of_file -> ()); 165 | let _ = Unix.close_process (ic, oc) in 166 | Buffer.contents buf |> String.trim 167 | ;; 168 | 169 | (* NOTE I can't figure out how to capture stdout into a string, so instead I read 170 | * from files and compare the results of running the lox file with the expected 171 | * file. Alcotest tests execute in the directory $PROJECT_ROOT/_build/default/test *) 172 | let test_interpreter dir_name test_name _ = 173 | Alcotest.(check string) 174 | "same string" 175 | (Lox.read_file 176 | @@ Printf.sprintf "../../../test/test_files/%s/%s.expected" dir_name test_name) 177 | (string_of_unix_command 178 | @@ Printf.sprintf 179 | "../bin/main.exe ../../../test/test_files/%s/%s.lox" 180 | dir_name 181 | test_name) 182 | ;; 183 | 184 | (* Utility function to make a list of test cases 185 | * `test_group` is a list of strings containing test filenames *) 186 | let rec make_unit_tests name = function 187 | | [] -> [] 188 | | filename :: filenames -> 189 | [ Alcotest.test_case ("test " ^ filename) `Slow (test_interpreter name filename) ] 190 | @ make_unit_tests name filenames 191 | ;; 192 | 193 | (* Run tests *) 194 | let () = 195 | Alcotest.run 196 | "Lox tests" 197 | [ ( "scanner tests" 198 | , [ Alcotest.test_case "simple scanner" `Quick test_scanner_basic 199 | ; Alcotest.test_case "advanced scanner" `Quick test_scanner_advanced 200 | ] ) 201 | ; ( "parser tests" 202 | , [ Alcotest.test_case "parse while loops" `Quick test_parser_while_loop 203 | ; Alcotest.test_case "parse simple for loops" `Quick test_parser_for_loop_simple 204 | ; Alcotest.test_case 205 | "parse for loops without declaration and increment" 206 | `Quick 207 | test_parser_for_loop_no_declaration_increment 208 | ; Alcotest.test_case 209 | "parse infinite for loops" 210 | `Quick 211 | test_parser_for_loop_infinite 212 | ; Alcotest.test_case 213 | "parse function with no arguments" 214 | `Quick 215 | test_parser_function_no_args 216 | ; Alcotest.test_case 217 | "parse function with multiple arguments" 218 | `Quick 219 | test_parser_function_multiple_args 220 | ; Alcotest.test_case 221 | "parse function declaration with no parameters" 222 | `Quick 223 | test_parser_function_declaration_no_params 224 | ; Alcotest.test_case 225 | "parse function declaration with multiple parameters" 226 | `Quick 227 | test_parser_function_declaration_multiple_params 228 | ] ) 229 | ; ( "interpreter tests" 230 | , [ Alcotest.test_case 231 | "interpret recursive function" 232 | `Slow 233 | (test_interpreter "_other" "test_fibonacci") 234 | ; Alcotest.test_case 235 | "interpret program with global variables" 236 | `Slow 237 | (test_interpreter "_other" "test_globals") 238 | ; Alcotest.test_case 239 | "interpret simple closure" 240 | `Slow 241 | (test_interpreter "_other" "test_simple_closure") 242 | ; Alcotest.test_case 243 | "interpret closure with globals" 244 | `Slow 245 | (test_interpreter "_other" "test_closure_globals") 246 | ] ) 247 | ; ( "assignment tests" 248 | , make_unit_tests "assignment" [ "syntax"; "global"; "associativity"; "local" ] ) 249 | ; ( "closure test" 250 | , make_unit_tests 251 | "closure" 252 | [ "reuse_closure_slot" 253 | ; "assign_to_shadowed_later" 254 | ; "close_over_later_variable" 255 | ; "closed_closure_in_function" 256 | ; "unused_later_closure" 257 | ; "shadow_closure_with_local" 258 | ; "unused_closure" 259 | ; "close_over_function_parameter" 260 | ; "close_over_method_parameter" 261 | ; "open_closure_in_function" 262 | ; "reference_closure_multiple_times" 263 | ; "nested_closure" 264 | ; "assign_to_closure" 265 | ] ) 266 | ; "comments test", make_unit_tests "comments" [ "line_at_eof"; "unicode" ] 267 | ; ( "variable tests" 268 | , make_unit_tests 269 | "variable" 270 | [ "in_nested_block" 271 | ; "scope_reuse_in_different_blocks" 272 | ; "use_global_in_initializer" 273 | ; "redeclare_global" 274 | ; "shadow_and_local" 275 | ; "early_bound" 276 | ; "uninitialized" 277 | ; "shadow_global" 278 | ; "in_middle_of_block" 279 | ; "shadow_local" 280 | ; "unreached_undefined" 281 | ; "redefine_global" 282 | ] ) 283 | ; "nil tests", make_unit_tests "nil" [ "literal" ] 284 | ; "if tests", make_unit_tests "if" [ "dangling_else"; "truth"; "else"; "if" ] 285 | ; ( "return tests" 286 | , make_unit_tests 287 | "return" 288 | [ "after_if" 289 | ; "after_else" 290 | ; "return_nil_if_no_value" 291 | ; "in_function" 292 | ; "after_while" 293 | ] ) 294 | ; ( "function tests" 295 | , make_unit_tests 296 | "function" 297 | [ "empty_body" 298 | ; "parameters" 299 | ; "local_recursion" 300 | ; "recursion" 301 | ; "print" 302 | ; "mutual_recursion" 303 | ] ) 304 | ; "number tests", make_unit_tests "number" [ "nan_equality"; "literals" ] 305 | ; ( "logical operators tests" 306 | , make_unit_tests "logical_operator" [ "and"; "or"; "and_truth"; "or_truth" ] ) 307 | ; "boolean tests", make_unit_tests "bool" [ "equality"; "not" ] 308 | (* ; ( "for loop tests" 309 | * , make_unit_tests 310 | * "for" 311 | * [ "return_closure"; "scope"; "syntax"; "return_inside"; "closure_in_body" ] ) *) 312 | ; "string tests", make_unit_tests "string" [ "literals"; "multiline" ] 313 | (* ; ( "while loop tests" 314 | * , make_unit_tests 315 | * "while" 316 | * [ "return_closure"; "syntax"; "return_inside"; "closure_in_body" ] ) *) 317 | ; ( "operator tests" 318 | , make_unit_tests 319 | "operator" 320 | [ "multiply" 321 | ; "negate" 322 | ; "comparison" 323 | ; "not_equals" 324 | ; "add" 325 | ; "equals" 326 | ; "divide" 327 | ; "not" 328 | ; "subtract" 329 | ] ) 330 | ; "block tests", make_unit_tests "block" [ "empty"; "scope" ] 331 | ] 332 | ;; 333 | -------------------------------------------------------------------------------- /lib/parser.ml: -------------------------------------------------------------------------------- 1 | open Base 2 | 3 | type expr = 4 | | Literal of literal 5 | | Unary of unary 6 | | Binary of binary 7 | | Call of call 8 | | Grouping of grouping 9 | | Variable of Scanner.token 10 | | Assign of assign 11 | | Logical of logical 12 | [@@deriving show { with_path = false }] 13 | 14 | and unary = 15 | { unary_operator : Scanner.token 16 | ; operand : expr 17 | } 18 | 19 | and binary = 20 | { left : expr 21 | ; binary_operator : Scanner.token 22 | ; right : expr 23 | } 24 | 25 | and call = 26 | { callee : expr 27 | ; paren : Scanner.token 28 | ; arguments : expr list 29 | } 30 | 31 | and grouping = { expression : expr } 32 | 33 | and literal = 34 | { token : Scanner.token 35 | ; value : Value.t 36 | } 37 | 38 | and assign = 39 | { name : Scanner.token 40 | ; assign_value : expr 41 | } 42 | 43 | and logical = 44 | { logical_left : expr 45 | ; operator : Scanner.token 46 | ; logical_right : expr 47 | } 48 | 49 | type t = 50 | { tokens : Scanner.token array 51 | ; mutable current : int 52 | } 53 | 54 | let rec string_of_expr expr = 55 | match expr with 56 | | Binary e -> 57 | "(" 58 | ^ string_of_expr e.left 59 | ^ " " 60 | ^ e.binary_operator.lexeme 61 | ^ " " 62 | ^ string_of_expr e.right 63 | ^ ")" 64 | | Call c -> 65 | string_of_expr c.callee 66 | ^ "(" 67 | ^ String.concat ~sep:", " (List.map ~f:(fun arg -> string_of_expr arg) c.arguments) 68 | ^ ")" 69 | | Grouping e -> "(" ^ string_of_expr e.expression ^ ")" 70 | | Literal e -> Value.to_string e.value 71 | | Unary e -> "(" ^ e.unary_operator.lexeme ^ " " ^ string_of_expr e.operand ^ ")" 72 | | Variable e -> e.lexeme 73 | | Assign e -> e.name.lexeme ^ " = " ^ string_of_expr e.assign_value 74 | | Logical e -> 75 | string_of_expr e.logical_left 76 | ^ " " 77 | ^ e.operator.lexeme 78 | ^ " " 79 | ^ string_of_expr e.logical_right 80 | ;; 81 | 82 | type statement = 83 | | Expression of expr 84 | | IfStatement of if_statement 85 | | Print of expr 86 | | ReturnStatement of return_statement 87 | | FunctionDeclaration of function_declaration 88 | | VarDeclaration of var_declaration 89 | | WhileStatement of while_statement 90 | | Block of statement list 91 | [@@deriving show { with_path = false }] 92 | 93 | and if_statement = 94 | { condition : expr 95 | ; then_branch : statement 96 | ; else_branch : statement option 97 | } 98 | 99 | and function_declaration = 100 | { fun_name : Scanner.token 101 | ; params : Scanner.token list 102 | ; fun_body : statement list 103 | } 104 | 105 | and var_declaration = 106 | { name : Scanner.token 107 | ; init : expr 108 | } 109 | 110 | and while_statement = 111 | { while_condition : expr 112 | ; body : statement 113 | } 114 | 115 | and return_statement = 116 | { keyword : Scanner.token 117 | ; value : expr option 118 | } 119 | 120 | let rec string_of_statement stmt = 121 | match stmt with 122 | | Expression e -> string_of_expr e ^ ";" 123 | | IfStatement s -> 124 | "if (" 125 | ^ string_of_expr s.condition 126 | ^ ") " 127 | ^ "{ " 128 | ^ string_of_statement s.then_branch 129 | ^ "} " 130 | ^ 131 | (match s.else_branch with 132 | | None -> "" 133 | | Some b -> " else { " ^ string_of_statement b ^ "}") 134 | | Print e -> "print " ^ string_of_expr e ^ ";" 135 | | ReturnStatement s -> 136 | "return " 137 | ^ (match s.value with 138 | | None -> "nil" 139 | | Some e -> string_of_expr e) 140 | ^ ";" 141 | | FunctionDeclaration f -> 142 | Printf.sprintf 143 | "fun %s(%s) {%s}" 144 | f.fun_name.lexeme 145 | (String.concat 146 | ~sep:", " 147 | (List.map ~f:(fun (t : Scanner.token) -> t.lexeme) f.params)) 148 | (String.concat ~sep:" " (List.map ~f:(fun s -> string_of_statement s) f.fun_body)) 149 | | VarDeclaration e -> 150 | let right_side = 151 | match e.init with 152 | | Literal l -> 153 | if Value.equal l.value LoxNil then ";" else " = " ^ string_of_expr e.init ^ ";" 154 | | _ -> " = " ^ string_of_expr e.init ^ ";" 155 | in 156 | "var " ^ e.name.lexeme ^ right_side 157 | | Block statements -> 158 | "{" ^ (List.map ~f:string_of_statement statements |> String.concat ~sep:" ") ^ "}" 159 | | WhileStatement s -> 160 | (* make sure there are always parentheses around the while condition *) 161 | let while_condition = 162 | match s.while_condition with 163 | | Literal _ -> "(" ^ string_of_expr s.while_condition ^ ")" 164 | | _ -> string_of_expr s.while_condition 165 | in 166 | "while " ^ while_condition ^ " " ^ string_of_statement s.body 167 | ;; 168 | 169 | (* For debugging *) 170 | let print_statements statements = 171 | List.iter ~f:(fun s -> string_of_statement s |> Stdio.printf "%s\n") statements 172 | ;; 173 | 174 | let make_parser tokens = { tokens = Array.of_list tokens; current = 0 } 175 | let at_end parser = parser.current >= Array.length parser.tokens - 1 176 | let previous parser = parser.tokens.(parser.current - 1) 177 | let peek parser = parser.tokens.(parser.current) 178 | 179 | let advance parser = 180 | if not (at_end parser) then parser.current <- parser.current + 1; 181 | previous parser 182 | ;; 183 | 184 | let check parser token_type = 185 | if at_end parser 186 | then false 187 | else Scanner.equal_token_type (peek parser).token_type token_type 188 | ;; 189 | 190 | let rec matches parser token_types = 191 | match token_types with 192 | | [] -> false 193 | | t :: ts -> 194 | if check parser t 195 | then ( 196 | ignore (advance parser); 197 | true) 198 | else matches parser ts 199 | ;; 200 | 201 | let consume parser token_type message = 202 | if check parser token_type 203 | then advance parser 204 | else 205 | raise 206 | (LoxError.ParseError 207 | { line = (peek parser).line; lexeme = (peek parser).lexeme; message }) 208 | ;; 209 | 210 | let synchronize parser = 211 | let _ = advance parser in 212 | let rec loop () = 213 | if at_end parser || Scanner.equal_token_type (previous parser).token_type Semicolon 214 | then parser 215 | else ( 216 | match (peek parser).token_type with 217 | | Class | Fun | Var | For | If | While | Print | Return -> parser 218 | | _ -> 219 | let _ = advance parser in 220 | loop ()) 221 | in 222 | loop () 223 | ;; 224 | 225 | (* Parses left associative binary expressions *) 226 | let rec binary next_precedence token_types parser = 227 | let expr = ref (next_precedence parser) in 228 | while matches parser token_types do 229 | let operator = previous parser in 230 | let right = next_precedence parser in 231 | expr := Binary { left = !expr; binary_operator = operator; right } 232 | done; 233 | !expr 234 | 235 | (* Parses logical and and or *) 236 | and logical next_precedence token_type parser = 237 | let expr = ref (next_precedence parser) in 238 | while matches parser [ token_type ] do 239 | let operator = previous parser in 240 | let logical_right = next_precedence parser in 241 | expr := Logical { logical_left = !expr; operator; logical_right } 242 | done; 243 | !expr 244 | 245 | (* Rule: primary → NUMBER | STRING | "false" | "true" | "nil" 246 | | "(" expression ")" 247 | | IDENTIFIER *) 248 | and primary parser = 249 | let token = advance parser in 250 | match token.token_type with 251 | | False -> Literal { token; value = LoxBool false } 252 | | True -> Literal { token; value = LoxBool true } 253 | | Nil -> Literal { token; value = LoxNil } 254 | | Number -> Literal { token; value = LoxNumber (Float.of_string token.lexeme) } 255 | | String -> Literal { token; value = token.literal } 256 | | Identifier -> Variable token 257 | | LeftParen -> 258 | let expr = expression parser in 259 | ignore (consume parser RightParen "Expect ')' after expression."); 260 | Grouping { expression = expr } 261 | | _ -> 262 | raise 263 | (LoxError.ParseError 264 | { line = token.line; lexeme = token.lexeme; message = "Expect expression." }) 265 | 266 | (* Rule: expression -> assignment *) 267 | and expression parser = assignment parser 268 | 269 | (* Rule: assignment -> IDENTIFIER '=' assignment | logical_or *) 270 | and assignment parser = 271 | let expr = logical_or parser in 272 | if matches parser [ Equal ] 273 | then ( 274 | let equals = previous parser in 275 | (* assignment is right-associative *) 276 | let value = assignment parser in 277 | match expr with 278 | | Variable name -> Assign { name; assign_value = value } 279 | | _ -> 280 | raise 281 | (LoxError.ParseError 282 | { line = equals.line 283 | ; lexeme = equals.lexeme 284 | ; message = "Invalid assignment target." 285 | })) 286 | else expr 287 | 288 | (* Rule: logical_or -> logical_and ( "or" logic_and )* *) 289 | and logical_or parser = logical logical_and Or parser 290 | (* Rule: logical_and → equality ( "and" equality )* *) 291 | and logical_and parser = logical equality And parser 292 | (* Rule: equality -> comparison ( ( "!=" | "==" ) comparison )* *) 293 | and equality parser = binary comparison [ BangEqual; EqualEqual ] parser 294 | (* Rule: comparison -> addition ( ( ">" | ">=" | "<" | "<=" ) addition )* *) 295 | and comparison parser = binary addition [ Greater; GreaterEqual; Less; LessEqual ] parser 296 | (* Rule: addition → multiplication ( ( "-" | "+" ) multiplication )* *) 297 | and addition parser = binary multiplication [ Minus; Plus ] parser 298 | (* Rule: multiplication → unary ( ( "/" | "*" ) unary )* *) 299 | and multiplication parser = binary unary [ Slash; Star ] parser 300 | 301 | (* Rule: unary → ( "!" | "-" ) unary | call *) 302 | and unary parser = 303 | if matches parser [ Bang; Minus ] 304 | then ( 305 | let operator = previous parser in 306 | let operand = unary parser in 307 | Unary { unary_operator = operator; operand }) 308 | else call parser 309 | 310 | (* Rule: call → primary ( "(" arguments? ")" )* 311 | * arguments → expression ( "," expression )* *) 312 | and call parser = 313 | let finish_call callee = 314 | let rec add_args args = 315 | if not (matches parser [ Comma ]) 316 | then List.rev args 317 | else add_args (expression parser :: args) 318 | in 319 | let arguments = 320 | if not (check parser RightParen) then add_args [ expression parser ] else [] 321 | in 322 | (* warn if there are 255+ arguments to a function *) 323 | let () = 324 | if List.length arguments >= 255 325 | then 326 | LoxError.error (peek parser).line "Function cannot have more than 255 arguments." 327 | in 328 | let paren = consume parser RightParen "Expect ')' after arguments." in 329 | Call { callee; paren; arguments } 330 | in 331 | let rec loop expr = 332 | if not (matches parser [ LeftParen ]) then expr else loop (finish_call expr) 333 | in 334 | loop (primary parser) 335 | ;; 336 | 337 | (* Rule: statement → exprStmt | forStmt | ifStmt | printStmt | returnStmt | whileStmt | block *) 338 | let rec statement parser = 339 | match (peek parser).token_type with 340 | | For -> 341 | let _ = advance parser in 342 | for_statement parser 343 | | If -> 344 | let _ = advance parser in 345 | if_statement parser 346 | | Print -> 347 | let _ = advance parser in 348 | make_statement (fun e -> Print e) parser 349 | | Return -> 350 | let _ = advance parser in 351 | return_statement parser 352 | | While -> 353 | let _ = advance parser in 354 | while_statement parser 355 | | LeftBrace -> 356 | let _ = advance parser in 357 | Block (block parser) 358 | | _ -> make_statement (fun e -> Expression e) parser 359 | 360 | and if_statement parser = 361 | let _ = consume parser LeftParen "Expect '(' after 'if'." in 362 | let condition = expression parser in 363 | let _ = consume parser RightParen "Expect ')' after 'if' condition." in 364 | let then_branch = statement parser in 365 | let else_branch = if matches parser [ Else ] then Some (statement parser) else None in 366 | IfStatement { condition; then_branch; else_branch } 367 | 368 | and while_statement parser = 369 | let _ = consume parser LeftParen "Expect '(' after 'while'." in 370 | let while_condition = expression parser in 371 | let _ = consume parser RightParen "Expect ')' after condition." in 372 | let body = statement parser in 373 | WhileStatement { while_condition; body } 374 | 375 | and for_statement parser = 376 | let _ = consume parser LeftParen "Expect '(' after 'for'." in 377 | let initial = 378 | if matches parser [ Semicolon ] 379 | then None 380 | else if matches parser [ Var ] 381 | then Some (var_declaration parser) 382 | else Some (make_statement (fun e -> Expression e) parser) 383 | in 384 | let condition = 385 | if not (check parser Semicolon) then Some (expression parser) else None 386 | in 387 | let _ = consume parser Semicolon "Expect ';' after loop condition." in 388 | let increment = 389 | if not (check parser RightParen) then Some (expression parser) else None 390 | in 391 | let _ = consume parser RightParen "Expect ')' after for clauses." in 392 | let temp_body = statement parser in 393 | let body = 394 | let true_cond = 395 | Literal 396 | { token = 397 | { token_type = True 398 | ; lexeme = "true" 399 | ; literal = LoxBool true 400 | ; line = (peek parser).line 401 | } 402 | ; value = LoxBool true 403 | } 404 | in 405 | match increment, condition, initial with 406 | | None, None, None -> WhileStatement { while_condition = true_cond; body = temp_body } 407 | | None, Some cond, None -> WhileStatement { while_condition = cond; body = temp_body } 408 | | None, None, Some init -> 409 | Block [ init; WhileStatement { while_condition = true_cond; body = temp_body } ] 410 | | None, Some cond, Some init -> 411 | Block [ init; WhileStatement { while_condition = cond; body = temp_body } ] 412 | | Some inc, None, None -> 413 | let block = 414 | match temp_body with 415 | | Block b -> 416 | let new_block = b @ [ Expression inc ] in 417 | Block new_block 418 | | _ -> Block [ temp_body; Expression inc ] 419 | in 420 | WhileStatement { while_condition = true_cond; body = block } 421 | | Some inc, Some cond, None -> 422 | let block = 423 | match temp_body with 424 | | Block b -> 425 | let new_block = b @ [ Expression inc ] in 426 | Block new_block 427 | | _ -> Block [ temp_body; Expression inc ] 428 | in 429 | WhileStatement { while_condition = cond; body = block } 430 | | Some inc, None, Some init -> 431 | let block = 432 | match temp_body with 433 | | Block b -> 434 | let new_block = b @ [ Expression inc ] in 435 | Block new_block 436 | | _ -> Block [ temp_body; Expression inc ] 437 | in 438 | Block [ init; WhileStatement { while_condition = true_cond; body = block } ] 439 | | Some inc, Some cond, Some init -> 440 | let block = 441 | match temp_body with 442 | | Block b -> 443 | let new_block = b @ [ Expression inc ] in 444 | Block new_block 445 | | _ -> Block [ temp_body; Expression inc ] 446 | in 447 | Block [ init; WhileStatement { while_condition = cond; body = block } ] 448 | in 449 | body 450 | 451 | (* Rule: returnStmt → "return" expression? ";" *) 452 | and return_statement parser = 453 | let keyword = previous parser in 454 | let value = if not (check parser Semicolon) then Some (expression parser) else None in 455 | let _ = consume parser Semicolon "Expect ; after return value." in 456 | ReturnStatement { keyword; value } 457 | 458 | and make_statement (statement_type : expr -> statement) parser = 459 | let expr = expression parser in 460 | ignore (consume parser Semicolon "Expect ';' after statement."); 461 | statement_type expr 462 | 463 | (* Rule: 464 | * funDecl → "fun" function 465 | * function → IDENTIFIER "(" parameters? ")" block 466 | * parameters → IDENTIFIER ( "," IDENTIFIER )* *) 467 | and function_declaration parser kind = 468 | let name = consume parser Identifier (Printf.sprintf "Expect %s name" kind) in 469 | let _ = consume parser LeftParen (Printf.sprintf "Expect '(' after %s name" kind) in 470 | let rec add_parameters params = 471 | if List.length params >= 255 472 | then ( 473 | let () = 474 | LoxError.error (peek parser).line "Cannot have more than 255 parameters." 475 | in 476 | []) 477 | else if not (matches parser [ Comma ]) 478 | then List.rev params 479 | else add_parameters (consume parser Identifier "Expect parameter name." :: params) 480 | in 481 | let params = 482 | if not (check parser RightParen) 483 | then add_parameters [ consume parser Identifier "Expect parameter name." ] 484 | else [] 485 | in 486 | let _ = consume parser RightParen "Expect ')' after parameters." in 487 | let _ = consume parser LeftBrace ("Expect '{' before " ^ kind ^ " body.") in 488 | let body = block parser in 489 | FunctionDeclaration { fun_name = name; params; fun_body = body } 490 | 491 | and var_declaration parser = 492 | let name = consume parser Identifier "Expect variable name." in 493 | let init = 494 | if matches parser [ Equal ] 495 | then expression parser 496 | else Literal { token = peek parser; value = LoxNil } 497 | in 498 | ignore (consume parser Semicolon "Expect ';' after variable declaration"); 499 | VarDeclaration { name; init } 500 | 501 | (* Rule: declaration → funDecl 502 | * | varDecl 503 | * | statement *) 504 | and declaration parser = 505 | try 506 | match (peek parser).token_type with 507 | | Fun -> 508 | let _ = advance parser in 509 | function_declaration parser "function" 510 | | Var -> 511 | let _ = advance parser in 512 | var_declaration parser 513 | | _ -> statement parser 514 | with 515 | | LoxError.ParseError error -> 516 | LoxError.report_parse_error error; 517 | statement (synchronize parser) 518 | 519 | and block ?(statements = []) parser = 520 | (* avoid infinite loops if a '}' is missing by adding the at_end check *) 521 | if at_end parser || check parser RightBrace 522 | then ( 523 | let _ = consume parser RightBrace "Expect '}' after block." in 524 | List.rev statements) 525 | else block ~statements:(declaration parser :: statements) parser 526 | ;; 527 | 528 | (* Parses the expression *) 529 | let rec parse ?(statements = []) parser = 530 | if at_end parser 531 | then List.rev statements 532 | else ( 533 | try parse ~statements:(declaration parser :: statements) parser with 534 | | LoxError.ParseError _ -> []) 535 | ;; 536 | --------------------------------------------------------------------------------