From baea31dc8f184bca3594d0154aca00a84bc161fd Mon Sep 17 00:00:00 2001 From: Vladislav Date: Sat, 21 Mar 2026 19:58:30 +0300 Subject: [PATCH 1/4] Fix unary minus parsing --- lib/lexer.ml | 3 --- lib/parser.ml | 47 +++++++++++++++++++++------------------------ lib/parser.mli | 1 - test/test_MiniML.ml | 20 ++++++++++++++++++- 4 files changed, 41 insertions(+), 30 deletions(-) diff --git a/lib/lexer.ml b/lib/lexer.ml index f9748ec..6e2adbd 100644 --- a/lib/lexer.ml +++ b/lib/lexer.ml @@ -132,9 +132,6 @@ module Lexer = struct | Some '>' -> let _ = get lexer in ARROW - | Some d when d >= '0' && d <= '9' -> - let n = read_int lexer in - INT (-n) | _ -> MINUS) | c when c >= '0' && c <= '9' -> let n = Char.code c - Char.code '0' in diff --git a/lib/parser.ml b/lib/parser.ml index 2333454..019f016 100644 --- a/lib/parser.ml +++ b/lib/parser.ml @@ -168,33 +168,30 @@ module Parser = struct | _ -> e and parse_add parser = - let e = parse_mul parser in - match parser.current_token with - | PLUS -> - next parser; - BinOp (Add, e, parse_add parser) - | MINUS -> - next parser; - BinOp (Sub, e, parse_add parser) - | _ -> e + let rec loop left = + match parser.current_token with + | PLUS -> + next parser; + loop (BinOp (Add, left, parse_mul parser)) + | MINUS -> + next parser; + loop (BinOp (Sub, left, parse_mul parser)) + | _ -> left + in + loop (parse_mul parser) and parse_mul parser = - let e = parse_unop parser in - match parser.current_token with - | STAR -> - next parser; - BinOp (Mul, e, parse_mul parser) - | SLASH -> - next parser; - BinOp (Div, e, parse_mul parser) - | _ -> e - - and parse_unop parser = - match parser.current_token with - | MINUS -> - next parser; - UnOp (Neg, parse_unop parser) - | _ -> parse_unary parser + let rec loop left = + match parser.current_token with + | STAR -> + next parser; + loop (BinOp (Mul, left, parse_unary parser)) + | SLASH -> + next parser; + loop (BinOp (Div, left, parse_unary parser)) + | _ -> left + in + loop (parse_unary parser) and parse_unary parser = match parser.current_token with diff --git a/lib/parser.mli b/lib/parser.mli index 26a3f6b..650e16e 100644 --- a/lib/parser.mli +++ b/lib/parser.mli @@ -17,7 +17,6 @@ module Parser : sig val parse_rel : parser -> Ast.expr val parse_add : parser -> Ast.expr val parse_mul : parser -> Ast.expr - val parse_unop : parser -> Ast.expr val parse_unary : parser -> Ast.expr val parse_app : parser -> Ast.expr val parse_atom : parser -> Ast.expr diff --git a/test/test_MiniML.ml b/test/test_MiniML.ml index 467776c..b2fd5db 100644 --- a/test/test_MiniML.ml +++ b/test/test_MiniML.ml @@ -155,7 +155,8 @@ let lexer_tests = Lexer.Lexer.OR; Lexer.Lexer.IDENT "x_1"; Lexer.Lexer.NEQ; - Lexer.Lexer.INT (-42); + Lexer.Lexer.MINUS; + Lexer.Lexer.INT 42; Lexer.Lexer.EOF; ] (collect_tokens @@ -266,6 +267,20 @@ let parser_tests = ( Ast.Or, Ast.UnOp (Ast.Not, Ast.Bool false), Ast.BinOp (Ast.And, Ast.Bool true, Ast.Bool false) )) ); + ( "unary minus binds tighter than addition", + `Quick, + expect_parse "-1 + 2" + (Ast.BinOp (Ast.Add, Ast.UnOp (Ast.Neg, Ast.Int 1), Ast.Int 2)) ); + ( "subtraction associates left", + `Quick, + expect_parse "1 - 2 - 3" + (Ast.BinOp + (Ast.Sub, Ast.BinOp (Ast.Sub, Ast.Int 1, Ast.Int 2), Ast.Int 3)) ); + ( "division associates left", + `Quick, + expect_parse "8 / 4 / 2" + (Ast.BinOp + (Ast.Div, Ast.BinOp (Ast.Div, Ast.Int 8, Ast.Int 4), Ast.Int 2)) ); ( "trailing token is rejected", `Quick, expect_parse_failure "1 < 2 < 3" "Unexpected trailing token: LT" ); @@ -304,6 +319,9 @@ let eval_tests = ( "integer comparisons", `Quick, expect_run "1 < 2 && 2 <= 2 && 3 > 2 && 3 >= 3" "true" ); + ("negative literal plus positive", `Quick, expect_run "-1 + 2" "1"); + ("subtraction associates left at runtime", `Quick, expect_run "1 - 2 - 3" "-4"); + ("division associates left at runtime", `Quick, expect_run "8 / 4 / 2" "1"); ("unary minus over arithmetic", `Quick, expect_run "-(1 + 2)" "-3"); ("not false", `Quick, expect_run "not false" "true"); ("if false branch", `Quick, expect_run "if false then 1 else 2" "2"); From 1563c369b1b3976d792c73724180b3d45a2bf0ca Mon Sep 17 00:00:00 2001 From: Vladislav Date: Sat, 21 Mar 2026 19:58:30 +0300 Subject: [PATCH 2/4] Fix unary minus parsing --- lib/lexer.ml | 3 --- lib/parser.ml | 47 +++++++++++++++++++++------------------------ lib/parser.mli | 1 - test/test_MiniML.ml | 22 ++++++++++++++++++++- 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/lib/lexer.ml b/lib/lexer.ml index f9748ec..6e2adbd 100644 --- a/lib/lexer.ml +++ b/lib/lexer.ml @@ -132,9 +132,6 @@ module Lexer = struct | Some '>' -> let _ = get lexer in ARROW - | Some d when d >= '0' && d <= '9' -> - let n = read_int lexer in - INT (-n) | _ -> MINUS) | c when c >= '0' && c <= '9' -> let n = Char.code c - Char.code '0' in diff --git a/lib/parser.ml b/lib/parser.ml index 2333454..019f016 100644 --- a/lib/parser.ml +++ b/lib/parser.ml @@ -168,33 +168,30 @@ module Parser = struct | _ -> e and parse_add parser = - let e = parse_mul parser in - match parser.current_token with - | PLUS -> - next parser; - BinOp (Add, e, parse_add parser) - | MINUS -> - next parser; - BinOp (Sub, e, parse_add parser) - | _ -> e + let rec loop left = + match parser.current_token with + | PLUS -> + next parser; + loop (BinOp (Add, left, parse_mul parser)) + | MINUS -> + next parser; + loop (BinOp (Sub, left, parse_mul parser)) + | _ -> left + in + loop (parse_mul parser) and parse_mul parser = - let e = parse_unop parser in - match parser.current_token with - | STAR -> - next parser; - BinOp (Mul, e, parse_mul parser) - | SLASH -> - next parser; - BinOp (Div, e, parse_mul parser) - | _ -> e - - and parse_unop parser = - match parser.current_token with - | MINUS -> - next parser; - UnOp (Neg, parse_unop parser) - | _ -> parse_unary parser + let rec loop left = + match parser.current_token with + | STAR -> + next parser; + loop (BinOp (Mul, left, parse_unary parser)) + | SLASH -> + next parser; + loop (BinOp (Div, left, parse_unary parser)) + | _ -> left + in + loop (parse_unary parser) and parse_unary parser = match parser.current_token with diff --git a/lib/parser.mli b/lib/parser.mli index 26a3f6b..650e16e 100644 --- a/lib/parser.mli +++ b/lib/parser.mli @@ -17,7 +17,6 @@ module Parser : sig val parse_rel : parser -> Ast.expr val parse_add : parser -> Ast.expr val parse_mul : parser -> Ast.expr - val parse_unop : parser -> Ast.expr val parse_unary : parser -> Ast.expr val parse_app : parser -> Ast.expr val parse_atom : parser -> Ast.expr diff --git a/test/test_MiniML.ml b/test/test_MiniML.ml index 467776c..d755e2d 100644 --- a/test/test_MiniML.ml +++ b/test/test_MiniML.ml @@ -155,7 +155,8 @@ let lexer_tests = Lexer.Lexer.OR; Lexer.Lexer.IDENT "x_1"; Lexer.Lexer.NEQ; - Lexer.Lexer.INT (-42); + Lexer.Lexer.MINUS; + Lexer.Lexer.INT 42; Lexer.Lexer.EOF; ] (collect_tokens @@ -266,6 +267,20 @@ let parser_tests = ( Ast.Or, Ast.UnOp (Ast.Not, Ast.Bool false), Ast.BinOp (Ast.And, Ast.Bool true, Ast.Bool false) )) ); + ( "unary minus binds tighter than addition", + `Quick, + expect_parse "-1 + 2" + (Ast.BinOp (Ast.Add, Ast.UnOp (Ast.Neg, Ast.Int 1), Ast.Int 2)) ); + ( "subtraction associates left", + `Quick, + expect_parse "1 - 2 - 3" + (Ast.BinOp + (Ast.Sub, Ast.BinOp (Ast.Sub, Ast.Int 1, Ast.Int 2), Ast.Int 3)) ); + ( "division associates left", + `Quick, + expect_parse "8 / 4 / 2" + (Ast.BinOp + (Ast.Div, Ast.BinOp (Ast.Div, Ast.Int 8, Ast.Int 4), Ast.Int 2)) ); ( "trailing token is rejected", `Quick, expect_parse_failure "1 < 2 < 3" "Unexpected trailing token: LT" ); @@ -304,6 +319,11 @@ let eval_tests = ( "integer comparisons", `Quick, expect_run "1 < 2 && 2 <= 2 && 3 > 2 && 3 >= 3" "true" ); + ("negative literal plus positive", `Quick, expect_run "-1 + 2" "1"); + ( "subtraction associates left at runtime", + `Quick, + expect_run "1 - 2 - 3" "-4" ); + ("division associates left at runtime", `Quick, expect_run "8 / 4 / 2" "1"); ("unary minus over arithmetic", `Quick, expect_run "-(1 + 2)" "-3"); ("not false", `Quick, expect_run "not false" "true"); ("if false branch", `Quick, expect_run "if false then 1 else 2" "2"); From f363d099c29f20bf4ee89a7937f0ea09abafd9e0 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Sat, 21 Mar 2026 20:18:28 +0300 Subject: [PATCH 3/4] ci: pin ocamlformat to 0.28.1 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 96d9610..b908ab8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,7 @@ jobs: - name: Install dependencies run: | opam install . --deps-only --with-test -y - opam install ocamlformat -y + opam install ocamlformat.0.28.1 -y - name: Check formatting run: | From 1dc5cf0b6f0450e69f338e3fcdbcb8ea372d3039 Mon Sep 17 00:00:00 2001 From: Vladislav Date: Sat, 21 Mar 2026 20:25:01 +0300 Subject: [PATCH 4/4] style: format test suite --- test/test_MiniML.ml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/test_MiniML.ml b/test/test_MiniML.ml index b2fd5db..d755e2d 100644 --- a/test/test_MiniML.ml +++ b/test/test_MiniML.ml @@ -320,7 +320,9 @@ let eval_tests = `Quick, expect_run "1 < 2 && 2 <= 2 && 3 > 2 && 3 >= 3" "true" ); ("negative literal plus positive", `Quick, expect_run "-1 + 2" "1"); - ("subtraction associates left at runtime", `Quick, expect_run "1 - 2 - 3" "-4"); + ( "subtraction associates left at runtime", + `Quick, + expect_run "1 - 2 - 3" "-4" ); ("division associates left at runtime", `Quick, expect_run "8 / 4 / 2" "1"); ("unary minus over arithmetic", `Quick, expect_run "-(1 + 2)" "-3"); ("not false", `Quick, expect_run "not false" "true");