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),
;; 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))))

View File

@@ -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"},

View File

@@ -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 |

View File

@@ -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))

View File

@@ -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