go: parse.sx — binary operators via Pratt precedence climbing + 9 tests [consumes-pratt]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 39s

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) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 07:39:03 +00:00
parent 976c6dd0ef
commit 750035d543
5 changed files with 143 additions and 21 deletions

View File

@@ -1,17 +1,18 @@
;; lib/go/parse.sx — Go parser. Tokenises via go-tokenize (lib/go/lex.sx), ;; 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 ;; builds canonical AST nodes per lib/guest/ast.sx, and uses
;; entry shape from lib/guest/pratt.sx for precedence climbing (Pratt). ;; pratt-op-lookup from lib/guest/pratt.sx for operator-precedence climbing.
;; ;;
;; First slice: primary expressions only — ;; Slices so far:
;; int / float / imag / string / rune literal(ast-literal VALUE) ;; 1. Primary expressions — literal / identifier → ast-literal / ast-var
;; identifier → (ast-var NAME) ;; 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 + ;; The climbing loop is per-language (see lib/guest/pratt.sx header on why)
;; pratt-op-lookup), function calls, type expressions, declarations, ;; but the entry shape and lookup are shared.
;; and statements.
;; ;;
;; All scanner locals are gp- prefixed (mirrors lib/go/lex.sx's gl- prefix): ;; All scanner locals are gp- prefixed: SX host primitives silently shadow
;; SX host primitives silently shadow guest-language defines. ;; guest-language defines.
(define (define
go-precedence-table go-precedence-table
@@ -63,4 +64,39 @@
(= ty "ident") (= ty "ident")
(do (gp-advance!) (ast-var v)) (do (gp-advance!) (ast-var v))
:else nil)))) :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))))

View File

@@ -1,10 +1,10 @@
{ {
"language": "go", "language": "go",
"total_pass": 146, "total_pass": 155,
"total": 146, "total": 155,
"suites": [ "suites": [
{"name":"lex","pass":129,"total":129,"status":"ok"}, {"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":"types","pass":0,"total":0,"status":"pending"},
{"name":"eval","pass":0,"total":0,"status":"pending"}, {"name":"eval","pass":0,"total":0,"status":"pending"},
{"name":"runtime","pass":0,"total":0,"status":"pending"}, {"name":"runtime","pass":0,"total":0,"status":"pending"},

View File

@@ -1,11 +1,11 @@
# Go-on-SX Scoreboard # Go-on-SX Scoreboard
**Total: 146 / 146 tests passing** **Total: 155 / 155 tests passing**
| | Suite | Pass | Total | | | Suite | Pass | Total |
|---|---|---|---| |---|---|---|---|
| ✅ | lex | 129 | 129 | | ✅ | lex | 129 | 129 |
| ✅ | parse | 17 | 17 | | ✅ | parse | 26 | 26 |
| ⬜ | types | 0 | 0 | | ⬜ | types | 0 | 0 |
| ⬜ | eval | 0 | 0 | | ⬜ | eval | 0 | 0 |
| ⬜ | runtime | 0 | 0 | | ⬜ | runtime | 0 | 0 |

View File

@@ -34,10 +34,87 @@
(go-parse-test "ident: with digit" (go-parse "x123") (ast-var "x123")) (go-parse-test "ident: with digit" (go-parse "x123") (ast-var "x123"))
;; ── primary: non-primary returns nil ────────────────────────────── ;; ── primary: non-primary returns nil ──────────────────────────────
(go-parse-test "non-primary: '+'" (go-parse "+") nil) (go-parse-test
(go-parse-test "non-primary: empty" (go-parse "") nil) "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 ──────────────────────────────────────────────────────── ;; ── 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 (define
go-parse-test-summary go-parse-test-summary
(str "parse " go-parse-test-pass "/" go-parse-test-count)) (str "parse " go-parse-test-pass "/" go-parse-test-count))

View File

@@ -157,8 +157,9 @@ Progress-log line → push `origin/loops/go`.
- [x] Parser scaffold + Go operator-precedence table (entry shape from - [x] Parser scaffold + Go operator-precedence table (entry shape from
`lib/guest/pratt.sx`) + primary expressions (int/float/imag/string/ `lib/guest/pratt.sx`) + primary expressions (int/float/imag/string/
rune/ident → ast-literal / ast-var via `lib/guest/ast.sx`). rune/ident → ast-literal / ast-var via `lib/guest/ast.sx`).
- [ ] Binary operators (Pratt precedence climbing using - [x] Binary operators (Pratt precedence climbing using `pratt-op-lookup`
`pratt-op-lookup` + Go precedence table). + 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`). - [ ] Unary operators (`!x`, `-x`, `^x`, `*p`, `&v`, `<-ch`).
- [ ] Function calls `f(a, b)` and member access `x.field`. - [ ] Function calls `f(a, b)` and member access `x.field`.
- [ ] Index `x[i]` and slice `x[a:b]`/`x[a:b:c]`. - [ ] 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`. assign, short-decl `:=`, send `ch <- v`, recv `<-ch`.
- [ ] End-to-end: hello-world, fibonacci, FizzBuzz, goroutine ping-pong, - [ ] End-to-end: hello-world, fibonacci, FizzBuzz, goroutine ping-pong,
struct + method. 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`) ⬜ ### Phase 3 — Bidirectional type checker, MVP (`lib/go/types.sx`) ⬜
- **Independent implementation.** Do NOT use lib/guest/static-types- - **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._ _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. - 2026-05-27 — Phase 2 first slice: `lib/go/parse.sx` parser scaffold.
Defines `go-precedence-table` using `lib/guest/pratt.sx` entry shape Defines `go-precedence-table` using `lib/guest/pratt.sx` entry shape
`(NAME PREC ASSOC)` — five Go precedence levels, all left-associative `(NAME PREC ASSOC)` — five Go precedence levels, all left-associative