Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 31s
New :go-package NAME ENTRIES value type with field lookup via
extended go-eval-select. New :go-builtin-fn callable for closure-
based stdlib functions. lib/go/std/strings.sx ships 12 functions
(Contains, HasPrefix, HasSuffix, Index, Count, Repeat, Join,
ToUpper, ToLower, TrimSpace, Split, Replace) + lib/go/std/strconv.sx
ships Itoa/Atoi.
Pre-existing bug fixed: parser was emitting (:literal V) for both
`42` and `"42"`, relying on first-char heuristic in eval/types.
Now emits :literal-string for string/rune literals so Atoi("42")
correctly receives the string. 3 parse tests + 2 in-composite-key
tests updated to new shape.
Total 597/597. Stdlib 41/41 — +40 acceptance bar cleared. Sister
diary documents the 11 value-type kinds (struct/slice/map/chan/
fn/method/builtin/builtin-fn/package/panic/defer) all sharing the
"(:KIND PAYLOAD...)" shape, alongside AST nodes and sentinel signals
as the kit's three orthogonal first-class-tag axes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
387 lines
12 KiB
Plaintext
387 lines
12 KiB
Plaintext
;; lib/go/std/strings.sx — Go's `strings` package, v0 subset.
|
|
;;
|
|
;; Exposed as `go-std-strings`, a (:go-package "strings" ENTRIES) value.
|
|
;; Register with `(go-env-extend env "strings" go-std-strings)` to make
|
|
;; `strings.X(...)` call sites work in evaluated Go code.
|
|
;;
|
|
;; Each entry is (FIELD-NAME (list :go-fn PARAMS BODY)) — the same
|
|
;; shape user-defined Go functions get. Bodies are written in SX
|
|
;; directly via go-builtin closures wrapping host-level string ops
|
|
;; for speed, OR as parsed Go source for fidelity. v0 uses
|
|
;; go-builtin wrappers — simpler and fast.
|
|
|
|
;; ── helpers: implement go-std-strings entries as builtins ────────
|
|
|
|
(define
|
|
go-strings-contains
|
|
(fn (args)
|
|
(cond
|
|
(not (= (len args) 2))
|
|
(list :eval-error :strings-contains-arity (len args))
|
|
:else
|
|
(let ((s (first args)) (sub (nth args 1)))
|
|
(cond
|
|
(not (string? s)) (list :eval-error :strings-not-string s)
|
|
(not (string? sub)) (list :eval-error :strings-not-string sub)
|
|
:else
|
|
(go-strings-index-of s sub 0))))))
|
|
|
|
(define
|
|
go-strings-index-of
|
|
;; Returns true if SUB appears in S at or after START, else false.
|
|
(fn (s sub start)
|
|
(let ((slen (len s)) (sublen (len sub)))
|
|
(cond
|
|
(= sublen 0) true
|
|
(> (+ start sublen) slen) false
|
|
(go-strings-match-at s sub start 0) true
|
|
:else (go-strings-index-of s sub (+ start 1))))))
|
|
|
|
(define
|
|
go-strings-match-at
|
|
(fn (s sub start k)
|
|
(cond
|
|
(>= k (len sub)) true
|
|
(= (nth s (+ start k)) (nth sub k))
|
|
(go-strings-match-at s sub start (+ k 1))
|
|
:else false)))
|
|
|
|
(define
|
|
go-strings-has-prefix
|
|
(fn (args)
|
|
(cond
|
|
(not (= (len args) 2))
|
|
(list :eval-error :strings-hasprefix-arity (len args))
|
|
:else
|
|
(let ((s (first args)) (p (nth args 1)))
|
|
(cond
|
|
(not (string? s)) (list :eval-error :strings-not-string s)
|
|
(not (string? p)) (list :eval-error :strings-not-string p)
|
|
(> (len p) (len s)) false
|
|
:else (go-strings-match-at s p 0 0))))))
|
|
|
|
(define
|
|
go-strings-has-suffix
|
|
(fn (args)
|
|
(cond
|
|
(not (= (len args) 2))
|
|
(list :eval-error :strings-hassuffix-arity (len args))
|
|
:else
|
|
(let ((s (first args)) (suf (nth args 1)))
|
|
(cond
|
|
(not (string? s)) (list :eval-error :strings-not-string s)
|
|
(not (string? suf)) (list :eval-error :strings-not-string suf)
|
|
(> (len suf) (len s)) false
|
|
:else
|
|
(go-strings-match-at s suf (- (len s) (len suf)) 0))))))
|
|
|
|
(define
|
|
go-strings-index
|
|
(fn (args)
|
|
(cond
|
|
(not (= (len args) 2))
|
|
(list :eval-error :strings-index-arity (len args))
|
|
:else
|
|
(let ((s (first args)) (sub (nth args 1)))
|
|
(cond
|
|
(not (string? s)) (list :eval-error :strings-not-string s)
|
|
(not (string? sub)) (list :eval-error :strings-not-string sub)
|
|
:else (go-strings-index-loop s sub 0))))))
|
|
|
|
(define
|
|
go-strings-index-loop
|
|
(fn (s sub start)
|
|
(let ((slen (len s)) (sublen (len sub)))
|
|
(cond
|
|
(= sublen 0) 0
|
|
(> (+ start sublen) slen) -1
|
|
(go-strings-match-at s sub start 0) start
|
|
:else (go-strings-index-loop s sub (+ start 1))))))
|
|
|
|
(define
|
|
go-strings-repeat
|
|
(fn (args)
|
|
(cond
|
|
(not (= (len args) 2))
|
|
(list :eval-error :strings-repeat-arity (len args))
|
|
:else
|
|
(let ((s (first args)) (n (nth args 1)))
|
|
(cond
|
|
(not (string? s)) (list :eval-error :strings-not-string s)
|
|
(< n 0) (list :eval-error :strings-repeat-negative n)
|
|
:else (go-strings-repeat-loop s n ""))))))
|
|
|
|
(define
|
|
go-strings-repeat-loop
|
|
(fn (s n acc)
|
|
(cond
|
|
(<= n 0) acc
|
|
:else (go-strings-repeat-loop s (- n 1) (str acc s)))))
|
|
|
|
(define
|
|
go-strings-count
|
|
(fn (args)
|
|
(cond
|
|
(not (= (len args) 2))
|
|
(list :eval-error :strings-count-arity (len args))
|
|
:else
|
|
(let ((s (first args)) (sub (nth args 1)))
|
|
(cond
|
|
(not (string? s)) (list :eval-error :strings-not-string s)
|
|
(not (string? sub)) (list :eval-error :strings-not-string sub)
|
|
:else (go-strings-count-loop s sub 0 0))))))
|
|
|
|
(define
|
|
go-strings-count-loop
|
|
(fn (s sub start acc)
|
|
(let ((idx (go-strings-index-loop s sub start)))
|
|
(cond
|
|
(< idx 0) acc
|
|
:else
|
|
(go-strings-count-loop s sub (+ idx (max 1 (len sub))) (+ acc 1))))))
|
|
|
|
(define
|
|
go-strings-join
|
|
(fn (args)
|
|
(cond
|
|
(not (= (len args) 2))
|
|
(list :eval-error :strings-join-arity (len args))
|
|
:else
|
|
(let ((sep (nth args 1)) (xs (first args)))
|
|
(cond
|
|
(not (string? sep)) (list :eval-error :strings-not-string sep)
|
|
(not (and (list? xs) (= (first xs) :go-slice)))
|
|
(list :eval-error :strings-join-not-slice xs)
|
|
:else (go-strings-join-loop (nth xs 1) sep ""))))))
|
|
|
|
(define
|
|
go-strings-join-loop
|
|
(fn (xs sep acc)
|
|
(cond
|
|
(= (len xs) 0) acc
|
|
(= (len acc) 0) (go-strings-join-loop (rest xs) sep (first xs))
|
|
:else
|
|
(go-strings-join-loop (rest xs) sep (str acc sep (first xs))))))
|
|
|
|
;; ── case conversion ──────────────────────────────────────────────
|
|
|
|
(define
|
|
go-strings-char-to-upper
|
|
(fn (c)
|
|
(cond
|
|
(and (>= c "a") (<= c "z"))
|
|
;; ASCII uppercase shift: 'a' is 0x61, 'A' is 0x41 → diff 0x20.
|
|
;; SX has no charcode primitive, so use a char-pair table.
|
|
(go-strings-letter-toggle c true)
|
|
:else c)))
|
|
|
|
(define
|
|
go-strings-char-to-lower
|
|
(fn (c)
|
|
(cond
|
|
(and (>= c "A") (<= c "Z"))
|
|
(go-strings-letter-toggle c false)
|
|
:else c)))
|
|
|
|
(define
|
|
go-strings-letter-toggle
|
|
;; Toggle a single ASCII letter's case via direct mapping.
|
|
;; `to-upper?` true means input is lowercase, output uppercase.
|
|
(fn (c to-upper?)
|
|
(cond
|
|
to-upper?
|
|
(cond
|
|
(= c "a") "A" (= c "b") "B" (= c "c") "C" (= c "d") "D"
|
|
(= c "e") "E" (= c "f") "F" (= c "g") "G" (= c "h") "H"
|
|
(= c "i") "I" (= c "j") "J" (= c "k") "K" (= c "l") "L"
|
|
(= c "m") "M" (= c "n") "N" (= c "o") "O" (= c "p") "P"
|
|
(= c "q") "Q" (= c "r") "R" (= c "s") "S" (= c "t") "T"
|
|
(= c "u") "U" (= c "v") "V" (= c "w") "W" (= c "x") "X"
|
|
(= c "y") "Y" (= c "z") "Z" :else c)
|
|
:else
|
|
(cond
|
|
(= c "A") "a" (= c "B") "b" (= c "C") "c" (= c "D") "d"
|
|
(= c "E") "e" (= c "F") "f" (= c "G") "g" (= c "H") "h"
|
|
(= c "I") "i" (= c "J") "j" (= c "K") "k" (= c "L") "l"
|
|
(= c "M") "m" (= c "N") "n" (= c "O") "o" (= c "P") "p"
|
|
(= c "Q") "q" (= c "R") "r" (= c "S") "s" (= c "T") "t"
|
|
(= c "U") "u" (= c "V") "v" (= c "W") "w" (= c "X") "x"
|
|
(= c "Y") "y" (= c "Z") "z" :else c))))
|
|
|
|
(define
|
|
go-strings-map-chars
|
|
(fn (s i acc char-fn)
|
|
(cond
|
|
(>= i (len s)) acc
|
|
:else
|
|
(go-strings-map-chars s (+ i 1) (str acc (char-fn (nth s i))) char-fn))))
|
|
|
|
(define
|
|
go-strings-to-upper
|
|
(fn (args)
|
|
(cond
|
|
(not (= (len args) 1))
|
|
(list :eval-error :strings-toupper-arity (len args))
|
|
:else
|
|
(let ((s (first args)))
|
|
(cond
|
|
(not (string? s)) (list :eval-error :strings-not-string s)
|
|
:else (go-strings-map-chars s 0 "" go-strings-char-to-upper))))))
|
|
|
|
(define
|
|
go-strings-to-lower
|
|
(fn (args)
|
|
(cond
|
|
(not (= (len args) 1))
|
|
(list :eval-error :strings-tolower-arity (len args))
|
|
:else
|
|
(let ((s (first args)))
|
|
(cond
|
|
(not (string? s)) (list :eval-error :strings-not-string s)
|
|
:else (go-strings-map-chars s 0 "" go-strings-char-to-lower))))))
|
|
|
|
;; ── TrimSpace ────────────────────────────────────────────────────
|
|
|
|
(define
|
|
go-strings-is-space?
|
|
(fn (c)
|
|
(or (= c " ") (= c "\t") (= c "\n") (= c "\r"))))
|
|
|
|
(define
|
|
go-strings-trim-left
|
|
(fn (s i)
|
|
(cond
|
|
(>= i (len s)) i
|
|
(go-strings-is-space? (nth s i)) (go-strings-trim-left s (+ i 1))
|
|
:else i)))
|
|
|
|
(define
|
|
go-strings-trim-right
|
|
(fn (s end)
|
|
(cond
|
|
(<= end 0) 0
|
|
(go-strings-is-space? (nth s (- end 1))) (go-strings-trim-right s (- end 1))
|
|
:else end)))
|
|
|
|
(define
|
|
go-strings-substr
|
|
;; Substring [lo, hi) — naive but predictable.
|
|
(fn (s lo hi)
|
|
(cond
|
|
(>= lo hi) ""
|
|
:else
|
|
(go-strings-substr-loop s lo hi ""))))
|
|
|
|
(define
|
|
go-strings-substr-loop
|
|
(fn (s i hi acc)
|
|
(cond
|
|
(>= i hi) acc
|
|
:else (go-strings-substr-loop s (+ i 1) hi (str acc (nth s i))))))
|
|
|
|
(define
|
|
go-strings-trim-space
|
|
(fn (args)
|
|
(cond
|
|
(not (= (len args) 1))
|
|
(list :eval-error :strings-trimspace-arity (len args))
|
|
:else
|
|
(let ((s (first args)))
|
|
(cond
|
|
(not (string? s)) (list :eval-error :strings-not-string s)
|
|
:else
|
|
(let ((lo (go-strings-trim-left s 0)))
|
|
(let ((hi (go-strings-trim-right s (len s))))
|
|
(go-strings-substr s lo hi))))))))
|
|
|
|
;; ── Split ────────────────────────────────────────────────────────
|
|
|
|
(define
|
|
go-strings-split
|
|
(fn (args)
|
|
(cond
|
|
(not (= (len args) 2))
|
|
(list :eval-error :strings-split-arity (len args))
|
|
:else
|
|
(let ((s (first args)) (sep (nth args 1)))
|
|
(cond
|
|
(not (string? s)) (list :eval-error :strings-not-string s)
|
|
(not (string? sep)) (list :eval-error :strings-not-string sep)
|
|
(= (len sep) 0)
|
|
;; Empty separator: real Go splits to all chars; v0 keeps
|
|
;; behaviour simple — single-element slice.
|
|
(list :go-slice (list s))
|
|
:else
|
|
(list :go-slice (go-strings-split-loop s sep 0 (list))))))))
|
|
|
|
(define
|
|
go-strings-split-loop
|
|
(fn (s sep start acc)
|
|
(let ((idx (go-strings-index-loop s sep start)))
|
|
(cond
|
|
(< idx 0)
|
|
(go-strings-split-finalize acc (go-strings-substr s start (len s)))
|
|
:else
|
|
(go-strings-split-loop s sep (+ idx (len sep))
|
|
(go-strings-split-finalize acc
|
|
(go-strings-substr s start idx)))))))
|
|
|
|
(define
|
|
go-strings-split-finalize
|
|
;; Append a piece to acc, growing the list in order.
|
|
(fn (acc piece)
|
|
(cond
|
|
(= (len acc) 0) (list piece)
|
|
:else (go-name-concat acc (list piece)))))
|
|
|
|
;; ── Replace ──────────────────────────────────────────────────────
|
|
|
|
(define
|
|
go-strings-replace
|
|
;; Replace(s, old, new, n). n < 0 = all.
|
|
(fn (args)
|
|
(cond
|
|
(not (= (len args) 4))
|
|
(list :eval-error :strings-replace-arity (len args))
|
|
:else
|
|
(let ((s (first args)) (old (nth args 1))
|
|
(newv (nth args 2)) (n (nth args 3)))
|
|
(cond
|
|
(not (string? s)) (list :eval-error :strings-not-string s)
|
|
(not (string? old)) (list :eval-error :strings-not-string old)
|
|
(not (string? newv)) (list :eval-error :strings-not-string newv)
|
|
(= (len old) 0) s
|
|
:else (go-strings-replace-loop s old newv n 0 ""))))))
|
|
|
|
(define
|
|
go-strings-replace-loop
|
|
(fn (s old newv n start acc)
|
|
(let ((idx (go-strings-index-loop s old start)))
|
|
(cond
|
|
(or (< idx 0) (= n 0))
|
|
(str acc (go-strings-substr s start (len s)))
|
|
:else
|
|
(go-strings-replace-loop s old newv
|
|
(cond (< n 0) -1 :else (- n 1))
|
|
(+ idx (len old))
|
|
(str acc (go-strings-substr s start idx) newv))))))
|
|
|
|
;; ── go-std-strings package value ─────────────────────────────────
|
|
|
|
(define
|
|
go-std-strings
|
|
(list :go-package "strings"
|
|
(list
|
|
(list "Contains" (list :go-builtin-fn go-strings-contains))
|
|
(list "HasPrefix" (list :go-builtin-fn go-strings-has-prefix))
|
|
(list "HasSuffix" (list :go-builtin-fn go-strings-has-suffix))
|
|
(list "Index" (list :go-builtin-fn go-strings-index))
|
|
(list "Count" (list :go-builtin-fn go-strings-count))
|
|
(list "Repeat" (list :go-builtin-fn go-strings-repeat))
|
|
(list "Join" (list :go-builtin-fn go-strings-join))
|
|
(list "ToUpper" (list :go-builtin-fn go-strings-to-upper))
|
|
(list "ToLower" (list :go-builtin-fn go-strings-to-lower))
|
|
(list "TrimSpace" (list :go-builtin-fn go-strings-trim-space))
|
|
(list "Split" (list :go-builtin-fn go-strings-split))
|
|
(list "Replace" (list :go-builtin-fn go-strings-replace)))))
|