;; 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)))))