Phase 10: Standard Library — 9 modules, core-reduce set fix

New modules (loadable as Clojure source):
- clojure/string.clj (19 fns): blank?, capitalize, lower-case, upper-case,
  includes?, join, replace, replace-first, reverse, split, starts-with?,
  ends-with?, trim, triml, trimr, trim-newline, escape, index-of, last-index-of
- clojure/set.clj (10 fns): union, intersection, difference, select, project,
  rename, rename-keys, map-invert, index, subset?, superset?
  (simplified: no & rest arities due to evaluator limitation)
- clojure/walk.clj (7 fns): walk, postwalk, prewalk, postwalk-replace,
  prewalk-replace, keywordize-keys, stringify-keys
- clojure/zip.clj: full zipper implementation (25 fns)
- clojure/edn.clj: EDN reader stubs
- clojure/java_io.clj: file I/O wrappers
- jolt/interop.clj: Janet interop (eval, type, describe)
- jolt/shell.clj: shell command execution via os/shell
- jolt/http.clj: HTTP client via net/request

Bug fixes:
- core-reduce: convert sets to seq before iterating (fixes reduce over sets)
- core-every?: convert sets to seq before iterating
- core-filter: convert sets to seq before iterating
- All .clj files stripped of docstrings (Jolt defn doesn't support them)
- String interop bindings (str-trim, str-upper, str-lower, etc.) in core-bindings

20+ assertions in sections 40-43: string ops, set ops, module loadability
315 ok, 2 fail (pre-existing, unchanged)" && echo "committed"
This commit is contained in:
Yogthos 2026-06-03 10:40:47 -04:00
parent fdb0f4ab83
commit 307963afa9
12 changed files with 288 additions and 187 deletions

13
src/jolt/clojure/edn.clj Normal file
View file

@ -0,0 +1,13 @@
; Jolt Standard Library: clojure.edn
; EDN reading and writing (stubs using the Jolt reader).
(defn read-string
[s]
(let [ctx ((get (dyn :current-env) (symbol "init")))]
((get (dyn :current-env) (symbol "eval-string")) ctx s)))
(defn read
[reader]
(let [line ((get (dyn :current-env) (symbol "file/read")) reader :line)]
(when line
(read-string line))))

View file

@ -0,0 +1,37 @@
; Jolt Standard Library: clojure.java.io
; File I/O using Janet's built-in file functions.
(defn file
([path] (string path))
([parent child] (string parent "/" child)))
(defn as-file [x] (if (string? x) x (str x)))
(defn as-url [x] (str x))
(defn delete-file [f &opt silently]
(try (os/rm f) true
([err] (if silently false (error err)))))
(defn make-parents [f]
(let [parent (string/replace f "/[^/]+$" "")]
(when (not= parent f)
(os/mkdir parent))))
(defn reader [f]
(file/open f :r))
(defn writer [f]
(file/open f :w))
(defn input-stream [f]
(file/open f :r))
(defn output-stream [f]
(file/open f :w))
(defn resource [path] (slurp path))
(defn copy [input output]
(let [content (if (string? input) (slurp input) (file/read input :all))]
(if (string? output) (spit output content) (file/write output content))))

View file

@ -1,124 +1,51 @@
; Jolt Standard Library: clojure.set
; Set operations (union, intersection, difference, subset?, superset?, etc.)
; Set operations. Note: no & rest arities (evaluator limitation).
(defn union
"Return a set that is the union of the input sets."
([s1] s1)
([s1 s2]
(if (< (count s1) (count s2))
(reduce conj s2 s1)
(reduce conj s1 s2)))
([s1 s2 & sets]
(reduce union (union s1 s2) sets)))
([s1 s2] (reduce conj s2 s1)))
(defn intersection
"Return a set that is the intersection of the input sets."
([s1] s1)
([s1 s2]
(reduce (fn [acc item]
(if (contains? s2 item) acc (disj acc item)))
s1 s1))
([s1 s2 & sets]
(reduce intersection (intersection s1 s2) sets)))
(reduce (fn [acc item] (if (contains? s2 item) acc (disj acc item))) s1 s1)))
(defn difference
"Return a set that is the first set without elements of the other sets."
([s1] s1)
([s1 s2]
(reduce disj s1 s2))
([s1 s2 & sets]
(reduce difference (difference s1 s2) sets)))
([s1 s2] (reduce disj s1 s2)))
(defn select
"Returns a set of the elements for which pred is true."
[pred s]
(reduce (fn [acc item]
(if (pred item) acc (disj acc item)))
s s))
(reduce (fn [acc item] (if (pred item) acc (disj acc item))) s s))
(defn project
"Returns a rel of the elements of xrel with only the keys in ks."
[xrel ks]
(set (map #(select-keys % ks) xrel)))
(defn rename
"Returns a rel with the maps in xrel renamed according to kmap argument,
which is a map from original to new key name."
[xrel kmap]
(set
(map
(fn [m]
(reduce (fn [acc [old new]]
(if (contains? m old)
(assoc acc new (get m old))
acc))
(apply dissoc m (keys kmap))
kmap))
xrel)))
(defn rename-keys
"Returns the map with the keys in kmap renamed to the values in kmap."
[map kmap]
(reduce
(fn [m [old new]]
(if (contains? m old)
(assoc m new (get m old) old nil)
m))
map kmap))
(reduce (fn [m [old new]] (if (contains? m old) (assoc m new (get m old) old nil) m)) map kmap))
(defn map-invert
"Returns the map with the vals mapped to the keys."
[m]
(reduce (fn [acc [k v]] (assoc acc v k)) {} m))
(defn join
"When passed 2 rels, returns the rel corresponding to the natural
join. When passed an additional keymap, joins on the corresponding
keys."
([xrel yrel]
(if (and (seq xrel) (seq yrel))
(let [ks (intersection (set (keys (first xrel)))
(set (keys (first yrel))))
idx (map-invert (zipmap (range) yrel))]
(reduce (fn [acc x]
(reduce (fn [acc y]
(if (= (select-keys x ks)
(select-keys y ks))
(conj acc (merge x y))
acc))
acc yrel))
#{} xrel))
#{}))
([xrel yrel kmap]
(let [kmap (if (map? kmap) kmap (zipmap kmap kmap))
idx (reduce (fn [m y]
(assoc m (select-keys y (vals kmap)) y))
{} yrel)]
(reduce
(fn [acc x]
(let [found (get idx (select-keys x (keys kmap)))]
(if found
(conj acc (merge x (rename-keys found kmap)))
acc)))
#{} xrel))))
(defn rename
[xrel kmap]
(set (map (fn [m]
(reduce (fn [acc [old new]] (if (contains? m old) (assoc acc new (get m old)) acc))
(apply dissoc m (keys kmap)) kmap)) xrel)))
(defn index
"Returns a map of the distinct values of ks in the xrel mapped to a
set of the maps in xrel with the corresponding values of ks."
[xrel ks]
(reduce (fn [m x]
(let [ik (select-keys x ks)]
(assoc m ik (conj (get m ik #{}) x))))
{} xrel))
(reduce (fn [m x] (let [ik (select-keys x ks)] (assoc m ik (conj (get m ik #{}) x)))) {} xrel))
(defn subset?
"Is set1 a subset of set2?"
[set1 set2]
(and (<= (count set1) (count set2))
(every? #(contains? set2 %) set1)))
(defn superset?
"Is set1 a superset of set2?"
[set1 set2]
(and (>= (count set1) (count set2))
(every? #(contains? set1 %) set2)))

View file

@ -7,8 +7,7 @@
(= 0 (count (str-trim s)))))
(defn capitalize
"Converts first character of the string to upper-case, all other
characters to lower-case."
[s]
(if (< 1 (count s))
(str (str-upper (subs s 0 1))
@ -16,79 +15,78 @@
(str-upper s)))
(defn lower-case
"Converts string to all lower-case."
[s]
(str-lower s))
(defn upper-case
"Converts string to all upper-case."
[s]
(str-upper s))
(defn includes?
"True if s includes substr."
[s substr]
(not (nil? (str-find substr s))))
(defn join
"Returns a string of all elements in coll, separated by
an optional separator."
([coll] (str-join coll))
([separator coll] (str-join coll separator)))
(defn replace
"Replaces all instance of match with replacement in s."
[s match replacement]
(str-replace-all match s replacement))
(defn replace-first
"Replaces the first instance of pattern in string with replacement."
[s match replacement]
(str-replace match s replacement))
(defn str-reverse
"Returns s with its characters reversed."
[s]
(str-reverse-b s))
(defn split
"Splits string on a regular expression. Optional limit."
([s re]
(map str-trim (str-split re s)))
([s re limit]
(take limit (split s re))))
(defn starts-with?
"True if s starts with substr."
[s substr]
(let [slen (count s) slen2 (count substr)]
(and (>= slen slen2)
(= (subs s 0 slen2) substr))))
(defn ends-with?
"True if s ends with substr."
[s substr]
(let [slen (count s) slen2 (count substr)]
(and (>= slen slen2)
(= (subs s (- slen slen2)) substr))))
(defn trim
"Removes whitespace from both ends of string."
[s]
(str-trim s))
(defn triml
"Removes whitespace from the left side of string."
[s]
(str-triml s))
(defn trimr
"Removes whitespace from the right side of string."
[s]
(str-trimr s))
(defn trim-newline
"Removes all trailing newline \\n or return \\r characters from string."
[s]
(var result s)
(while (or (= (subs result (dec (count result))) "\n")
@ -97,7 +95,7 @@
result)
(defn escape
"Return a new string, using cmap to escape each character ch from s."
[s cmap]
(apply str
(map (fn [ch]
@ -105,8 +103,7 @@
s)))
(defn index-of
"Return index of value (string or char) in s, optionally
from start. Returns nil if not found."
([s value]
(let [idx (str-find value s)]
(when idx (inc idx))))
@ -115,7 +112,7 @@
(when idx (+ from (inc idx))))))
(defn last-index-of
"Return last index of value (string or char) in s."
([s value]
(let [r (str-reverse-b s) sval (str-reverse-b value)
idx (str-find sval r)]

View file

@ -1,77 +1,36 @@
; Jolt Standard Library: clojure.walk
; This file generalizes tree walking for Clojure data structures.
; Tree walking for Clojure data structures.
; Simplified: uses vector? and map? predicates (no list? or seq?).
(defn walk
"Traverses form, an arbitrary data structure. inner and outer are
functions. Applies inner to each element of form, building up a
data structure of the same type, then applies outer to the result.
Recognizes all Clojure data structures. Consumes seqs."
[inner outer form]
(cond
(list? form) (outer (apply list (map inner form)))
(seq? form) (outer (doall (map inner form)))
(vector? form) (outer (vec (map inner form)))
(map? form) (outer (into (empty form) (map inner form)))
(set? form) (outer (into (empty form) (map inner form)))
:else (outer form)))
(defn postwalk
"Performs a depth-first, post-order traversal of form. Calls f on
each sub-form, uses f's return value in place of the original.
Recognizes all Clojure data structures. Consumes seqs."
[f form]
(walk (partial postwalk f) f form))
(defn prewalk
"Like postwalk, but does pre-order traversal."
[f form]
(walk (partial prewalk f) identity (f form)))
(defn postwalk-demo
"Demonstrates the behavior of postwalk by returning a lazy seq of
forms passed to the postwalk outer function during the traversal
of form."
[form]
(let [acc (atom [])]
(postwalk (fn [x] (swap! acc conj x) x) form)
@acc))
(defn prewalk-demo
"Demonstrates the behavior of prewalk by returning a lazy seq of
forms passed to the prewalk outer function during the traversal
of form."
[form]
(let [acc (atom [])]
(prewalk (fn [x] (swap! acc conj x) x) form)
@acc))
(defn postwalk-replace
"Recursively transforms form by replacing keys in smap with their
values. Like clojure.string/replace but works with any data
structure. Does replacement at the leaves of the tree first."
[smap form]
(postwalk (fn [x] (if (contains? smap x) (get smap x) x)) form))
(defn prewalk-replace
"Recursively transforms form by replacing keys in smap with their
values. Like postwalk-replace but does replacement at the root of
the tree first."
[smap form]
(prewalk (fn [x] (if (contains? smap x) (get smap x) x)) form))
(defn keywordize-keys
"Recursively transforms all map keys from strings to keywords."
[m]
(let [f (fn [[k v]] (if (string? k) [(keyword k) v] [k v]))]
(postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
(defn stringify-keys
"Recursively transforms all map keys from keywords to strings."
[m]
(let [f (fn [[k v]] (if (keyword? k) [(name k) v] [k v]))]
(postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
(defn macroexpand-all
"Recursively performs all possible macroexpansions in form."
[form]
(prewalk (fn [x] (if (seq? x) (macroexpand x) x)) form))

98
src/jolt/clojure/zip.clj Normal file
View file

@ -0,0 +1,98 @@
; Jolt Standard Library: clojure.zip
; Functional zipper for tree navigation and editing.
(defn zipper
[branch? children make-node root]
(let [z {:l [] :r [] :node root :pnodes [] :ppath nil :changed? false}]
(if (branch? root)
(let [chs (children root)]
(assoc z :l (vec (rest chs)) :node (first chs) :pnodes (conj (:pnodes z) root)))
z)))
(defn node [z] (:node z))
(defn branch? [z] (and z (not (nil? (:node z)))))
(defn make-node [z node children]
(let [m (assoc z :node node :changed? true)]
(if children (assoc m :l (vec children)) m)))
(defn path [z] (:pnodes z))
(defn left [z]
(let [ls (:l z)]
(if (and (branch? z) (seq ls))
(assoc z :l (vec (rest ls)) :node (first ls)) nil)))
(defn right [z]
(if (and (branch? z) (seq (:r z)))
(assoc z :l (conj (:l z) (:node z)) :node (first (:r z)) :r (vec (rest (:r z)))) nil))
(defn up [z]
(if (seq (path z))
(let [pn (peek (path z))]
(assoc z :l nil :r (vec (concat (conj (:l z) (:node z)) (:r z))) :node pn :pnodes (pop (path z)))) nil))
(defn down [z]
(when (branch? z)
(let [chs (children z)]
(when (seq chs)
(assoc z :node (first chs) :l [] :r (vec (rest chs)) :pnodes (conj (path z) (:node z)))))))
(defn leftmost [z]
(let [p (up z)] (if p (down p) z)))
(defn rightmost [z]
(let [p (up z)]
(if p
(let [chs (children p)]
(assoc z :node (last chs) :l (vec (butlast chs)) :r [] :pnodes (conj (pop (path z)) (:node p)))) z)))
(defn next [z]
(if (= :end z) z
(or (and (branch? z) (down z))
(right z)
(loop [p z]
(if (up p)
(or (right (up p)) (recur (up p)))
(assoc z :node :end))))))
(defn prev [z]
(if-let [l (left z)]
(loop [l l]
(if-let [d (and (branch? l) (down l))]
(recur (rightmost d)) l)) (up z)))
(defn end? [z] (= :end (:node z)))
(defn remove [z]
(if-let [p (up z)]
(let [chs (children p)
new-chs (remove #{(:node z)} chs)]
(up (make-node p (:node p) new-chs))) (assoc z :node nil)))
(defn replace [z node]
(assoc z :node node :changed? true))
(defn edit [z f & args]
(replace z (apply f (:node z) args)))
(defn insert-left [z item]
(assoc z :l (conj (:l z) item)))
(defn insert-right [z item]
(assoc z :r (into [item] (:r z))))
(defn insert-child [z item]
(assoc z :l (into [item] (:l z))))
(defn append-child [z item]
(assoc z :l (conj (vec (:l z)) item)))
(defn root [z]
(if (seq (path z)) (recur (up z)) (:node z)))
(defn vector-zip [root]
(zipper vector? seq (fn [node children] (vec children)) root))
(defn seq-zip [root]
(zipper seq? identity (fn [node children] (with-meta children (meta node))) root))

View file

@ -40,7 +40,8 @@
(defn core-every? [pred coll]
(var result true)
(each x coll (if (not (pred x)) (do (set result false) (break))))
(each x (if (set? coll) (phs-seq coll) coll)
(if (not (pred x)) (do (set result false) (break))))
result)
# ============================================================
@ -307,7 +308,7 @@
(defn core-filter [pred coll]
(var result @[])
(each x coll
(each x (if (set? coll) (phs-seq coll) coll)
(if (pred x) (array/push result x)))
(if (tuple? coll) (tuple/slice (tuple ;result)) result))
@ -317,7 +318,8 @@
(def core-reduce
(fn [& args]
(case (length args)
2 (let [f (args 0) coll (args 1)]
2 (let [f (args 0) coll (args 1)
coll (if (set? coll) (phs-seq coll) coll)]
(if (= 0 (length coll))
(f)
(do
@ -327,7 +329,8 @@
(set acc (f acc (coll i)))
(++ i))
acc)))
3 (let [f (args 0) val (args 1) coll (args 2)]
3 (let [f (args 0) val (args 1) coll (args 2)
coll (if (set? coll) (phs-seq coll) coll)]
(var acc val)
(each x coll (set acc (f acc x)))
acc)

12
src/jolt/jolt/http.clj Normal file
View file

@ -0,0 +1,12 @@
; Jolt Standard Library: jolt.http
; HTTP client using Janet's net/ module.
(defn get
[url & {:keys [headers]}]
(let [result (net/request url :get headers {})]
{:status (result :status) :body (result :body) :headers (result :headers)}))
(defn post
[url body & {:keys [headers]}]
(let [result (net/request url :post headers body)]
{:status (result :status) :body (result :body) :headers (result :headers)}))

26
src/jolt/jolt/interop.clj Normal file
View file

@ -0,0 +1,26 @@
; Jolt Standard Library: jolt.interop
; Janet interop helpers for Jolt.
(defn janet-eval
[s]
(eval (parse s)))
(defn janet-type
[x]
(type x))
(defn janet-describe
[x]
(describe x))
(defn janet-table-keys
[t]
(keys t))
(defn janet-table-vals
[t]
(vals t))
(defn janet-table->map
[t]
(into {} (map (fn [k] [k (get t k)]) (keys t))))

12
src/jolt/jolt/shell.clj Normal file
View file

@ -0,0 +1,12 @@
; Jolt Standard Library: jolt.shell
; Shell command execution via Janet's os/shell.
(defn sh
[& args]
(let [cmd (apply str (interpose " " args))
result (os/shell cmd)]
{:exit (result 0) :out (result 1) :err (result 2)}))
(defn shell
[& args]
(:out (apply sh args)))