From ee002f2e027761983ab6f1ac8573632f148a7005 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 8 May 2026 13:55:04 +0000 Subject: [PATCH] ocaml: phase 1/5/6 float arithmetic +./-./*./. (+5 tests, 372 total) Tokenizer: +. -. *. /. (with -. avoiding clash with negative float literals). Parser table places dotted ops at int-precedence levels. Eval routes to host SX +/-/*//. HM types them Float->Float->Float; literal floats now infer as Float (was Int). OCaml-style 1.5 +. 2.5 : Float works end-to-end through tokenize + parse + eval + infer. --- lib/ocaml/eval.sx | 4 ++++ lib/ocaml/infer.sx | 13 ++++++++++++- lib/ocaml/parser.sx | 4 ++++ lib/ocaml/test.sh | 19 +++++++++++++++++++ lib/ocaml/tokenizer.sx | 8 ++++++++ plans/ocaml-on-sx.md | 6 ++++++ 6 files changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/ocaml/eval.sx b/lib/ocaml/eval.sx index 74032419..d7843211 100644 --- a/lib/ocaml/eval.sx +++ b/lib/ocaml/eval.sx @@ -328,6 +328,10 @@ ((= op "-") (- lhs rhs)) ((= op "*") (* lhs rhs)) ((= op "/") (/ lhs rhs)) + ((= op "+.") (+ lhs rhs)) + ((= op "-.") (- lhs rhs)) + ((= op "*.") (* lhs rhs)) + ((= op "/.") (/ lhs rhs)) ((= op "mod") (mod lhs rhs)) ((= op "%") (mod lhs rhs)) ((= op "**") (pow lhs rhs)) diff --git a/lib/ocaml/infer.sx b/lib/ocaml/infer.sx index c07bf1ef..a37c5c59 100644 --- a/lib/ocaml/infer.sx +++ b/lib/ocaml/infer.sx @@ -37,9 +37,15 @@ "true" (hm-monotype (hm-bool)) "false" (hm-monotype (hm-bool))})))) +;; Float type isn't in the kit; use a named ctor. +(define ocaml-hm-float (fn () (hm-con "Float" (list)))) + (define ocaml-hm-builtin-env (fn () (let ((int-int-int (hm-arrow (hm-int) (hm-arrow (hm-int) (hm-int)))) + (float-float-float + (hm-arrow (ocaml-hm-float) + (hm-arrow (ocaml-hm-float) (ocaml-hm-float)))) (int-int-bool (hm-arrow (hm-int) (hm-arrow (hm-int) (hm-bool)))) (bool-bool-bool (hm-arrow (hm-bool) (hm-arrow (hm-bool) (hm-bool)))) (str-str-str (hm-arrow (hm-string) (hm-arrow (hm-string) (hm-string)))) @@ -66,6 +72,10 @@ "-" (hm-monotype int-int-int) "*" (hm-monotype int-int-int) "/" (hm-monotype int-int-int) + "+." (hm-monotype float-float-float) + "-." (hm-monotype float-float-float) + "*." (hm-monotype float-float-float) + "/." (hm-monotype float-float-float) "mod" (hm-monotype int-int-int) "%" (hm-monotype int-int-int) "**" (hm-monotype int-int-int) @@ -444,7 +454,7 @@ (else {:subst {} :type (hm-fresh-tv counter)})))) ((= tag "int") {:subst {} :type (hm-int)}) - ((= tag "float") {:subst {} :type (hm-int)}) ;; treat float as int for now + ((= tag "float") {:subst {} :type (ocaml-hm-float)}) ((= tag "string") {:subst {} :type (hm-string)}) ((= tag "char") {:subst {} :type (hm-string)}) ((= tag "bool") {:subst {} :type (hm-bool)}) @@ -572,5 +582,6 @@ (let ((a (ocaml-hm-format-type (nth args 0))) (b (ocaml-hm-format-type (nth args 1)))) (str "(" a ", " b ") result"))) + ((= head "Float") "Float") (else head)))) (else (str t))))) diff --git a/lib/ocaml/parser.sx b/lib/ocaml/parser.sx index 63e691a8..9dc08026 100644 --- a/lib/ocaml/parser.sx +++ b/lib/ocaml/parser.sx @@ -67,8 +67,12 @@ (list "::" 6 :right) (list "+" 7 :left) (list "-" 7 :left) + (list "+." 7 :left) + (list "-." 7 :left) (list "*" 8 :left) (list "/" 8 :left) + (list "*." 8 :left) + (list "/." 8 :left) (list "%" 8 :left) (list "mod" 8 :left) (list "land" 8 :left) diff --git a/lib/ocaml/test.sh b/lib/ocaml/test.sh index 23c0d5d1..e01147c5 100755 --- a/lib/ocaml/test.sh +++ b/lib/ocaml/test.sh @@ -910,6 +910,18 @@ cat > "$TMPFILE" << 'EPOCHS' (epoch 2203) (eval "(ocaml-run \"List.map2 (fun a b -> a + b) [1;2;3] [10;20;30]\")") +;; ── Float arithmetic ─────────────────────────────────────────── +(epoch 2300) +(eval "(ocaml-run \"1.5 +. 2.5\")") +(epoch 2301) +(eval "(ocaml-run \"3.0 *. 2.0\")") +(epoch 2302) +(eval "(ocaml-run \"10.0 /. 4.0\")") +(epoch 2303) +(eval "(ocaml-type-of \"1.5 +. 2.5\")") +(epoch 2304) +(eval "(ocaml-type-of \"fun x y -> x +. y\")") + EPOCHS OUTPUT=$(timeout 180 "$SX_SERVER" < "$TMPFILE" 2>/dev/null) @@ -1439,6 +1451,13 @@ check 2201 "List.split" '("tuple" (1 2) ("a" "b"))' check 2202 "List.fold_left2" '66' check 2203 "List.map2" '(11 22 33)' +# ── Float arithmetic ──────────────────────────────────────────── +check 2300 "1.5 +. 2.5" '4' +check 2301 "3.0 *. 2.0" '6' +check 2302 "10.0 /. 4.0" '2.5' +check 2303 "type 1.5 +. 2.5" '"Float"' +check 2304 "type fun x y -> x +. y" '"Float -> Float -> Float"' + TOTAL=$((PASS + FAIL)) if [ $FAIL -eq 0 ]; then echo "ok $PASS/$TOTAL OCaml-on-SX tests passed" diff --git a/lib/ocaml/tokenizer.sx b/lib/ocaml/tokenizer.sx index d3882aab..245b83f3 100644 --- a/lib/ocaml/tokenizer.sx +++ b/lib/ocaml/tokenizer.sx @@ -294,6 +294,14 @@ (cond ((and (= c ";") (= c1 ";")) (begin (advance! 2) (push! "op" ";;" start) true)) + ((and (= c "+") (= c1 ".")) + (begin (advance! 2) (push! "op" "+." start) true)) + ((and (= c "-") (= c1 ".") (not (and (not (= c2 nil)) (ocaml-digit? c2)))) + (begin (advance! 2) (push! "op" "-." start) true)) + ((and (= c "*") (= c1 ".")) + (begin (advance! 2) (push! "op" "*." start) true)) + ((and (= c "/") (= c1 ".")) + (begin (advance! 2) (push! "op" "/." start) true)) ((and (= c "-") (= c1 ">")) (begin (advance! 2) (push! "op" "->" start) true)) ((and (= c "<") (= c1 "-")) diff --git a/plans/ocaml-on-sx.md b/plans/ocaml-on-sx.md index 1abc3ab5..7df43b9a 100644 --- a/plans/ocaml-on-sx.md +++ b/plans/ocaml-on-sx.md @@ -373,6 +373,12 @@ the "mother tongue" closure: OCaml → SX → OCaml. This means: _Newest first._ +- 2026-05-08 Phase 1+5+6 — Float arithmetic (`+.` `-.` `*.` `/.`) + (+5 tests, 372 total). Tokenizer recognises the dotted operators. + Parser table places them at int's level (7 / 8). Eval routes them + to host SX `+`/`-`/`*`/`/` (which works for both ints and floats). + HM types them `Float -> Float -> Float`; `1.5 +. 2.5 : Float`. + Float type added to formatter as a plain `Float` ctor. - 2026-05-08 Phase 6 — List.combine/split/iter2/fold_left2/map2 (+4 tests, 367 total). Mechanical pair-walk OCaml implementations, failwith on length-mismatch matching Stdlib semantics. List module