nandi/clonimpublic Fork
⑂ 5757588
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/clonim.git
git clone ssh://git@rickub.com/nandi/clonim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

feat: add native IO and HTTP compatibility namespaces

nandithebull committed 2026-09-21T00:42:04-07:00 Browse files
5757588 parent: 6cd8efc
modified src/core.nim +20 -1
@@ -1,5 +1,5 @@
11 ## clonim core — clojure.core builtins, registered into the global var table.
2-import std/[strutils, math, times, random, re]
2+import std/[strutils, math, times, random, re, os, httpclient]
33 import runtime
44
55 proc num(v: Value): float64 =
@@ -544,6 +544,25 @@ proc registerCore*() =
544544 def "slurp", proc (a: openArray[Value]): Value = mkStr(readFile(a[0].s))
545545 def "spit", proc (a: openArray[Value]): Value =
546546 writeFile(a[0].s, str(a[1])); NilV
547+ def "*delete-file*", proc (a: openArray[Value]): Value =
548+ try:
549+ removeFile(a[0].s)
550+ TrueV
551+ except OSError:
552+ if a.len > 1 and truthy(a[1]): FalseV
553+ else: raise
554+ def "*http-fetch*", proc (a: openArray[Value]): Value =
555+ ## Native replacement for the narrow Jolt HTTP API used by Freeqsay.
556+ try:
557+ let response = newHttpClient().get(a[0].s)
558+ writeFile(a[1].s, response.body)
559+ mkMap(@[(mkKeyword("outcome"), mkKeyword("ok")),
560+ (mkKeyword("status"), mkInt(response.code.int)),
561+ (mkKeyword("error"), NilV)])
562+ except CatchableError as e:
563+ mkMap(@[(mkKeyword("outcome"), mkKeyword("error")),
564+ (mkKeyword("status"), mkInt(0)),
565+ (mkKeyword("error"), mkStr(e.msg))])
547566 # Host primitive used by the source-level stdlib's now-ms wrapper.
548567 def "*epoch-time-ms*", proc (a: openArray[Value]): Value = mkInt(int64(epochTime() * 1000))
549568
@@ -1,5 +1,5 @@
1 ## clonim core — clojure.core builtins, registered into the global var table.1 ## clonim core — clojure.core builtins, registered into the global var table.
2-import std/[strutils, math, times, random, re]2+import std/[strutils, math, times, random, re, os, httpclient]
3 import runtime3 import runtime
4 4
5 proc num(v: Value): float64 =5 proc num(v: Value): float64 =
@@ -544,6 +544,25 @@ proc registerCore*() =
544 def "slurp", proc (a: openArray[Value]): Value = mkStr(readFile(a[0].s))544 def "slurp", proc (a: openArray[Value]): Value = mkStr(readFile(a[0].s))
545 def "spit", proc (a: openArray[Value]): Value =545 def "spit", proc (a: openArray[Value]): Value =
546 writeFile(a[0].s, str(a[1])); NilV546 writeFile(a[0].s, str(a[1])); NilV
547+ def "*delete-file*", proc (a: openArray[Value]): Value =
548+ try:
549+ removeFile(a[0].s)
550+ TrueV
551+ except OSError:
552+ if a.len > 1 and truthy(a[1]): FalseV
553+ else: raise
554+ def "*http-fetch*", proc (a: openArray[Value]): Value =
555+ ## Native replacement for the narrow Jolt HTTP API used by Freeqsay.
556+ try:
557+ let response = newHttpClient().get(a[0].s)
558+ writeFile(a[1].s, response.body)
559+ mkMap(@[(mkKeyword("outcome"), mkKeyword("ok")),
560+ (mkKeyword("status"), mkInt(response.code.int)),
561+ (mkKeyword("error"), NilV)])
562+ except CatchableError as e:
563+ mkMap(@[(mkKeyword("outcome"), mkKeyword("error")),
564+ (mkKeyword("status"), mkInt(0)),
565+ (mkKeyword("error"), mkStr(e.msg))])
547 # Host primitive used by the source-level stdlib's now-ms wrapper.566 # Host primitive used by the source-level stdlib's now-ms wrapper.
548 def "*epoch-time-ms*", proc (a: openArray[Value]): Value = mkInt(int64(epochTime() * 1000))567 def "*epoch-time-ms*", proc (a: openArray[Value]): Value = mkInt(int64(epochTime() * 1000))
549 568
modified src/reader.nim +20 -2
@@ -1,5 +1,5 @@
11 ## clonim reader — text -> data (forms are ordinary runtime Values, as in Clojure).
2-import std/[strutils]
2+import std/[strutils, sequtils]
33 import runtime
44
55 type
@@ -7,6 +7,7 @@ type
77 src: string
88 pos: int
99 line: int
10+ anonId: int
1011
1112 proc peek(r: Reader): char =
1213 (if r.pos < r.src.len: r.src[r.pos] else: '\0')
@@ -36,6 +37,18 @@ proc skipWs(r: var Reader) =
3637
3738 proc readForm(r: var Reader): Value
3839
40+proc replaceAnonArg(v: Value, arg: string): Value =
41+ if v.kind == kSymbol and v.s == "%": return mkSymbol(arg)
42+ case v.kind
43+ of kList: mkList(v.items.mapIt(replaceAnonArg(it, arg)))
44+ of kVector: mkVector(v.items.mapIt(replaceAnonArg(it, arg)))
45+ of kSet: mkSet(v.items.mapIt(replaceAnonArg(it, arg)))
46+ of kMap:
47+ var pairs: seq[(Value, Value)]
48+ for (k, val) in v.pairs: pairs.add (replaceAnonArg(k, arg), replaceAnonArg(val, arg))
49+ mkMap(pairs)
50+ else: v
51+
3952 proc readDelimited(r: var Reader, closing: char): seq[Value] =
4053 result = @[]
4154 while true:
@@ -143,7 +156,12 @@ proc readForm(r: var Reader): Value =
143156 r.skipWs
144157 return r.readForm
145158 if r.peek2 == '(':
146- r.readerErr("#() anonymous fn literals are not supported; use (fn [x] ...)")
159+ discard r.advance; discard r.advance
160+ inc r.anonId
161+ let arg = "anon_arg_" & $r.anonId
162+ let body = r.readDelimited(')')
163+ return mkList(@[mkSymbol("fn"), mkVector(@[mkSymbol(arg)])] &
164+ body.mapIt(replaceAnonArg(it, arg)))
147165 r.readerErr("Unsupported dispatch: #" & r.peek2
148166 )
149167 else:
@@ -1,5 +1,5 @@
1 ## clonim reader — text -> data (forms are ordinary runtime Values, as in Clojure).1 ## clonim reader — text -> data (forms are ordinary runtime Values, as in Clojure).
2-import std/[strutils]2+import std/[strutils, sequtils]
3 import runtime3 import runtime
4 4
5 type5 type
@@ -7,6 +7,7 @@ type
7 src: string7 src: string
8 pos: int8 pos: int
9 line: int9 line: int
10+ anonId: int
10 11
11 proc peek(r: Reader): char =12 proc peek(r: Reader): char =
12 (if r.pos < r.src.len: r.src[r.pos] else: '\0')13 (if r.pos < r.src.len: r.src[r.pos] else: '\0')
@@ -36,6 +37,18 @@ proc skipWs(r: var Reader) =
36 37
37 proc readForm(r: var Reader): Value38 proc readForm(r: var Reader): Value
38 39
40+proc replaceAnonArg(v: Value, arg: string): Value =
41+ if v.kind == kSymbol and v.s == "%": return mkSymbol(arg)
42+ case v.kind
43+ of kList: mkList(v.items.mapIt(replaceAnonArg(it, arg)))
44+ of kVector: mkVector(v.items.mapIt(replaceAnonArg(it, arg)))
45+ of kSet: mkSet(v.items.mapIt(replaceAnonArg(it, arg)))
46+ of kMap:
47+ var pairs: seq[(Value, Value)]
48+ for (k, val) in v.pairs: pairs.add (replaceAnonArg(k, arg), replaceAnonArg(val, arg))
49+ mkMap(pairs)
50+ else: v
51+
39 proc readDelimited(r: var Reader, closing: char): seq[Value] =52 proc readDelimited(r: var Reader, closing: char): seq[Value] =
40 result = @[]53 result = @[]
41 while true:54 while true:
@@ -143,7 +156,12 @@ proc readForm(r: var Reader): Value =
143 r.skipWs156 r.skipWs
144 return r.readForm157 return r.readForm
145 if r.peek2 == '(':158 if r.peek2 == '(':
146- r.readerErr("#() anonymous fn literals are not supported; use (fn [x] ...)")159+ discard r.advance; discard r.advance
160+ inc r.anonId
161+ let arg = "anon_arg_" & $r.anonId
162+ let body = r.readDelimited(')')
163+ return mkList(@[mkSymbol("fn"), mkVector(@[mkSymbol(arg)])] &
164+ body.mapIt(replaceAnonArg(it, arg)))
147 r.readerErr("Unsupported dispatch: #" & r.peek2165 r.readerErr("Unsupported dispatch: #" & r.peek2
148 )166 )
149 else:167 else:
added stdlib/clojure/java/io.clj +5 -0
new file mode 100644
@@ -0,0 +1,5 @@
1+(ns clojure.java.io)
2+
3+;; Native file operations supplied by Clonim's runtime.
4+(defn delete-file [path silently]
5+ (*delete-file* path silently))
new file mode 100644
@@ -0,0 +1,5 @@
1+(ns clojure.java.io)
2+
3+;; Native file operations supplied by Clonim's runtime.
4+(defn delete-file [path silently]
5+ (*delete-file* path silently))
added stdlib/jolt/mvn_http.clj +5 -0
new file mode 100644
@@ -0,0 +1,5 @@
1+(ns jolt.mvn-http)
2+
3+;; Compatibility surface for Jolt's small HTTP helper.
4+(defn fetch* [url path]
5+ (*http-fetch* url path))
new file mode 100644
@@ -0,0 +1,5 @@
1+(ns jolt.mvn-http)
2+
3+;; Compatibility surface for Jolt's small HTTP helper.
4+(defn fetch* [url path]
5+ (*http-fetch* url path))