go: parse.sx — go/defer/send/for-range + 9 tests [shapes-scheduler]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
Adds Go's concurrency + iteration primitives to the statement parser:
go EXPR → (list :go EXPR)
defer EXPR → (list :defer EXPR)
ch <- v → (list :send CHAN VALUE)
for range COLL { ... } → (list :range-for nil nil nil COLL BODY)
for k := range C { ... } → (list :range-for :short-decl KEY nil COLL BODY)
for k, v := range C { } → (list :range-for :short-decl KEY VAL COLL BODY)
for k, v = range C { ... } → (list :range-for :assign KEY VAL COLL BODY)
gp-for-find-range pre-scans the for-header (to '{' or eof) looking
for the 'range' keyword; if present, dispatches to gp-parse-for-range
which handles the four range shapes. C-style and while-like and
infinite are now in gp-parse-for-c-style — gp-parse-for is just a
dispatcher.
Send statement detection lives in the LHS-list branch of gp-parse-stmt:
after parsing a single LHS expression, '<-' triggers (:send LHS RHS).
Channel-recv (`<-ch`) was already parsed as unary `<-` in the expression
layer, so both directions cover.
This is the **chiselling-relevant iteration** for the scheduler sister
kit: the AST shapes Go-on-SX will eventually feed into the kit's
scheduler primitives (sched-spawn, sched-defer, chan-op) have landed.
Sister-plan diary updated with three design insights:
* :go / :defer both wrap a single expr — kit's sched-spawn should
accept a thunk uniformly across Erlang's spawn(M,F,A) and Go's
go fn().
* :send carries CHAN+VALUE symmetrically with the unary <- recv —
both reduce to (chan-op direction chan value) in the kit.
* `for v := range ch` uses the same :range-for shape as range-over-
slice; the scheduler kit's range dispatch is where chan-recv ⇄
iteration polymorphism lives.
parse 161/161, total 290/290.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
102
lib/go/parse.sx
102
lib/go/parse.sx
@@ -765,21 +765,76 @@
|
||||
(do (gp-advance!) (set! els (gp-parse-block-body)))))
|
||||
(list :if cnd then els))))
|
||||
(define
|
||||
gp-parse-for
|
||||
;; Caller has consumed 'for'.
|
||||
;; for { BODY } — infinite
|
||||
;; for COND { BODY } — while-like
|
||||
;; for INIT; COND; POST { BODY } — C-style
|
||||
;; for k, v := range coll { BODY } — deferred (range)
|
||||
;; AST: (list :for INIT COND POST BODY); any of INIT/COND/POST may be nil.
|
||||
gp-for-find-range
|
||||
;; Scan tokens from current idx looking for the `range` keyword;
|
||||
;; stops at '{' or eof. Restores idx before returning. Returns
|
||||
;; true iff the for-header contains a range clause.
|
||||
(fn
|
||||
()
|
||||
(let ((saved-idx gp-idx) (found false))
|
||||
(define
|
||||
gp-scan-rng
|
||||
(fn
|
||||
()
|
||||
(set! gp-no-comp-lit (+ gp-no-comp-lit 1))
|
||||
(let ((init nil) (cnd nil) (post nil) (body nil))
|
||||
(cond
|
||||
(and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
|
||||
(or (= (gp-tok-type) "eof")
|
||||
(and (= (gp-tok-type) "op")
|
||||
(= (gp-tok-value) "{")))
|
||||
nil
|
||||
(and (= (gp-tok-type) "keyword")
|
||||
(= (gp-tok-value) "range"))
|
||||
(set! found true)
|
||||
:else (do (gp-advance!) (gp-scan-rng)))))
|
||||
(gp-scan-rng)
|
||||
(set! gp-idx saved-idx)
|
||||
found)))
|
||||
(define
|
||||
gp-parse-for-range
|
||||
;; Range form:
|
||||
;; for range COLL { ... }
|
||||
;; for k := range COLL { ... }
|
||||
;; for k, v := range COLL { ... }
|
||||
;; for k, v = range COLL { ... } (reuse existing vars)
|
||||
;; AST: (list :range-for DECL-KIND KEY VALUE COLL BODY)
|
||||
;; DECL-KIND : :short-decl | :assign | nil (no kv)
|
||||
;; KEY/VALUE : ast-var nodes or nil
|
||||
(fn
|
||||
()
|
||||
(let ((decl-kind nil) (key nil) (value nil)
|
||||
(coll nil) (body nil))
|
||||
(cond
|
||||
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "range"))
|
||||
nil
|
||||
:else
|
||||
(do
|
||||
(when (= (gp-tok-type) "ident")
|
||||
(set! key (ast-var (gp-tok-value)))
|
||||
(gp-advance!))
|
||||
(when (and (= (gp-tok-type) "op") (= (gp-tok-value) ","))
|
||||
(gp-advance!)
|
||||
(when (= (gp-tok-type) "ident")
|
||||
(set! value (ast-var (gp-tok-value)))
|
||||
(gp-advance!)))
|
||||
(cond
|
||||
(and (= (gp-tok-type) "op") (= (gp-tok-value) ":="))
|
||||
(do (gp-advance!) (set! decl-kind :short-decl))
|
||||
(and (= (gp-tok-type) "op") (= (gp-tok-value) "="))
|
||||
(do (gp-advance!) (set! decl-kind :assign)))))
|
||||
(when (and (= (gp-tok-type) "keyword") (= (gp-tok-value) "range"))
|
||||
(gp-advance!))
|
||||
(set! coll (gp-parse-expr 1))
|
||||
(set! gp-no-comp-lit (- gp-no-comp-lit 1))
|
||||
(when (and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
|
||||
(gp-advance!)
|
||||
(set! body (gp-parse-block-body)))
|
||||
(list :range-for decl-kind key value coll body))))
|
||||
(define
|
||||
gp-parse-for-c-style
|
||||
;; for COND { ... } OR for INIT; COND; POST { ... }
|
||||
;; AST: (list :for INIT COND POST BODY).
|
||||
(fn
|
||||
()
|
||||
(let ((init nil) (cnd nil) (post nil) (body nil))
|
||||
(let ((first (gp-parse-stmt)))
|
||||
(cond
|
||||
(= (gp-tok-type) "semi")
|
||||
@@ -795,12 +850,26 @@
|
||||
(when (not (and (= (gp-tok-type) "op")
|
||||
(= (gp-tok-value) "{")))
|
||||
(set! post (gp-parse-stmt))))
|
||||
:else (set! cnd first))))
|
||||
:else (set! cnd first)))
|
||||
(set! gp-no-comp-lit (- gp-no-comp-lit 1))
|
||||
(when (and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
|
||||
(gp-advance!)
|
||||
(set! body (gp-parse-block-body)))
|
||||
(list :for init cnd post body))))
|
||||
(define
|
||||
gp-parse-for
|
||||
;; Caller has consumed 'for'. Dispatches on header shape.
|
||||
(fn
|
||||
()
|
||||
(set! gp-no-comp-lit (+ gp-no-comp-lit 1))
|
||||
(cond
|
||||
(and (= (gp-tok-type) "op") (= (gp-tok-value) "{"))
|
||||
(do
|
||||
(set! gp-no-comp-lit (- gp-no-comp-lit 1))
|
||||
(gp-advance!)
|
||||
(list :for nil nil nil (gp-parse-block-body)))
|
||||
(gp-for-find-range) (gp-parse-for-range)
|
||||
:else (gp-parse-for-c-style))))
|
||||
(define
|
||||
gp-stmt-assign-ops
|
||||
;; Compound assignment operators per Go spec § Assignment operations.
|
||||
@@ -838,6 +907,10 @@
|
||||
(do (gp-advance!) (list :break nil))
|
||||
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "continue"))
|
||||
(do (gp-advance!) (list :continue nil))
|
||||
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "go"))
|
||||
(do (gp-advance!) (list :go (gp-parse-expr 1)))
|
||||
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "defer"))
|
||||
(do (gp-advance!) (list :defer (gp-parse-expr 1)))
|
||||
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "if"))
|
||||
(do (gp-advance!) (gp-parse-if))
|
||||
(and (= (gp-tok-type) "keyword") (= (gp-tok-value) "for"))
|
||||
@@ -881,6 +954,13 @@
|
||||
(let ((op (gp-tok-value)))
|
||||
(gp-advance!)
|
||||
(list :inc-dec op lhs))
|
||||
;; Channel send statement: ch <- v (Go spec § Send
|
||||
;; statements). Only valid when LHS is a single expr.
|
||||
(and (= (gp-tok-type) "op") (= (gp-tok-value) "<-")
|
||||
(= (len lhs-list) 1))
|
||||
(do
|
||||
(gp-advance!)
|
||||
(list :send lhs (gp-parse-expr 1)))
|
||||
:else
|
||||
;; Plain expression statement — return the single expr.
|
||||
;; (If somehow there was a comma chain without =/:=, just
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"language": "go",
|
||||
"total_pass": 281,
|
||||
"total": 281,
|
||||
"total_pass": 290,
|
||||
"total": 290,
|
||||
"suites": [
|
||||
{"name":"lex","pass":129,"total":129,"status":"ok"},
|
||||
{"name":"parse","pass":152,"total":152,"status":"ok"},
|
||||
{"name":"parse","pass":161,"total":161,"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"},
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Go-on-SX Scoreboard
|
||||
|
||||
**Total: 281 / 281 tests passing**
|
||||
**Total: 290 / 290 tests passing**
|
||||
|
||||
| | Suite | Pass | Total |
|
||||
|---|---|---|---|
|
||||
| ✅ | lex | 129 | 129 |
|
||||
| ✅ | parse | 152 | 152 |
|
||||
| ✅ | parse | 161 | 161 |
|
||||
| ⬜ | types | 0 | 0 |
|
||||
| ⬜ | eval | 0 | 0 |
|
||||
| ⬜ | runtime | 0 | 0 |
|
||||
|
||||
@@ -977,6 +977,76 @@
|
||||
(go-parse "if Foo {}")
|
||||
(list :if (ast-var "Foo") (list :block (list)) nil))
|
||||
|
||||
(go-parse-test
|
||||
"stmt: go f()"
|
||||
(go-parse "go f()")
|
||||
(list :go (ast-app (ast-var "f") (list))))
|
||||
|
||||
(go-parse-test
|
||||
"stmt: go method(x, y)"
|
||||
(go-parse "go obj.method(x, y)")
|
||||
(list
|
||||
:go (ast-app
|
||||
(list :select (ast-var "obj") "method")
|
||||
(list (ast-var "x") (ast-var "y")))))
|
||||
|
||||
(go-parse-test
|
||||
"stmt: defer cleanup()"
|
||||
(go-parse "defer cleanup()")
|
||||
(list :defer (ast-app (ast-var "cleanup") (list))))
|
||||
|
||||
(go-parse-test
|
||||
"stmt: send ch <- v"
|
||||
(go-parse "ch <- 42")
|
||||
(list :send (ast-var "ch") (ast-literal "42")))
|
||||
|
||||
(go-parse-test
|
||||
"for-range: no kv — for range coll { }"
|
||||
(go-parse "for range coll { }")
|
||||
(list :range-for nil nil nil (ast-var "coll") (list :block (list))))
|
||||
|
||||
(go-parse-test
|
||||
"for-range: key only — for k := range m { }"
|
||||
(go-parse "for k := range m { }")
|
||||
(list
|
||||
:range-for :short-decl
|
||||
(ast-var "k")
|
||||
nil
|
||||
(ast-var "m")
|
||||
(list :block (list))))
|
||||
|
||||
(go-parse-test
|
||||
"for-range: k, v := range m"
|
||||
(go-parse "for k, v := range m { }")
|
||||
(list
|
||||
:range-for :short-decl
|
||||
(ast-var "k")
|
||||
(ast-var "v")
|
||||
(ast-var "m")
|
||||
(list :block (list))))
|
||||
|
||||
(go-parse-test
|
||||
"for-range: assign form k = range coll"
|
||||
(go-parse "for k = range coll { }")
|
||||
(list
|
||||
:range-for :assign
|
||||
(ast-var "k")
|
||||
nil
|
||||
(ast-var "coll")
|
||||
(list :block (list))))
|
||||
|
||||
(go-parse-test
|
||||
"concurrency: defer + go in func body"
|
||||
(go-parse "func main() { defer cleanup(); go worker() }")
|
||||
(list
|
||||
:func-decl "main"
|
||||
(list)
|
||||
(list)
|
||||
(list
|
||||
:block (list
|
||||
(list :defer (ast-app (ast-var "cleanup") (list)))
|
||||
(list :go (ast-app (ast-var "worker") (list)))))))
|
||||
|
||||
(go-parse-test "non-primary: '+'" (go-parse "+") nil)
|
||||
|
||||
(go-parse-test "non-primary: empty" (go-parse "") nil)
|
||||
|
||||
@@ -196,19 +196,17 @@ Progress-log line → push `origin/loops/go`.
|
||||
canonical AST kit. Grouped/parenthesized decls (`var (...)`, etc.)
|
||||
and variadic params deferred. Anonymous param-list disambiguation
|
||||
(`func(int, string)`) is a known parser-greedy limitation, flagged.
|
||||
- [/] Statements: `return`, short-decl `:=`, assign `=`, compound assign
|
||||
(`+=` etc.), expression stmt, block `{...}`, **if/else (with chained
|
||||
else-if), for (infinite / while-like / C-style), break, continue,
|
||||
inc-dec (`x++` / `x--`)** all done. **Composite-literal `{`
|
||||
suppression** active in control-flow conditions via
|
||||
`gp-no-comp-lit` counter — closes the parser-mode caveat flagged
|
||||
when composite literals landed. `switch`/`select`/`defer`/`go`/
|
||||
`for...range`/send `ch<-v` deferred.
|
||||
- [/] Statements: return, short-decl, assign, compound assign, expr stmt,
|
||||
block, if/else (chained), for (4 shapes incl. range), break,
|
||||
continue, inc-dec, **`go EXPR`, `defer EXPR`, send `ch <- v`,
|
||||
`for k, v := range coll`** all done. Composite-literal `{`
|
||||
suppression active in control-flow conditions. `switch` and
|
||||
`select` deferred.
|
||||
- [ ] End-to-end: hello-world, fibonacci, FizzBuzz, goroutine ping-pong,
|
||||
struct + method.
|
||||
- **Acceptance:** parse/ suite at 80+ tests. **Acceptance bar crossed:
|
||||
152/152.** Remaining sub-items (switch/select/defer/go/range,
|
||||
end-to-end programs) keep Phase 2 open ⬜.
|
||||
161/161.** Remaining sub-items (switch/select, end-to-end programs)
|
||||
keep Phase 2 open ⬜.
|
||||
|
||||
### Phase 3 — Bidirectional type checker, MVP (`lib/go/types.sx`) ⬜
|
||||
- **Independent implementation.** Do NOT use lib/guest/static-types-
|
||||
@@ -525,6 +523,19 @@ 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.: concurrency + iteration statements.
|
||||
`go EXPR`, `defer EXPR`, channel send `ch <- v`, and the four
|
||||
`for ... range` shapes (no-kv / k-only / k,v / assign-form). New
|
||||
`gp-for-find-range` pre-scans the for-header to dispatch between
|
||||
range and C-style/while forms cleanly. Send-stmt detection added to
|
||||
the LHS-list branch (after lhs, `<-` → send). +9 tests, parse
|
||||
161/161, total 290/290. `[shapes-scheduler]` — Go's concurrency-
|
||||
primitive AST shapes (`:go`, `:defer`, `:send`, `:range-for`) all
|
||||
landed; sister-plan diary updated with the corresponding kit-API
|
||||
insights (uniform spawn-thunk shape, channel-recv ⇄ iteration
|
||||
polymorphism at the range-coll dispatch).
|
||||
|
||||
Sister-plan diary update follows.
|
||||
- 2026-05-27 — Phase 2 cont.: control-flow statements. `if cond { } [else
|
||||
{ }]` with chained else-if, `for { }` (infinite), `for cond { }`
|
||||
(while-like), `for init; cond; post { }` (C-style), `break`,
|
||||
|
||||
@@ -231,5 +231,37 @@ real result.
|
||||
|
||||
_Newest first. Append one dated entry per milestone landed._
|
||||
|
||||
- 2026-05-27 — From Go-on-SX Phase 2 (parser side, ahead of scheduler
|
||||
implementation): the **parsed AST shapes** for Go's concurrency
|
||||
primitives have landed and are worth recording before Phase 5 builds
|
||||
the scheduler.
|
||||
|
||||
```
|
||||
go EXPR → (list :go EXPR)
|
||||
defer EXPR → (list :defer EXPR)
|
||||
ch <- v → (list :send CHAN VALUE)
|
||||
<-ch → (list :app (:var "<-") [CHAN]) ; unary recv
|
||||
for range COLL { } → (list :range-for nil nil nil COLL BODY)
|
||||
for k, v := range C → (list :range-for :short-decl KEY VAL COLL BODY)
|
||||
```
|
||||
|
||||
**Design insight for the kit**: the `:go` and `:defer` shapes are
|
||||
pleasingly minimal — both wrap a single expression. Erlang's
|
||||
`spawn(Mod, Fun, Args)` will produce something more elaborate; the
|
||||
scheduler kit primitive `(sched-spawn task)` should accept a thunk so
|
||||
both languages reduce to a uniform spawn API.
|
||||
|
||||
The `:send` shape carries CHAN + VALUE — symmetric with channel-recv
|
||||
as the unary `<-` form. Once the scheduler has channel primitives,
|
||||
both shapes thunk-down to a single `(chan-op direction chan value)`
|
||||
abstraction.
|
||||
|
||||
Range over channels (`for v := range ch`) is currently parsed as
|
||||
range-for with `coll = ch`; the scheduler kit will dispatch on the
|
||||
type of `coll` at execution time (channels yield via receive,
|
||||
collections via iteration). This dispatch is the right place for the
|
||||
scheduler kit to express the channel-receive ⇄ iteration polymorphism.
|
||||
Source: Go-on-SX commit `parse.sx — go/defer/send/range`.
|
||||
|
||||
- 2026-05-26 — Plan drafted. Phase 0 unstarted. Awaiting Go-on-SX to begin
|
||||
Phase 1.
|
||||
|
||||
Reference in New Issue
Block a user