ocaml: phase 1+2 records {x=1;y=2} + with-update (+6 tests, 289 total)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 51s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 51s
Parser: { f = e; f = e; ... } -> (:record (F E)...). { base with f = e;
... } -> (:record-update BASE (F E)...). Eval builds a dict from field
bindings; record-update merges the new fields over the base dict — the
same dict representation already used for modules.
{ also added to at-app-start? so records are valid arg atoms. Field
access via the existing :field postfix unifies record/module access.
Record patterns deferred to a later iteration.
This commit is contained in:
@@ -381,6 +381,37 @@
|
||||
;; matches its argument against the clauses.
|
||||
(let ((clauses (nth ast 1)) (captured env))
|
||||
(fn (arg) (ocaml-match-clauses arg clauses captured))))
|
||||
((= tag "record")
|
||||
(let ((fields (rest ast)) (result {}))
|
||||
(begin
|
||||
(define loop
|
||||
(fn (xs)
|
||||
(when (not (= xs (list)))
|
||||
(let ((kv (first xs)))
|
||||
(let ((k (first kv)) (v (ocaml-eval (nth kv 1) env)))
|
||||
(begin
|
||||
(set! result (merge result (dict k v)))
|
||||
(loop (rest xs))))))))
|
||||
(loop fields)
|
||||
result)))
|
||||
((= tag "record-update")
|
||||
(let ((base-ast (nth ast 1)) (fields (rest (rest ast))))
|
||||
(let ((base (ocaml-eval base-ast env)))
|
||||
(cond
|
||||
((dict? base)
|
||||
(let ((result base))
|
||||
(begin
|
||||
(define loop
|
||||
(fn (xs)
|
||||
(when (not (= xs (list)))
|
||||
(let ((kv (first xs)))
|
||||
(let ((k (first kv)) (v (ocaml-eval (nth kv 1) env)))
|
||||
(begin
|
||||
(set! result (merge result (dict k v)))
|
||||
(loop (rest xs))))))))
|
||||
(loop fields)
|
||||
result)))
|
||||
(else (error (str "ocaml-eval: with-update on non-record: " base)))))))
|
||||
((= tag "field")
|
||||
;; `e.name` — evaluate e, expect a dict (record/module), get name.
|
||||
;; Special case: `(:field (:con "M") "x")` looks up M as a module
|
||||
|
||||
@@ -163,7 +163,7 @@
|
||||
((= tt "ctor") true)
|
||||
((and (= tt "keyword") (or (= tv "true") (= tv "false")))
|
||||
true)
|
||||
((and (= tt "op") (or (= tv "(") (= tv "["))) true)
|
||||
((and (= tt "op") (or (= tv "(") (= tv "[") (= tv "{"))) true)
|
||||
(else false)))))
|
||||
(set!
|
||||
parse-pattern-atom
|
||||
@@ -401,6 +401,60 @@
|
||||
(let
|
||||
((e (parse-expr)))
|
||||
(begin (consume! "keyword" "end") e))))
|
||||
;; Record literal { f1 = e1; f2 = e2 } or update
|
||||
;; { r with f1 = e1; f2 = e2 }.
|
||||
((and (= tt "op") (= tv "{"))
|
||||
(begin
|
||||
(advance-tok!)
|
||||
(cond
|
||||
;; { r with field = expr; ... } — base ident + with.
|
||||
((and (= (ocaml-tok-type (peek-tok)) "ident")
|
||||
(= (ocaml-tok-value (peek-tok-at 1)) "with"))
|
||||
(let ((base-name (ocaml-tok-value (peek-tok))))
|
||||
(begin
|
||||
(advance-tok!) ;; ident
|
||||
(advance-tok!) ;; with
|
||||
(let ((fields (list)))
|
||||
(begin
|
||||
(define one
|
||||
(fn ()
|
||||
(let ((fname (ocaml-tok-value (consume! "ident" nil))))
|
||||
(begin
|
||||
(consume! "op" "=")
|
||||
(let ((fexpr (parse-expr-no-seq)))
|
||||
(append! fields (list fname fexpr)))))))
|
||||
(one)
|
||||
(define more
|
||||
(fn ()
|
||||
(when (at-op? ";")
|
||||
(begin (advance-tok!)
|
||||
(when (not (at-op? "}"))
|
||||
(begin (one) (more)))))))
|
||||
(more)
|
||||
(consume! "op" "}")
|
||||
(cons :record-update
|
||||
(cons (list :var base-name) fields)))))))
|
||||
(else
|
||||
;; Plain record literal { f = e; f = e; ... }.
|
||||
(let ((fields (list)))
|
||||
(begin
|
||||
(define one
|
||||
(fn ()
|
||||
(let ((fname (ocaml-tok-value (consume! "ident" nil))))
|
||||
(begin
|
||||
(consume! "op" "=")
|
||||
(let ((fexpr (parse-expr-no-seq)))
|
||||
(append! fields (list fname fexpr)))))))
|
||||
(one)
|
||||
(define more
|
||||
(fn ()
|
||||
(when (at-op? ";")
|
||||
(begin (advance-tok!)
|
||||
(when (not (at-op? "}"))
|
||||
(begin (one) (more)))))))
|
||||
(more)
|
||||
(consume! "op" "}")
|
||||
(cons :record fields)))))))
|
||||
(else
|
||||
(error
|
||||
(str
|
||||
@@ -425,7 +479,7 @@
|
||||
((= tt "ctor") true)
|
||||
((and (= tt "keyword") (or (= tv "true") (= tv "false") (= tv "begin")))
|
||||
true)
|
||||
((and (= tt "op") (or (= tv "(") (= tv "["))) true)
|
||||
((and (= tt "op") (or (= tv "(") (= tv "[") (= tv "{"))) true)
|
||||
(else false)))))
|
||||
(define parse-atom-postfix
|
||||
(fn ()
|
||||
|
||||
@@ -714,6 +714,20 @@ cat > "$TMPFILE" << 'EPOCHS'
|
||||
(epoch 1004)
|
||||
(eval "(ocaml-run-program \"let g _ x = x + 1;; g 99 41\")")
|
||||
|
||||
;; ── Records ────────────────────────────────────────────────────
|
||||
(epoch 1100)
|
||||
(eval "(ocaml-run \"let r = { x = 1; y = 2 } in r.x\")")
|
||||
(epoch 1101)
|
||||
(eval "(ocaml-run \"let r = { x = 1; y = 2 } in r.x + r.y\")")
|
||||
(epoch 1102)
|
||||
(eval "(ocaml-run \"let r = { x = 1; y = 2 } in let r2 = { r with x = 99 } in r2.x + r2.y\")")
|
||||
(epoch 1103)
|
||||
(eval "(ocaml-run \"let p = { name = \\\"Bob\\\"; age = 30 } in p.name\")")
|
||||
(epoch 1104)
|
||||
(eval "(ocaml-run \"let p = { name = \\\"Bob\\\"; age = 30 } in p.age\")")
|
||||
(epoch 1105)
|
||||
(eval "(ocaml-run-program \"let r = { x = 1; y = 2 };; r.x + r.y\")")
|
||||
|
||||
EPOCHS
|
||||
|
||||
OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null)
|
||||
@@ -1131,6 +1145,14 @@ check 1002 "let f _ = 1 in f 5" '1'
|
||||
check 1003 "top-level let f () =" '7'
|
||||
check 1004 "wildcard top-level" '42'
|
||||
|
||||
# ── Records ─────────────────────────────────────────────────────
|
||||
check 1100 "record literal + access" '1'
|
||||
check 1101 "record sum fields" '3'
|
||||
check 1102 "record with-update" '101'
|
||||
check 1103 "record string field" '"Bob"'
|
||||
check 1104 "record int field" '30'
|
||||
check 1105 "top-level record decl" '3'
|
||||
|
||||
TOTAL=$((PASS + FAIL))
|
||||
if [ $FAIL -eq 0 ]; then
|
||||
echo "ok $PASS/$TOTAL OCaml-on-SX tests passed"
|
||||
|
||||
@@ -355,6 +355,12 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means:
|
||||
|
||||
_Newest first._
|
||||
|
||||
- 2026-05-08 Phase 1+2 — record literals `{ x = 1; y = 2 }` and
|
||||
functional update `{ r with x = 99 }`. Parser produces `(:record (F E)
|
||||
...)` and `(:record-update BASE-EXPR (F E) ...)`. Eval builds a dict
|
||||
from field bindings; record-update merges over the base dict (the same
|
||||
dict-based representation we already use for modules). Field access
|
||||
via existing `:field` postfix. Record patterns deferred. 289/289 (+6).
|
||||
- 2026-05-08 Phase 5.1 — `lib/ocaml/conformance.sh` + `scoreboard.json`
|
||||
+ `scoreboard.md`. Classifies tests into 14 suites by description
|
||||
prefix and emits structured pass/fail counts. Current: 284 pass / 0
|
||||
|
||||
Reference in New Issue
Block a user