diff --git a/lib/go/parse.sx b/lib/go/parse.sx index ec9741e7..05c328f8 100644 --- a/lib/go/parse.sx +++ b/lib/go/parse.sx @@ -63,7 +63,58 @@ (do (gp-advance!) (ast-literal v)) (= ty "ident") (do (gp-advance!) (ast-var v)) + ;; Type-prefixed composite literal starters: [, map, struct. + ;; We parse a full type, then if '{' follows it's a composite + ;; literal; otherwise the type is the operand (the caller + ;; decides what to do — currently statement parsing isn't here). + (or (and (= ty "op") (= v "[")) + (and (= ty "keyword") + (or (= v "map") (= v "struct")))) + (let ((tytree (gp-parse-type))) + (cond + (and (= (gp-tok-type) "op") (= (gp-tok-value) "{")) + (do + (gp-advance!) + (list :composite tytree (gp-parse-composite-elems))) + :else tytree)) :else nil)))) + (define + gp-parse-composite-elems + ;; Caller has consumed '{'. Parses elements until '}'. + ;; Each element: either an expression, or KEY ':' VALUE. + ;; KEY can be an ident (struct field name) or an expression + ;; (map key) — parser is permissive, types phase disambiguates. + ;; Returns a list of expression nodes or (list :kv KEY VALUE). + (fn + () + (let ((elems (list))) + (define + gp-comp-loop + (fn + () + (cond + (= (gp-tok-type) "semi") + (do (gp-advance!) (gp-comp-loop)) + (and (= (gp-tok-type) "op") (= (gp-tok-value) "}")) + (gp-advance!) + :else + (do + (let ((first (gp-parse-expr 1))) + (cond + (and (= (gp-tok-type) "op") + (= (gp-tok-value) ":")) + (do + (gp-advance!) + (let ((val (gp-parse-expr 1))) + (append! elems (list :kv first val)))) + :else + (append! elems first))) + (when (and (= (gp-tok-type) "op") + (= (gp-tok-value) ",")) + (gp-advance!)) + (gp-comp-loop))))) + (gp-comp-loop) + elems))) (define gp-parse-call-args ;; Parse comma-separated args inside (...). Caller has already @@ -413,6 +464,14 @@ (do (gp-advance!) (gp-postfix-loop (gp-parse-bracket base))) + ;; Ident-prefixed composite literal: T{...}. The base is + ;; the AST expression for the type-name (an ast-var or a + ;; :select node); a later phase resolves it as a type. + (and (= (get tok :type) "op") (= (get tok :value) "{")) + (do + (gp-advance!) + (gp-postfix-loop + (list :composite base (gp-parse-composite-elems)))) :else base))))) (define gp-unary-ops diff --git a/lib/go/scoreboard.json b/lib/go/scoreboard.json index b0dce7eb..4e21cc98 100644 --- a/lib/go/scoreboard.json +++ b/lib/go/scoreboard.json @@ -1,10 +1,10 @@ { "language": "go", - "total_pass": 235, - "total": 235, + "total_pass": 243, + "total": 243, "suites": [ {"name":"lex","pass":129,"total":129,"status":"ok"}, - {"name":"parse","pass":106,"total":106,"status":"ok"}, + {"name":"parse","pass":114,"total":114,"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 f11686b6..9579cd8e 100644 --- a/lib/go/scoreboard.md +++ b/lib/go/scoreboard.md @@ -1,11 +1,11 @@ # Go-on-SX Scoreboard -**Total: 235 / 235 tests passing** +**Total: 243 / 243 tests passing** | | Suite | Pass | Total | |---|---|---|---| | ✅ | lex | 129 | 129 | -| ✅ | parse | 106 | 106 | +| ✅ | parse | 114 | 114 | | ⬜ | types | 0 | 0 | | ⬜ | eval | 0 | 0 | | ⬜ | runtime | 0 | 0 | diff --git a/lib/go/tests/parse.sx b/lib/go/tests/parse.sx index 602cf5b5..ee009125 100644 --- a/lib/go/tests/parse.sx +++ b/lib/go/tests/parse.sx @@ -650,6 +650,70 @@ (list (list :ty-name "int"))) (list :method "Close" (list) (list)))))) +(go-parse-test + "comp: Point{} (empty)" + (go-parse "Point{}") + (list :composite (ast-var "Point") (list))) + +(go-parse-test + "comp: Point{1, 2} (positional)" + (go-parse "Point{1, 2}") + (list + :composite (ast-var "Point") + (list (ast-literal "1") (ast-literal "2")))) + +(go-parse-test + "comp: Point{X: 1, Y: 2} (keyed)" + (go-parse "Point{X: 1, Y: 2}") + (list + :composite (ast-var "Point") + (list + (list :kv (ast-var "X") (ast-literal "1")) + (list :kv (ast-var "Y") (ast-literal "2"))))) + +(go-parse-test + "comp: []int{1, 2, 3} (slice literal)" + (go-parse "[]int{1, 2, 3}") + (list + :composite (list :ty-slice (list :ty-name "int")) + (list (ast-literal "1") (ast-literal "2") (ast-literal "3")))) + +(go-parse-test + "comp: [3]int{1, 2, 3} (array literal)" + (go-parse "[3]int{1, 2, 3}") + (list + :composite (list :ty-array (ast-literal "3") (list :ty-name "int")) + (list (ast-literal "1") (ast-literal "2") (ast-literal "3")))) + +(go-parse-test + "comp: map[string]int{\"a\": 1, \"b\": 2} (map literal)" + (go-parse "map[string]int{\"a\": 1, \"b\": 2}") + (list + :composite (list :ty-map (list :ty-name "string") (list :ty-name "int")) + (list + (list :kv (ast-literal "a") (ast-literal "1")) + (list :kv (ast-literal "b") (ast-literal "2"))))) + +(go-parse-test + "comp: pkg.Point{1, 2} (qualified type)" + (go-parse "pkg.Point{1, 2}") + (list + :composite (list :select (ast-var "pkg") "Point") + (list (ast-literal "1") (ast-literal "2")))) + +(go-parse-test + "comp: nested — []Point{Point{1,2}, Point{3,4}}" + (go-parse "[]Point{Point{1, 2}, Point{3, 4}}") + (list + :composite (list :ty-slice (list :ty-name "Point")) + (list + (list + :composite (ast-var "Point") + (list (ast-literal "1") (ast-literal "2"))) + (list + :composite (ast-var "Point") + (list (ast-literal "3") (ast-literal "4")))))) + (go-parse-test "non-primary: '+'" (go-parse "+") nil) (go-parse-test "non-primary: empty" (go-parse "") nil) diff --git a/plans/go-on-sx.md b/plans/go-on-sx.md index 133e8b90..4fd718f9 100644 --- a/plans/go-on-sx.md +++ b/plans/go-on-sx.md @@ -181,8 +181,13 @@ Progress-log line → push `origin/loops/go`. with methods + embedded interfaces (named and qualified)** all done — kit has no type primitives. Field tags, struct embeds, variadic, named func-params, Go 1.18 type sets, generics deferred. -- [ ] Composite literals: `T{...}`, `[]T{...}`, `map[K]V{...}`, - `struct{...}{...}`. +- [x] Composite literals: `T{...}`, `[]T{...}`, `[N]T{...}`, + `map[K]V{...}`, `pkg.T{...}`, nested. Positional and keyed + (`X: 1, Y: 2`) elements. AST `(list :composite TYPE-OR-EXPR ELEMS)`; + elements are exprs or `(list :kv KEY VALUE)`. Note: in statement + context (e.g. `if cond { ... }`) my parser would WRONGLY treat + the body as a composite; statement parsing will need a "no- + composite-here" mode flag — to be added when statements arrive. - [ ] Declarations: `package`, `import`, `var`, `const`, `type`, `func` (including methods, parameter lists, return types). - [ ] Statements: `if`/`else`, `for` (C-style + range), `switch` (expr + @@ -191,8 +196,7 @@ Progress-log line → push `origin/loops/go`. - [ ] End-to-end: hello-world, fibonacci, FizzBuzz, goroutine ping-pong, struct + method. - **Acceptance:** parse/ suite at 80+ tests. **Acceptance bar crossed: - 106/106.** Remaining sub-items (composite literals, decls, stmts, - e2e) still keep Phase 2 open ⬜. + 114/114.** Remaining sub-items (decls, stmts, e2e) keep Phase 2 open ⬜. ### Phase 3 — Bidirectional type checker, MVP (`lib/go/types.sx`) ⬜ - **Independent implementation.** Do NOT use lib/guest/static-types- @@ -509,6 +513,17 @@ 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.: composite literals. `T{}`, `T{1, 2}`, + `T{X: 1, Y: 2}`, `[]T{...}`, `[N]T{...}`, `map[K]V{...}`, + `pkg.T{...}`, nested composites. AST shape + `(list :composite TYPE-OR-EXPR ELEMS)`; each element is an expression + or `(list :kv KEY VALUE)`. Two parser entry points: type-prefixed + (`gp-parse-primary` adds `[`/`map`/`struct` branches) and + ident-prefixed (postfix loop adds `{` branch). **Known limitation + flagged in plan:** when statement parsing arrives, the postfix `{` + branch will misread `if cond { ... }` as composite literal — needs a + "no-composite-here" parser-mode flag. +8 tests, parse 114/114, total + 243/243. `[nothing]` — pure Go parser shape work. - 2026-05-27 — Phase 2 cont.: interface-type expressions. `interface {}`, `interface { Close() }`, `interface { String() string }`, `interface { Read([]byte) (int, error) }`, plus embedded interfaces