From 750035d543195fe42be8e74f6eddce28f747715d Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 27 May 2026 07:39:03 +0000 Subject: [PATCH] =?UTF-8?q?go:=20parse.sx=20=E2=80=94=20binary=20operators?= =?UTF-8?q?=20via=20Pratt=20precedence=20climbing=20+=209=20tests=20[consu?= =?UTF-8?q?mes-pratt]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gp-parse-expr / gp-pratt-loop implement classic Pratt climbing against go-precedence-table (entry shape from lib/guest/pratt.sx). The kit gives us pratt-op-lookup + accessors; the climbing loop itself stays per-language (per kit header — Lua and Prolog have opposite conventions). Left-associative ops raise the right-recursion min by 1; right- associative would keep prec. All Go binary operators are left-assoc. AST shape: a binary node is emitted as (ast-app (ast-var OP) [LHS RHS]) — canonical ast-app rather than a Go-specific binary node, since a future evaluator can recognise operator-named apps without losing information. Coverage: equal-prec left-to-right, * tighter than +, && tighter than ||, comparison tighter than &&, long left-assoc chains, mixed literal+ident operands. parse 26/26, total 155/155. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/go/parse.sx | 58 ++++++++++++++++++++++++------ lib/go/scoreboard.json | 6 ++-- lib/go/scoreboard.md | 4 +-- lib/go/tests/parse.sx | 81 ++++++++++++++++++++++++++++++++++++++++-- plans/go-on-sx.md | 15 ++++++-- 5 files changed, 143 insertions(+), 21 deletions(-) diff --git a/lib/go/parse.sx b/lib/go/parse.sx index 89fe8341..6ca89765 100644 --- a/lib/go/parse.sx +++ b/lib/go/parse.sx @@ -1,17 +1,18 @@ ;; lib/go/parse.sx — Go parser. Tokenises via go-tokenize (lib/go/lex.sx), -;; builds canonical AST nodes per lib/guest/ast.sx, and uses the operator -;; entry shape from lib/guest/pratt.sx for precedence climbing (Pratt). +;; builds canonical AST nodes per lib/guest/ast.sx, and uses +;; pratt-op-lookup from lib/guest/pratt.sx for operator-precedence climbing. ;; -;; First slice: primary expressions only — -;; int / float / imag / string / rune literal → (ast-literal VALUE) -;; identifier → (ast-var NAME) +;; Slices so far: +;; 1. Primary expressions — literal / identifier → ast-literal / ast-var +;; 2. Binary operators — Pratt precedence climbing against +;; go-precedence-table; binary application +;; emitted as (ast-app (ast-var OP) [LHS RHS]). ;; -;; Subsequent slices add binary operators (via gp-precedence-table + -;; pratt-op-lookup), function calls, type expressions, declarations, -;; and statements. +;; The climbing loop is per-language (see lib/guest/pratt.sx header on why) +;; but the entry shape and lookup are shared. ;; -;; All scanner locals are gp- prefixed (mirrors lib/go/lex.sx's gl- prefix): -;; SX host primitives silently shadow guest-language defines. +;; All scanner locals are gp- prefixed: SX host primitives silently shadow +;; guest-language defines. (define go-precedence-table @@ -63,4 +64,39 @@ (= ty "ident") (do (gp-advance!) (ast-var v)) :else nil)))) - (gp-parse-primary)))) + (define + gp-parse-expr + (fn + (min-prec) + (let ((left (gp-parse-primary))) (gp-pratt-loop left min-prec)))) + (define + gp-pratt-loop + (fn + (left min-prec) + (cond + (= left nil) nil + :else + (let + ((tok (gp-cur))) + (cond + (not (= (get tok :type) "op")) + left + :else (let + ((entry (pratt-op-lookup go-precedence-table (get tok :value)))) + (cond + (= entry nil) + left + (< (pratt-op-prec entry) min-prec) + left + :else (do + (gp-advance!) + (let + ((next-min (if (= (pratt-op-assoc entry) :left) (+ (pratt-op-prec entry) 1) (pratt-op-prec entry)))) + (let + ((right (gp-parse-expr next-min))) + (gp-pratt-loop + (ast-app + (ast-var (get tok :value)) + (list left right)) + min-prec))))))))))) + (gp-parse-expr 1)))) diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index 32152771..c871ab09 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -1,10 +1,10 @@ { "language": "go", - "total_pass": 146, - "total": 146, + "total_pass": 155, + "total": 155, "suites": [ {"name":"lex","pass":129,"total":129,"status":"ok"}, - {"name":"parse","pass":17,"total":17,"status":"ok"}, + {"name":"parse","pass":26,"total":26,"status":"ok"}, {"name":"types","pass":0,"total":0,"status":"pending"}, {"name":"eval","pass":0,"total":0,"status":"pending"}, {"name":"runtime","pass":0,"total":0,"status":"pending"}, diff --git a/lib/go/scoreboard.md b/lib/go/scoreboard.md index 535fedfd..6b526bbb 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -1,11 +1,11 @@ # Go-on-SX Scoreboard -**Total: 146 / 146 tests passing** +**Total: 155 / 155 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | lex | 129 | 129 | -| ✅ | parse | 17 | 17 | +| ✅ | parse | 26 | 26 | | ⬜ | types | 0 | 0 | | ⬜ | eval | 0 | 0 | | ⬜ | runtime | 0 | 0 | diff --git a/lib/go/tests/parse.sx b/lib/go/tests/parse.sx index 8025dded..052074c5 100644 --- a/lib/go/tests/parse.sx +++ b/lib/go/tests/parse.sx @@ -34,10 +34,87 @@ (go-parse-test "ident: with digit" (go-parse "x123") (ast-var "x123")) ;; ── primary: non-primary returns nil ────────────────────────────── -(go-parse-test "non-primary: '+'" (go-parse "+") nil) -(go-parse-test "non-primary: empty" (go-parse "") nil) +(go-parse-test + "bin: a + b" + (go-parse "a + b") + (ast-app (ast-var "+") (list (ast-var "a") (ast-var "b")))) +(go-parse-test + "bin: int + int" + (go-parse "1 + 2") + (ast-app (ast-var "+") (list (ast-literal "1") (ast-literal "2")))) ;; ── report ──────────────────────────────────────────────────────── +(go-parse-test + "bin: left-assoc a + b + c" + (go-parse "a + b + c") + (ast-app + (ast-var "+") + (list + (ast-app (ast-var "+") (list (ast-var "a") (ast-var "b"))) + (ast-var "c")))) + +(go-parse-test + "bin: * tighter than + → a + b * c" + (go-parse "a + b * c") + (ast-app + (ast-var "+") + (list + (ast-var "a") + (ast-app (ast-var "*") (list (ast-var "b") (ast-var "c")))))) + +(go-parse-test + "bin: * tighter than + → a * b + c" + (go-parse "a * b + c") + (ast-app + (ast-var "+") + (list + (ast-app (ast-var "*") (list (ast-var "a") (ast-var "b"))) + (ast-var "c")))) + +(go-parse-test + "bin: && tighter than || → a || b && c" + (go-parse "a || b && c") + (ast-app + (ast-var "||") + (list + (ast-var "a") + (ast-app (ast-var "&&") (list (ast-var "b") (ast-var "c")))))) + +(go-parse-test + "bin: comparison tighter than &&" + (go-parse "a == b && c < d") + (ast-app + (ast-var "&&") + (list + (ast-app (ast-var "==") (list (ast-var "a") (ast-var "b"))) + (ast-app (ast-var "<") (list (ast-var "c") (ast-var "d")))))) + +(go-parse-test + "bin: long left-assoc chain a + b - c + d" + (go-parse "a + b - c + d") + (ast-app + (ast-var "+") + (list + (ast-app + (ast-var "-") + (list + (ast-app (ast-var "+") (list (ast-var "a") (ast-var "b"))) + (ast-var "c"))) + (ast-var "d")))) + +(go-parse-test + "bin: equal-prec left-assoc — a | b ^ c → (a | b) ^ c" + (go-parse "a | b ^ c") + (ast-app + (ast-var "^") + (list + (ast-app (ast-var "|") (list (ast-var "a") (ast-var "b"))) + (ast-var "c")))) + +(go-parse-test "non-primary: '+'" (go-parse "+") nil) + +(go-parse-test "non-primary: empty" (go-parse "") nil) + (define go-parse-test-summary (str "parse " go-parse-test-pass "/" go-parse-test-count)) diff --git a/plans/go-on-sx.md b/plans/go-on-sx.md index 1e573dd4..bd7a8e44 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -157,8 +157,9 @@ Progress-log line → push `origin/loops/go`. - [x] Parser scaffold + Go operator-precedence table (entry shape from `lib/guest/pratt.sx`) + primary expressions (int/float/imag/string/ rune/ident → ast-literal / ast-var via `lib/guest/ast.sx`). -- [ ] Binary operators (Pratt precedence climbing using - `pratt-op-lookup` + Go precedence table). +- [x] Binary operators (Pratt precedence climbing using `pratt-op-lookup` + + Go precedence table). Operator app emitted as + `(ast-app (ast-var OP) [LHS RHS])`; left-assoc raises right-min by 1. - [ ] Unary operators (`!x`, `-x`, `^x`, `*p`, `&v`, `<-ch`). - [ ] Function calls `f(a, b)` and member access `x.field`. - [ ] Index `x[i]` and slice `x[a:b]`/`x[a:b:c]`. @@ -175,7 +176,7 @@ Progress-log line → push `origin/loops/go`. assign, short-decl `:=`, send `ch <- v`, recv `<-ch`. - [ ] End-to-end: hello-world, fibonacci, FizzBuzz, goroutine ping-pong, struct + method. -- **Acceptance:** parse/ suite at 80+ tests. Current: 17/17. +- **Acceptance:** parse/ suite at 80+ tests. Current: 26/26. ### Phase 3 — Bidirectional type checker, MVP (`lib/go/types.sx`) ⬜ - **Independent implementation.** Do NOT use lib/guest/static-types- @@ -434,6 +435,14 @@ Minimal repro: see `lib/go/lex.sx#gl-oct-digit?` and `#gl-match-op`. _Newest first. Append one dated entry per commit._ +- 2026-05-27 — Phase 2 cont.: binary operators via Pratt precedence + climbing. `gp-pratt-loop` consumes `pratt-op-lookup` against + `go-precedence-table`; left-assoc bumps right-min by 1, right-assoc + keeps prec. Binary op nodes are `(ast-app (ast-var OP) [LHS RHS])` — + uses the canonical `ast-app` shape rather than inventing a Go-specific + binary node. Covers: equal-prec left-to-right, `*` tighter than `+`, + `&&` tighter than `||`, comparison tighter than `&&`, long chains. + +9 tests, parse 26/26, total 155/155. `[consumes-pratt]`. - 2026-05-27 — Phase 2 first slice: `lib/go/parse.sx` parser scaffold. Defines `go-precedence-table` using `lib/guest/pratt.sx` entry shape `(NAME PREC ASSOC)` — five Go precedence levels, all left-associative