feat: add native IO and HTTP compatibility namespaces
5757588 parent: 6cd8efc modified
src/core.nim +20 -1 | @@ -1,5 +1,5 @@ | ||
| 1 | 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 | 3 | import runtime |
| 4 | 4 | |
| 5 | 5 | proc num(v: Value): float64 = |
| @@ -544,6 +544,25 @@ proc registerCore*() = | ||
| 544 | 544 | def "slurp", proc (a: openArray[Value]): Value = mkStr(readFile(a[0].s)) |
| 545 | 545 | def "spit", proc (a: openArray[Value]): Value = |
| 546 | 546 | 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 | 566 | # Host primitive used by the source-level stdlib's now-ms wrapper. |
| 548 | 567 | def "*epoch-time-ms*", proc (a: openArray[Value]): Value = mkInt(int64(epochTime() * 1000)) |
| 549 | 568 | |
| @@ -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 runtime | 3 | 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])); NilV | 546 | 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 @@ | ||
| 1 | 1 | ## clonim reader — text -> data (forms are ordinary runtime Values, as in Clojure). |
| 2 | -import std/[strutils] | |
| 2 | +import std/[strutils, sequtils] | |
| 3 | 3 | import runtime |
| 4 | 4 | |
| 5 | 5 | type |
| @@ -7,6 +7,7 @@ type | ||
| 7 | 7 | src: string |
| 8 | 8 | pos: int |
| 9 | 9 | line: int |
| 10 | + anonId: int | |
| 10 | 11 | |
| 11 | 12 | proc peek(r: Reader): char = |
| 12 | 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 | 38 | 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 | 52 | proc readDelimited(r: var Reader, closing: char): seq[Value] = |
| 40 | 53 | result = @[] |
| 41 | 54 | while true: |
| @@ -143,7 +156,12 @@ proc readForm(r: var Reader): Value = | ||
| 143 | 156 | r.skipWs |
| 144 | 157 | return r.readForm |
| 145 | 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 | 165 | r.readerErr("Unsupported dispatch: #" & r.peek2 |
| 148 | 166 | ) |
| 149 | 167 | 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 runtime | 3 | import runtime |
| 4 | 4 | ||
| 5 | type | 5 | type |
| @@ -7,6 +7,7 @@ type | |||
| 7 | src: string | 7 | src: string |
| 8 | pos: int | 8 | pos: int |
| 9 | line: int | 9 | 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): Value | 38 | 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.skipWs | 156 | r.skipWs |
| 144 | return r.readForm | 157 | 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.peek2 | 165 | 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)) | ||