nandi/clonimpublic Fork
⑂ c408078
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 characters and the host builtins real programs reach for

A char is now its own value kind (reader literals, printing, equality,
hashing) rather than an int, so (str (char 27)) is an escape and not "27".
Sets and keywords become callable as lookups.

Adds mapcat/into/partition-all/partition-by, format (a java.util.Formatter
subset), volatiles, char and Character/*, long and the unchecked coercions,
Integer/Long/Double parsing, Math/floor and ceil, System/exit, getProperty
and currentTimeMillis, ex-message, boolean, class, instance?, and the
missing clojure.string predicates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-21T01:03:59-07:00 Browse files
c408078 parent: 399bfb7
added examples/hostfns.clj +31 -0
new file mode 100644
@@ -0,0 +1,31 @@
1+(ns hostfns (:require [clojure.string :as str]))
2+
3+;; characters
4+(println \a \space (int \A) (char 66) (str \x \y) (char? \a)) ; a 65 B xy true
5+(println (.charAt "abc" 1) (= \a \a) (= \a 97) (#{\- \+} \+)) ; b true false +
6+(prn \a \newline \tab) ; \a \newline \tab
7+(println (Character/isDigit \7) (Character/isLetter \7)) ; true false
8+
9+;; numbers
10+(println (long 3.7) (unchecked-int 4294967295) (unchecked-byte 200)) ; 3 -1 -56
11+(println (Integer/parseInt "ff" 16) (Long/parseLong "-12")) ; 255 -12
12+(println (Double/parseDouble "2.5") (Math/floor 2.7) (Math/ceil 2.1)); 2.5 2.0 3.0
13+
14+;; format
15+(println (format "%02x|%s|%5d|%-4s|%.2f|%X|%%" 10 :kw 42 "ab" 3.14159 255))
16+
17+;; sequences
18+(println (into [] (mapcat (fn [a b] [a b]) [1 2] [:a :b]))) ; [1 :a 2 :b]
19+(println (partition-all 2 [1 2 3 4 5])) ; ((1 2) (3 4) (5))
20+(println (partition-by even? [1 3 2 4 5])) ; ((1 3) (2 4) (5))
21+(println (into #{} [1 1 2]) (into {} [[:a 1]])) ; #{1 2} {:a 1}
22+
23+;; strings
24+(println (str/blank? " ") (str/starts-with? "abc" "ab")
25+ (str/ends-with? "abc" "bc") (str/includes? "abc" "b")
26+ (str/index-of "abc" "c")) ; true true true true 2
27+
28+;; volatiles, classes, exceptions
29+(let [v (volatile! 1)] (vreset! v 9) (println @v (vswap! v + 1))) ; 9 10
30+(println (boolean nil) (instance? (class (int-array 0)) (int-array 2)))
31+(println (try (throw (ex-info "boom" {})) (catch Exception e (ex-message e))))
new file mode 100644
@@ -0,0 +1,31 @@
1+(ns hostfns (:require [clojure.string :as str]))
2+
3+;; characters
4+(println \a \space (int \A) (char 66) (str \x \y) (char? \a)) ; a 65 B xy true
5+(println (.charAt "abc" 1) (= \a \a) (= \a 97) (#{\- \+} \+)) ; b true false +
6+(prn \a \newline \tab) ; \a \newline \tab
7+(println (Character/isDigit \7) (Character/isLetter \7)) ; true false
8+
9+;; numbers
10+(println (long 3.7) (unchecked-int 4294967295) (unchecked-byte 200)) ; 3 -1 -56
11+(println (Integer/parseInt "ff" 16) (Long/parseLong "-12")) ; 255 -12
12+(println (Double/parseDouble "2.5") (Math/floor 2.7) (Math/ceil 2.1)); 2.5 2.0 3.0
13+
14+;; format
15+(println (format "%02x|%s|%5d|%-4s|%.2f|%X|%%" 10 :kw 42 "ab" 3.14159 255))
16+
17+;; sequences
18+(println (into [] (mapcat (fn [a b] [a b]) [1 2] [:a :b]))) ; [1 :a 2 :b]
19+(println (partition-all 2 [1 2 3 4 5])) ; ((1 2) (3 4) (5))
20+(println (partition-by even? [1 3 2 4 5])) ; ((1 3) (2 4) (5))
21+(println (into #{} [1 1 2]) (into {} [[:a 1]])) ; #{1 2} {:a 1}
22+
23+;; strings
24+(println (str/blank? " ") (str/starts-with? "abc" "ab")
25+ (str/ends-with? "abc" "bc") (str/includes? "abc" "b")
26+ (str/index-of "abc" "c")) ; true true true true 2
27+
28+;; volatiles, classes, exceptions
29+(let [v (volatile! 1)] (vreset! v 9) (println @v (vswap! v + 1))) ; 9 10
30+(println (boolean nil) (instance? (class (int-array 0)) (int-array 2)))
31+(println (try (throw (ex-info "boom" {})) (catch Exception e (ex-message e))))
modified src/app_runtime.nim +1 -0
@@ -30,6 +30,7 @@ iterator elems*(v: Value): Value =
3030
3131 proc mkInt*(x: int64): Value {.importc: "clonim_mk_int".}
3232 proc mkFloat*(x: float64): Value {.importc: "clonim_mk_float".}
33+proc mkChar*(code: int64): Value {.importc: "clonim_mk_char".}
3334 proc mkStr*(x: string): Value {.importc: "clonim_mk_str".}
3435 proc mkKeyword*(x: string): Value {.importc: "clonim_mk_keyword".}
3536 proc mkSymbol*(x: string): Value {.importc: "clonim_mk_symbol".}
@@ -30,6 +30,7 @@ iterator elems*(v: Value): Value =
30 30
31 proc mkInt*(x: int64): Value {.importc: "clonim_mk_int".}31 proc mkInt*(x: int64): Value {.importc: "clonim_mk_int".}
32 proc mkFloat*(x: float64): Value {.importc: "clonim_mk_float".}32 proc mkFloat*(x: float64): Value {.importc: "clonim_mk_float".}
33+proc mkChar*(code: int64): Value {.importc: "clonim_mk_char".}
33 proc mkStr*(x: string): Value {.importc: "clonim_mk_str".}34 proc mkStr*(x: string): Value {.importc: "clonim_mk_str".}
34 proc mkKeyword*(x: string): Value {.importc: "clonim_mk_keyword".}35 proc mkKeyword*(x: string): Value {.importc: "clonim_mk_keyword".}
35 proc mkSymbol*(x: string): Value {.importc: "clonim_mk_symbol".}36 proc mkSymbol*(x: string): Value {.importc: "clonim_mk_symbol".}
modified src/compiler.nim +3 -2
@@ -175,6 +175,7 @@ proc quoteLit(v: Value): string =
175175 of kBool: (if v.b: "TrueV" else: "FalseV")
176176 of kInt: "mkInt(" & $v.i & ")"
177177 of kFloat: "mkFloat(" & $v.f & ")"
178+ of kChar: "mkChar(" & $v.i & ")"
178179 of kStr: "mkStr(" & nimStr(v.s) & ")"
179180 of kKeyword: "mkKeyword(" & nimStr(v.s) & ")"
180181 of kSymbol: "mkSymbol(" & nimStr(v.s) & ")"
@@ -778,7 +779,7 @@ proc tryExpr(f: Value, env: Env, c: Ctx): string =
778779 ## Keeping a subexpression as an expression is what lets the C compiler hold
779780 ## it in a register instead of round-tripping it through a Value slot.
780781 case f.kind
781- of kNil, kBool, kInt, kFloat, kStr, kKeyword:
782+ of kNil, kBool, kInt, kFloat, kChar, kStr, kKeyword:
782783 quoteLit(f)
783784 of kSymbol:
784785 let local = env.lookup(f.s)
@@ -846,7 +847,7 @@ proc genInto(f: Value, dst: string, env: Env, c: Ctx) =
846847 if e.len > 0:
847848 c.line(dst & " = " & e); return
848849 case f.kind
849- of kNil, kBool, kInt, kFloat, kStr, kKeyword:
850+ of kNil, kBool, kInt, kFloat, kChar, kStr, kKeyword:
850851 c.line(dst & " = " & quoteLit(f))
851852 of kSymbol:
852853 let local = env.lookup(f.s)
@@ -175,6 +175,7 @@ proc quoteLit(v: Value): string =
175 of kBool: (if v.b: "TrueV" else: "FalseV")175 of kBool: (if v.b: "TrueV" else: "FalseV")
176 of kInt: "mkInt(" & $v.i & ")"176 of kInt: "mkInt(" & $v.i & ")"
177 of kFloat: "mkFloat(" & $v.f & ")"177 of kFloat: "mkFloat(" & $v.f & ")"
178+ of kChar: "mkChar(" & $v.i & ")"
178 of kStr: "mkStr(" & nimStr(v.s) & ")"179 of kStr: "mkStr(" & nimStr(v.s) & ")"
179 of kKeyword: "mkKeyword(" & nimStr(v.s) & ")"180 of kKeyword: "mkKeyword(" & nimStr(v.s) & ")"
180 of kSymbol: "mkSymbol(" & nimStr(v.s) & ")"181 of kSymbol: "mkSymbol(" & nimStr(v.s) & ")"
@@ -778,7 +779,7 @@ proc tryExpr(f: Value, env: Env, c: Ctx): string =
778 ## Keeping a subexpression as an expression is what lets the C compiler hold779 ## Keeping a subexpression as an expression is what lets the C compiler hold
779 ## it in a register instead of round-tripping it through a Value slot.780 ## it in a register instead of round-tripping it through a Value slot.
780 case f.kind781 case f.kind
781- of kNil, kBool, kInt, kFloat, kStr, kKeyword:782+ of kNil, kBool, kInt, kFloat, kChar, kStr, kKeyword:
782 quoteLit(f)783 quoteLit(f)
783 of kSymbol:784 of kSymbol:
784 let local = env.lookup(f.s)785 let local = env.lookup(f.s)
@@ -846,7 +847,7 @@ proc genInto(f: Value, dst: string, env: Env, c: Ctx) =
846 if e.len > 0:847 if e.len > 0:
847 c.line(dst & " = " & e); return848 c.line(dst & " = " & e); return
848 case f.kind849 case f.kind
849- of kNil, kBool, kInt, kFloat, kStr, kKeyword:850+ of kNil, kBool, kInt, kFloat, kChar, kStr, kKeyword:
850 c.line(dst & " = " & quoteLit(f))851 c.line(dst & " = " & quoteLit(f))
851 of kSymbol:852 of kSymbol:
852 let local = env.lookup(f.s)853 let local = env.lookup(f.s)
modified src/core.nim +187 -1
@@ -15,7 +15,7 @@ proc isFloaty(vs: openArray[Value]): bool =
1515
1616 proc intOf(v: Value): int64 =
1717 case v.kind
18- of kInt: v.i
18+ of kInt, kChar: v.i
1919 of kFloat: int64(v.f)
2020 else: err("Not a number: " & prStr(v))
2121
@@ -397,6 +397,80 @@ proc fusedCount*(base: Value, ops: openArray[FusedOp]): Value =
397397 inc n
398398 mkInt(n)
399399
400+proc parseIntRadix(s: string, radix: int): int64 =
401+ ## Java's Integer/Long.parseInt: an optional sign, then digits in `radix`.
402+ var i = 0
403+ var neg = false
404+ if i < s.len and (s[i] == '-' or s[i] == '+'):
405+ neg = s[i] == '-'; inc i
406+ if i >= s.len: err("Not a number: " & s)
407+ while i < s.len:
408+ let c = toLowerAscii(s[i])
409+ let d = (if c in '0'..'9': ord(c) - ord('0')
410+ elif c in 'a'..'z': ord(c) - ord('a') + 10
411+ else: 99)
412+ if d >= radix: err("Not a number: " & s)
413+ result = result * int64(radix) + int64(d)
414+ inc i
415+ if neg: result = -result
416+
417+proc javaFormat(fmt: string, args: openArray[Value]): string =
418+ ## The subset of java.util.Formatter that shows up in Clojure source:
419+ ## %s %d %x %X %o %c %f %e %b, with flags "-0+ " and width.precision.
420+ var ai = 0
421+ var i = 0
422+ while i < fmt.len:
423+ if fmt[i] != '%': result.add fmt[i]; inc i; continue
424+ inc i
425+ if i < fmt.len and fmt[i] == '%': result.add '%'; inc i; continue
426+ var flags = ""
427+ while i < fmt.len and fmt[i] in {'-', '0', '+', ' ', ',', '#'}:
428+ flags.add fmt[i]; inc i
429+ var width = ""
430+ while i < fmt.len and fmt[i].isDigit: width.add fmt[i]; inc i
431+ var prec = -1
432+ if i < fmt.len and fmt[i] == '.':
433+ inc i
434+ var p = ""
435+ while i < fmt.len and fmt[i].isDigit: p.add fmt[i]; inc i
436+ prec = parseInt(p)
437+ if i >= fmt.len: err("Bad format string: " & fmt)
438+ let conv = fmt[i]; inc i
439+ if ai >= args.len: err("Too few arguments for format string: " & fmt)
440+ let v = args[ai]; inc ai
441+ var body: string
442+ case conv
443+ of 's', 'S':
444+ body = str(v)
445+ if prec >= 0 and body.len > prec: body = body[0 ..< prec]
446+ if conv == 'S': body = body.toUpperAscii
447+ of 'd': body = $intOf(v)
448+ of 'x', 'X':
449+ body = toHex(intOf(v)).strip(trailing = false, chars = {'0'})
450+ if body.len == 0: body = "0"
451+ body = (if conv == 'x': body.toLowerAscii else: body.toUpperAscii)
452+ of 'o':
453+ body = toOct(intOf(v), 22).strip(trailing = false, chars = {'0'})
454+ if body.len == 0: body = "0"
455+ of 'f': body = formatFloat(num(v), ffDecimal, (if prec < 0: 6 else: prec))
456+ of 'e', 'E':
457+ body = formatFloat(num(v), ffScientific, (if prec < 0: 6 else: prec))
458+ if conv == 'E': body = body.toUpperAscii
459+ of 'b': body = (if truthy(v): "true" else: "false")
460+ of 'c': body = str(v)
461+ of 'n': body = "\n"; dec ai
462+ else: err("Unsupported format conversion: %" & conv
463+ )
464+ let w = (if width.len == 0: 0 else: parseInt(width))
465+ if body.len < w:
466+ let pad = w - body.len
467+ if '-' in flags: body = body & " ".repeat(pad)
468+ elif '0' in flags and conv in {'d', 'x', 'X', 'o', 'f', 'e', 'E'}:
469+ if body.len > 0 and body[0] == '-': body = "-" & "0".repeat(pad) & body[1 .. ^1]
470+ else: body = "0".repeat(pad) & body
471+ else: body = " ".repeat(pad) & body
472+ result.add body
473+
400474 proc registerCore*() =
401475 # ---- arithmetic
402476 def "+", proc (a: openArray[Value]): Value =
@@ -465,6 +539,37 @@ proc registerCore*() =
465539 def "rand-int", proc (a: openArray[Value]): Value = mkInt(rand(int(intOf(a[0])) - 1))
466540 def "double", proc (a: openArray[Value]): Value = mkFloat(num(a[0]))
467541 def "int", proc (a: openArray[Value]): Value = mkInt(intOf(a[0]))
542+ def "char", proc (a: openArray[Value]): Value =
543+ (if a[0].kind == kChar: a[0] else: mkChar(intOf(a[0])))
544+ def "char?", proc (a: openArray[Value]): Value = mkBool(a[0].kind == kChar)
545+ def ".charAt", proc (a: openArray[Value]): Value =
546+ let i = int(intOf(a[1]))
547+ if i < 0 or i >= a[0].s.len: err("String index out of range: " & $i)
548+ mkChar(int64(ord(a[0].s[i])))
549+ def "Character/isDigit", proc (a: openArray[Value]): Value =
550+ mkBool(a[0].kind == kChar and a[0].i >= int64(ord('0')) and a[0].i <= int64(ord('9')))
551+ def "Character/isLetter", proc (a: openArray[Value]): Value =
552+ mkBool(a[0].kind == kChar and char(a[0].i) in Letters)
553+ def "Character/isWhitespace", proc (a: openArray[Value]): Value =
554+ mkBool(a[0].kind == kChar and char(a[0].i) in {' ', '\t', '\n', '\r', '\f', '\v'})
555+ def "long", proc (a: openArray[Value]): Value = mkInt(intOf(a[0]))
556+ def "unchecked-int", proc (a: openArray[Value]): Value =
557+ mkInt(int64(cast[int32](uint32(intOf(a[0]) and 0xffffffff'i64))))
558+ def "unchecked-byte", proc (a: openArray[Value]): Value =
559+ mkInt(int64(cast[int8](uint8(intOf(a[0]) and 0xff))))
560+ def "Math/floor", proc (a: openArray[Value]): Value = mkFloat(floor(num(a[0])))
561+ def "Math/ceil", proc (a: openArray[Value]): Value = mkFloat(ceil(num(a[0])))
562+ def "Math/abs", proc (a: openArray[Value]): Value =
563+ (if a[0].kind == kFloat: mkFloat(abs(a[0].f)) else: mkInt(abs(a[0].i)))
564+ def "Integer/parseInt", proc (a: openArray[Value]): Value =
565+ let radix = (if a.len > 1: int(intOf(a[1])) else: 10)
566+ mkInt(parseIntRadix(a[0].s.strip, radix))
567+ def "Long/parseLong", proc (a: openArray[Value]): Value =
568+ let radix = (if a.len > 1: int(intOf(a[1])) else: 10)
569+ mkInt(parseIntRadix(a[0].s.strip, radix))
570+ def "Double/parseDouble", proc (a: openArray[Value]): Value =
571+ try: mkFloat(parseFloat(a[0].s.strip))
572+ except ValueError: err("Not a number: " & a[0].s)
468573
469574 # ---- comparison / predicates
470575 def "=", proc (a: openArray[Value]): Value =
@@ -780,6 +885,45 @@ proc registerCore*() =
780885 r.add mkList(s[i ..< i + n]); i += n
781886 mkList(r)
782887
888+ def "partition-all", proc (a: openArray[Value]): Value =
889+ let n = int(intOf(a[0]))
890+ let step = (if a.len > 2: int(intOf(a[1])) else: n)
891+ let s = toSeq(a[^1])
892+ var r: seq[Value] = @[]
893+ var i = 0
894+ while i < s.len:
895+ r.add mkList(s[i ..< min(i + n, s.len)]); i += step
896+ mkList(r)
897+ def "partition-by", proc (a: openArray[Value]): Value =
898+ let s = toSeq(a[1])
899+ var r: seq[Value] = @[]
900+ var run: seq[Value] = @[]
901+ var key = NilV
902+ for x in s:
903+ let k = call(a[0], [x])
904+ if run.len == 0 or equals(k, key): run.add x
905+ else:
906+ r.add mkList(run); run = @[x]
907+ key = k
908+ if run.len > 0: r.add mkList(run)
909+ mkList(r)
910+ def "into", proc (a: openArray[Value]): Value =
911+ result = a[0]
912+ if a.len > 1:
913+ for x in elems(a[1]): result = conjOne(result, x)
914+ def "mapcat", proc (a: openArray[Value]): Value =
915+ ## Like map, then concat: the fn takes one element from each collection.
916+ var colls: seq[seq[Value]] = @[]
917+ for i in 1 ..< a.len: colls.add toSeq(a[i])
918+ var n = -1
919+ for c in colls: (if n < 0 or c.len < n: n = c.len)
920+ var r: seq[Value] = @[]
921+ for i in 0 ..< max(n, 0):
922+ var args: seq[Value] = @[]
923+ for c in colls: args.add c[i]
924+ for x in elems(call(a[0], args)): r.add x
925+ mkList(r)
926+
783927 # ---- higher order
784928 def "apply", proc (a: openArray[Value]): Value =
785929 var callArgs: seq[Value] = @[]
@@ -879,6 +1023,48 @@ proc registerCore*() =
8791023 let nv = call(a[1], @[cur] & @(a[2 .. ^1]))
8801024 call(a[0], [mkKeyword("set"), nv])
8811025
1026+ def "ex-message", proc (a: openArray[Value]): Value =
1027+ ## Thrown values reach a catch clause as their message string.
1028+ (if a[0].kind == kStr: a[0] else: NilV)
1029+ def "volatile!", proc (a: openArray[Value]): Value =
1030+ var cell = a[0]
1031+ mkFn("volatile", proc (args: openArray[Value]): Value =
1032+ if args.len == 0: return cell
1033+ cell = args[1]
1034+ cell)
1035+ def "vreset!", proc (a: openArray[Value]): Value =
1036+ call(a[0], [mkKeyword("set"), a[1]])
1037+ def "vswap!", proc (a: openArray[Value]): Value =
1038+ let cur = call(a[0], [])
1039+ call(a[0], [mkKeyword("set"), call(a[1], @[cur] & @(a[2 .. ^1]))])
1040+ def "boolean", proc (a: openArray[Value]): Value = mkBool(truthy(a[0]))
1041+ def "class", proc (a: openArray[Value]): Value = mkKeyword($a[0].kind)
1042+ def "instance?", proc (a: openArray[Value]): Value =
1043+ mkBool(a[0].kind == kKeyword and a[0].s == $a[1].kind)
1044+ def "format", proc (a: openArray[Value]): Value = mkStr(javaFormat(a[0].s, a[1 .. ^1]))
1045+ def "System/exit", proc (a: openArray[Value]): Value =
1046+ quit(if a.len > 0: int(intOf(a[0])) else: 0)
1047+ def "System/currentTimeMillis", proc (a: openArray[Value]): Value =
1048+ mkInt(int64(epochTime() * 1000))
1049+ def "System/getProperty", proc (a: openArray[Value]): Value =
1050+ ## Only the properties a hosted program can reasonably expect here.
1051+ case a[0].s
1052+ of "java.io.tmpdir": mkStr(getTempDir().strip(leading = false, chars = {'/'}))
1053+ of "user.dir": mkStr(getCurrentDir())
1054+ of "user.name": mkStr(getEnv("USER"))
1055+ of "line.separator": mkStr("\n")
1056+ else: (if a.len > 1: a[1] else: NilV)
1057+ def "clojure.string/blank?", proc (a: openArray[Value]): Value =
1058+ mkBool(a[0].kind == kNil or a[0].s.strip.len == 0)
1059+ def "clojure.string/starts-with?", proc (a: openArray[Value]): Value =
1060+ mkBool(a[0].s.startsWith(a[1].s))
1061+ def "clojure.string/ends-with?", proc (a: openArray[Value]): Value =
1062+ mkBool(a[0].s.endsWith(a[1].s))
1063+ def "clojure.string/includes?", proc (a: openArray[Value]): Value =
1064+ mkBool(a[1].s in a[0].s)
1065+ def "clojure.string/index-of", proc (a: openArray[Value]): Value =
1066+ let i = a[0].s.find(a[1].s)
1067+ (if i < 0: NilV else: mkInt(int64(i)))
8821068 def "throw", proc (a: openArray[Value]): Value = err(str(a[0]))
8831069 def "ex-info", proc (a: openArray[Value]): Value = mkStr(str(a[0]))
8841070 def "time-ms", proc (a: openArray[Value]): Value = mkInt(int64(epochTime() * 1000))
@@ -15,7 +15,7 @@ proc isFloaty(vs: openArray[Value]): bool =
15 15
16 proc intOf(v: Value): int64 =16 proc intOf(v: Value): int64 =
17 case v.kind17 case v.kind
18- of kInt: v.i18+ of kInt, kChar: v.i
19 of kFloat: int64(v.f)19 of kFloat: int64(v.f)
20 else: err("Not a number: " & prStr(v))20 else: err("Not a number: " & prStr(v))
21 21
@@ -397,6 +397,80 @@ proc fusedCount*(base: Value, ops: openArray[FusedOp]): Value =
397 inc n397 inc n
398 mkInt(n)398 mkInt(n)
399 399
400+proc parseIntRadix(s: string, radix: int): int64 =
401+ ## Java's Integer/Long.parseInt: an optional sign, then digits in `radix`.
402+ var i = 0
403+ var neg = false
404+ if i < s.len and (s[i] == '-' or s[i] == '+'):
405+ neg = s[i] == '-'; inc i
406+ if i >= s.len: err("Not a number: " & s)
407+ while i < s.len:
408+ let c = toLowerAscii(s[i])
409+ let d = (if c in '0'..'9': ord(c) - ord('0')
410+ elif c in 'a'..'z': ord(c) - ord('a') + 10
411+ else: 99)
412+ if d >= radix: err("Not a number: " & s)
413+ result = result * int64(radix) + int64(d)
414+ inc i
415+ if neg: result = -result
416+
417+proc javaFormat(fmt: string, args: openArray[Value]): string =
418+ ## The subset of java.util.Formatter that shows up in Clojure source:
419+ ## %s %d %x %X %o %c %f %e %b, with flags "-0+ " and width.precision.
420+ var ai = 0
421+ var i = 0
422+ while i < fmt.len:
423+ if fmt[i] != '%': result.add fmt[i]; inc i; continue
424+ inc i
425+ if i < fmt.len and fmt[i] == '%': result.add '%'; inc i; continue
426+ var flags = ""
427+ while i < fmt.len and fmt[i] in {'-', '0', '+', ' ', ',', '#'}:
428+ flags.add fmt[i]; inc i
429+ var width = ""
430+ while i < fmt.len and fmt[i].isDigit: width.add fmt[i]; inc i
431+ var prec = -1
432+ if i < fmt.len and fmt[i] == '.':
433+ inc i
434+ var p = ""
435+ while i < fmt.len and fmt[i].isDigit: p.add fmt[i]; inc i
436+ prec = parseInt(p)
437+ if i >= fmt.len: err("Bad format string: " & fmt)
438+ let conv = fmt[i]; inc i
439+ if ai >= args.len: err("Too few arguments for format string: " & fmt)
440+ let v = args[ai]; inc ai
441+ var body: string
442+ case conv
443+ of 's', 'S':
444+ body = str(v)
445+ if prec >= 0 and body.len > prec: body = body[0 ..< prec]
446+ if conv == 'S': body = body.toUpperAscii
447+ of 'd': body = $intOf(v)
448+ of 'x', 'X':
449+ body = toHex(intOf(v)).strip(trailing = false, chars = {'0'})
450+ if body.len == 0: body = "0"
451+ body = (if conv == 'x': body.toLowerAscii else: body.toUpperAscii)
452+ of 'o':
453+ body = toOct(intOf(v), 22).strip(trailing = false, chars = {'0'})
454+ if body.len == 0: body = "0"
455+ of 'f': body = formatFloat(num(v), ffDecimal, (if prec < 0: 6 else: prec))
456+ of 'e', 'E':
457+ body = formatFloat(num(v), ffScientific, (if prec < 0: 6 else: prec))
458+ if conv == 'E': body = body.toUpperAscii
459+ of 'b': body = (if truthy(v): "true" else: "false")
460+ of 'c': body = str(v)
461+ of 'n': body = "\n"; dec ai
462+ else: err("Unsupported format conversion: %" & conv
463+ )
464+ let w = (if width.len == 0: 0 else: parseInt(width))
465+ if body.len < w:
466+ let pad = w - body.len
467+ if '-' in flags: body = body & " ".repeat(pad)
468+ elif '0' in flags and conv in {'d', 'x', 'X', 'o', 'f', 'e', 'E'}:
469+ if body.len > 0 and body[0] == '-': body = "-" & "0".repeat(pad) & body[1 .. ^1]
470+ else: body = "0".repeat(pad) & body
471+ else: body = " ".repeat(pad) & body
472+ result.add body
473+
400 proc registerCore*() =474 proc registerCore*() =
401 # ---- arithmetic475 # ---- arithmetic
402 def "+", proc (a: openArray[Value]): Value =476 def "+", proc (a: openArray[Value]): Value =
@@ -465,6 +539,37 @@ proc registerCore*() =
465 def "rand-int", proc (a: openArray[Value]): Value = mkInt(rand(int(intOf(a[0])) - 1))539 def "rand-int", proc (a: openArray[Value]): Value = mkInt(rand(int(intOf(a[0])) - 1))
466 def "double", proc (a: openArray[Value]): Value = mkFloat(num(a[0]))540 def "double", proc (a: openArray[Value]): Value = mkFloat(num(a[0]))
467 def "int", proc (a: openArray[Value]): Value = mkInt(intOf(a[0]))541 def "int", proc (a: openArray[Value]): Value = mkInt(intOf(a[0]))
542+ def "char", proc (a: openArray[Value]): Value =
543+ (if a[0].kind == kChar: a[0] else: mkChar(intOf(a[0])))
544+ def "char?", proc (a: openArray[Value]): Value = mkBool(a[0].kind == kChar)
545+ def ".charAt", proc (a: openArray[Value]): Value =
546+ let i = int(intOf(a[1]))
547+ if i < 0 or i >= a[0].s.len: err("String index out of range: " & $i)
548+ mkChar(int64(ord(a[0].s[i])))
549+ def "Character/isDigit", proc (a: openArray[Value]): Value =
550+ mkBool(a[0].kind == kChar and a[0].i >= int64(ord('0')) and a[0].i <= int64(ord('9')))
551+ def "Character/isLetter", proc (a: openArray[Value]): Value =
552+ mkBool(a[0].kind == kChar and char(a[0].i) in Letters)
553+ def "Character/isWhitespace", proc (a: openArray[Value]): Value =
554+ mkBool(a[0].kind == kChar and char(a[0].i) in {' ', '\t', '\n', '\r', '\f', '\v'})
555+ def "long", proc (a: openArray[Value]): Value = mkInt(intOf(a[0]))
556+ def "unchecked-int", proc (a: openArray[Value]): Value =
557+ mkInt(int64(cast[int32](uint32(intOf(a[0]) and 0xffffffff'i64))))
558+ def "unchecked-byte", proc (a: openArray[Value]): Value =
559+ mkInt(int64(cast[int8](uint8(intOf(a[0]) and 0xff))))
560+ def "Math/floor", proc (a: openArray[Value]): Value = mkFloat(floor(num(a[0])))
561+ def "Math/ceil", proc (a: openArray[Value]): Value = mkFloat(ceil(num(a[0])))
562+ def "Math/abs", proc (a: openArray[Value]): Value =
563+ (if a[0].kind == kFloat: mkFloat(abs(a[0].f)) else: mkInt(abs(a[0].i)))
564+ def "Integer/parseInt", proc (a: openArray[Value]): Value =
565+ let radix = (if a.len > 1: int(intOf(a[1])) else: 10)
566+ mkInt(parseIntRadix(a[0].s.strip, radix))
567+ def "Long/parseLong", proc (a: openArray[Value]): Value =
568+ let radix = (if a.len > 1: int(intOf(a[1])) else: 10)
569+ mkInt(parseIntRadix(a[0].s.strip, radix))
570+ def "Double/parseDouble", proc (a: openArray[Value]): Value =
571+ try: mkFloat(parseFloat(a[0].s.strip))
572+ except ValueError: err("Not a number: " & a[0].s)
468 573
469 # ---- comparison / predicates574 # ---- comparison / predicates
470 def "=", proc (a: openArray[Value]): Value =575 def "=", proc (a: openArray[Value]): Value =
@@ -780,6 +885,45 @@ proc registerCore*() =
780 r.add mkList(s[i ..< i + n]); i += n885 r.add mkList(s[i ..< i + n]); i += n
781 mkList(r)886 mkList(r)
782 887
888+ def "partition-all", proc (a: openArray[Value]): Value =
889+ let n = int(intOf(a[0]))
890+ let step = (if a.len > 2: int(intOf(a[1])) else: n)
891+ let s = toSeq(a[^1])
892+ var r: seq[Value] = @[]
893+ var i = 0
894+ while i < s.len:
895+ r.add mkList(s[i ..< min(i + n, s.len)]); i += step
896+ mkList(r)
897+ def "partition-by", proc (a: openArray[Value]): Value =
898+ let s = toSeq(a[1])
899+ var r: seq[Value] = @[]
900+ var run: seq[Value] = @[]
901+ var key = NilV
902+ for x in s:
903+ let k = call(a[0], [x])
904+ if run.len == 0 or equals(k, key): run.add x
905+ else:
906+ r.add mkList(run); run = @[x]
907+ key = k
908+ if run.len > 0: r.add mkList(run)
909+ mkList(r)
910+ def "into", proc (a: openArray[Value]): Value =
911+ result = a[0]
912+ if a.len > 1:
913+ for x in elems(a[1]): result = conjOne(result, x)
914+ def "mapcat", proc (a: openArray[Value]): Value =
915+ ## Like map, then concat: the fn takes one element from each collection.
916+ var colls: seq[seq[Value]] = @[]
917+ for i in 1 ..< a.len: colls.add toSeq(a[i])
918+ var n = -1
919+ for c in colls: (if n < 0 or c.len < n: n = c.len)
920+ var r: seq[Value] = @[]
921+ for i in 0 ..< max(n, 0):
922+ var args: seq[Value] = @[]
923+ for c in colls: args.add c[i]
924+ for x in elems(call(a[0], args)): r.add x
925+ mkList(r)
926+
783 # ---- higher order927 # ---- higher order
784 def "apply", proc (a: openArray[Value]): Value =928 def "apply", proc (a: openArray[Value]): Value =
785 var callArgs: seq[Value] = @[]929 var callArgs: seq[Value] = @[]
@@ -879,6 +1023,48 @@ proc registerCore*() =
879 let nv = call(a[1], @[cur] & @(a[2 .. ^1]))1023 let nv = call(a[1], @[cur] & @(a[2 .. ^1]))
880 call(a[0], [mkKeyword("set"), nv])1024 call(a[0], [mkKeyword("set"), nv])
881 1025
1026+ def "ex-message", proc (a: openArray[Value]): Value =
1027+ ## Thrown values reach a catch clause as their message string.
1028+ (if a[0].kind == kStr: a[0] else: NilV)
1029+ def "volatile!", proc (a: openArray[Value]): Value =
1030+ var cell = a[0]
1031+ mkFn("volatile", proc (args: openArray[Value]): Value =
1032+ if args.len == 0: return cell
1033+ cell = args[1]
1034+ cell)
1035+ def "vreset!", proc (a: openArray[Value]): Value =
1036+ call(a[0], [mkKeyword("set"), a[1]])
1037+ def "vswap!", proc (a: openArray[Value]): Value =
1038+ let cur = call(a[0], [])
1039+ call(a[0], [mkKeyword("set"), call(a[1], @[cur] & @(a[2 .. ^1]))])
1040+ def "boolean", proc (a: openArray[Value]): Value = mkBool(truthy(a[0]))
1041+ def "class", proc (a: openArray[Value]): Value = mkKeyword($a[0].kind)
1042+ def "instance?", proc (a: openArray[Value]): Value =
1043+ mkBool(a[0].kind == kKeyword and a[0].s == $a[1].kind)
1044+ def "format", proc (a: openArray[Value]): Value = mkStr(javaFormat(a[0].s, a[1 .. ^1]))
1045+ def "System/exit", proc (a: openArray[Value]): Value =
1046+ quit(if a.len > 0: int(intOf(a[0])) else: 0)
1047+ def "System/currentTimeMillis", proc (a: openArray[Value]): Value =
1048+ mkInt(int64(epochTime() * 1000))
1049+ def "System/getProperty", proc (a: openArray[Value]): Value =
1050+ ## Only the properties a hosted program can reasonably expect here.
1051+ case a[0].s
1052+ of "java.io.tmpdir": mkStr(getTempDir().strip(leading = false, chars = {'/'}))
1053+ of "user.dir": mkStr(getCurrentDir())
1054+ of "user.name": mkStr(getEnv("USER"))
1055+ of "line.separator": mkStr("\n")
1056+ else: (if a.len > 1: a[1] else: NilV)
1057+ def "clojure.string/blank?", proc (a: openArray[Value]): Value =
1058+ mkBool(a[0].kind == kNil or a[0].s.strip.len == 0)
1059+ def "clojure.string/starts-with?", proc (a: openArray[Value]): Value =
1060+ mkBool(a[0].s.startsWith(a[1].s))
1061+ def "clojure.string/ends-with?", proc (a: openArray[Value]): Value =
1062+ mkBool(a[0].s.endsWith(a[1].s))
1063+ def "clojure.string/includes?", proc (a: openArray[Value]): Value =
1064+ mkBool(a[1].s in a[0].s)
1065+ def "clojure.string/index-of", proc (a: openArray[Value]): Value =
1066+ let i = a[0].s.find(a[1].s)
1067+ (if i < 0: NilV else: mkInt(int64(i)))
882 def "throw", proc (a: openArray[Value]): Value = err(str(a[0]))1068 def "throw", proc (a: openArray[Value]): Value = err(str(a[0]))
883 def "ex-info", proc (a: openArray[Value]): Value = mkStr(str(a[0]))1069 def "ex-info", proc (a: openArray[Value]): Value = mkStr(str(a[0]))
884 def "time-ms", proc (a: openArray[Value]): Value = mkInt(int64(epochTime() * 1000))1070 def "time-ms", proc (a: openArray[Value]): Value = mkInt(int64(epochTime() * 1000))
modified src/reader.nim +29 -0
@@ -80,6 +80,33 @@ proc readString(r: var Reader): Value =
8080 s.add c
8181 mkStr(s)
8282
83+proc readChar(r: var Reader): Value =
84+ ## A character literal: \a, \newline, \uXXXX, or any single character —
85+ ## including the delimiters, which never start a name here.
86+ discard r.advance # the backslash
87+ if r.pos >= r.src.len: r.readerErr("EOF while reading character")
88+ let first = r.advance
89+ var name = $first
90+ if first in Letters or first in Digits:
91+ while r.pos < r.src.len:
92+ let c = r.peek
93+ if c in {' ', '\t', '\n', '\r', ','} or (c in macroChars and c != '\''): break
94+ name.add r.advance
95+ if name.len == 1: return mkChar(int64(ord(name[0])))
96+ case name
97+ of "newline": mkChar(10)
98+ of "tab": mkChar(9)
99+ of "return": mkChar(13)
100+ of "space": mkChar(32)
101+ of "backspace": mkChar(8)
102+ of "formfeed": mkChar(12)
103+ else:
104+ if name[0] == 'u' and name.len == 5:
105+ try: return mkChar(int64(parseHexInt(name[1 .. ^1])))
106+ except ValueError: discard
107+ r.readerErr("Unsupported character literal: \\" & name)
108+ NilV
109+
83110 proc readToken(r: var Reader): string =
84111 result = ""
85112 while r.pos < r.src.len:
@@ -135,6 +162,8 @@ proc readForm(r: var Reader): Value =
135162 r.readerErr("Unmatched delimiter: " & c)
136163 of '"':
137164 return r.readString
165+ of '\\':
166+ return r.readChar
138167 of '\'':
139168 discard r.advance
140169 return mkList(@[mkSymbol("quote"), r.readForm])
@@ -80,6 +80,33 @@ proc readString(r: var Reader): Value =
80 s.add c80 s.add c
81 mkStr(s)81 mkStr(s)
82 82
83+proc readChar(r: var Reader): Value =
84+ ## A character literal: \a, \newline, \uXXXX, or any single character —
85+ ## including the delimiters, which never start a name here.
86+ discard r.advance # the backslash
87+ if r.pos >= r.src.len: r.readerErr("EOF while reading character")
88+ let first = r.advance
89+ var name = $first
90+ if first in Letters or first in Digits:
91+ while r.pos < r.src.len:
92+ let c = r.peek
93+ if c in {' ', '\t', '\n', '\r', ','} or (c in macroChars and c != '\''): break
94+ name.add r.advance
95+ if name.len == 1: return mkChar(int64(ord(name[0])))
96+ case name
97+ of "newline": mkChar(10)
98+ of "tab": mkChar(9)
99+ of "return": mkChar(13)
100+ of "space": mkChar(32)
101+ of "backspace": mkChar(8)
102+ of "formfeed": mkChar(12)
103+ else:
104+ if name[0] == 'u' and name.len == 5:
105+ try: return mkChar(int64(parseHexInt(name[1 .. ^1])))
106+ except ValueError: discard
107+ r.readerErr("Unsupported character literal: \\" & name)
108+ NilV
109+
83 proc readToken(r: var Reader): string =110 proc readToken(r: var Reader): string =
84 result = ""111 result = ""
85 while r.pos < r.src.len:112 while r.pos < r.src.len:
@@ -135,6 +162,8 @@ proc readForm(r: var Reader): Value =
135 r.readerErr("Unmatched delimiter: " & c)162 r.readerErr("Unmatched delimiter: " & c)
136 of '"':163 of '"':
137 return r.readString164 return r.readString
165+ of '\\':
166+ return r.readChar
138 of '\'':167 of '\'':
139 discard r.advance168 discard r.advance
140 return mkList(@[mkSymbol("quote"), r.readForm])169 return mkList(@[mkSymbol("quote"), r.readForm])
modified src/runtime.nim +21 -2
@@ -6,7 +6,7 @@
66 ## collection. Maps and sets additionally remember insertion order — every
77 ## entry carries an `ord` stamp and iteration sorts by it — so printing and
88 ## `keys`/`vals` stay predictable the way Clojure's small array-maps are.
9-import std/[tables, strutils, hashes, bitops, algorithm]
9+import std/[tables, strutils, hashes, bitops, algorithm, unicode]
1010 import runtime_types
1111 export runtime_types
1212
@@ -267,6 +267,7 @@ proc mapEntries*(m: PMap): seq[MEntry] =
267267 proc mkBool*(x: bool): Value = (if x: TrueV else: FalseV)
268268 proc mkInt*(x: int64): Value {.inline.} = Value(kind: kInt, raw: x)
269269 proc mkFloat*(x: float64): Value {.inline.} = Value(kind: kFloat, raw: cast[int64](x))
270+proc mkChar*(code: int64): Value {.inline.} = Value(kind: kChar, raw: code)
270271 proc mkStr*(x: string): Value = Value(kind: kStr, obj: Obj(kind: kStr, s: x))
271272 proc mkKeyword*(x: string): Value = Value(kind: kKeyword, obj: Obj(kind: kKeyword, s: x))
272273 proc mkSymbol*(x: string): Value = Value(kind: kSymbol, obj: Obj(kind: kSymbol, s: x))
@@ -445,6 +446,7 @@ proc hashValue*(v: Value): uint32 =
445446 of kNil: 0'u32
446447 of kBool: (if v.b: 0x9e3779b9'u32 else: 0x85ebca6b'u32)
447448 of kInt: uint32(hash(v.i))
449+ of kChar: mixHash(5'u32, uint32(hash(v.i)))
448450 of kFloat:
449451 # ints and floats compare equal across kinds, so they must hash alike
450452 if v.f == float64(int64(v.f)): uint32(hash(int64(v.f)))
@@ -536,6 +538,7 @@ proc equals*(a, b: Value): bool =
536538 of kNil: true
537539 of kBool: a.b == b.b
538540 of kInt: a.i == b.i
541+ of kChar: a.i == b.i
539542 of kFloat: a.f == b.f
540543 of kStr, kKeyword, kSymbol: a.s == b.s
541544 of kSet:
@@ -574,6 +577,17 @@ proc toStr*(v: Value, readable: bool): string =
574577 var s = $v.f
575578 if '.' notin s and 'e' notin s and 'n' notin s and 'i' notin s: s &= ".0"
576579 s
580+ of kChar:
581+ if not readable: $Rune(v.i)
582+ else:
583+ case v.i
584+ of 10: "\\newline"
585+ of 9: "\\tab"
586+ of 13: "\\return"
587+ of 8: "\\backspace"
588+ of 12: "\\formfeed"
589+ of 32: "\\space"
590+ else: "\\" & $Rune(v.i)
577591 of kStr: (if readable: escapeStr(v.s) else: v.s)
578592 of kKeyword: ":" & v.s
579593 of kSymbol: v.s
@@ -635,11 +649,16 @@ proc call*(f: Value, args: openArray[Value]): Value =
635649 # (:k m) => lookup
636650 if args.len == 0: err("Wrong number of args to keyword")
637651 let m = args[0]
638- if m.isNil or m.kind != kMap: return NilV
652+ if m.isNil or m.kind notin {kMap, kSet}: return NilV
639653 mapGet(m.m, f, (if args.len > 1: args[1] else: NilV))
640654 of kMap:
641655 if args.len == 0: err("Wrong number of args to map")
642656 mapGet(f.m, args[0], (if args.len > 1: args[1] else: NilV))
657+ of kSet:
658+ # (#{...} x) => the element, or nil
659+ if args.len == 0: err("Wrong number of args to set")
660+ if mapContains(f.m, args[0]): args[0]
661+ else: (if args.len > 1: args[1] else: NilV)
643662 of kVector:
644663 if args.len != 1 or args[0].kind != kInt: err("Vector lookup needs one int")
645664 vecNth(f.vec, int(args[0].i))
@@ -6,7 +6,7 @@
6 ## collection. Maps and sets additionally remember insertion order — every6 ## collection. Maps and sets additionally remember insertion order — every
7 ## entry carries an `ord` stamp and iteration sorts by it — so printing and7 ## entry carries an `ord` stamp and iteration sorts by it — so printing and
8 ## `keys`/`vals` stay predictable the way Clojure's small array-maps are.8 ## `keys`/`vals` stay predictable the way Clojure's small array-maps are.
9-import std/[tables, strutils, hashes, bitops, algorithm]9+import std/[tables, strutils, hashes, bitops, algorithm, unicode]
10 import runtime_types10 import runtime_types
11 export runtime_types11 export runtime_types
12 12
@@ -267,6 +267,7 @@ proc mapEntries*(m: PMap): seq[MEntry] =
267 proc mkBool*(x: bool): Value = (if x: TrueV else: FalseV)267 proc mkBool*(x: bool): Value = (if x: TrueV else: FalseV)
268 proc mkInt*(x: int64): Value {.inline.} = Value(kind: kInt, raw: x)268 proc mkInt*(x: int64): Value {.inline.} = Value(kind: kInt, raw: x)
269 proc mkFloat*(x: float64): Value {.inline.} = Value(kind: kFloat, raw: cast[int64](x))269 proc mkFloat*(x: float64): Value {.inline.} = Value(kind: kFloat, raw: cast[int64](x))
270+proc mkChar*(code: int64): Value {.inline.} = Value(kind: kChar, raw: code)
270 proc mkStr*(x: string): Value = Value(kind: kStr, obj: Obj(kind: kStr, s: x))271 proc mkStr*(x: string): Value = Value(kind: kStr, obj: Obj(kind: kStr, s: x))
271 proc mkKeyword*(x: string): Value = Value(kind: kKeyword, obj: Obj(kind: kKeyword, s: x))272 proc mkKeyword*(x: string): Value = Value(kind: kKeyword, obj: Obj(kind: kKeyword, s: x))
272 proc mkSymbol*(x: string): Value = Value(kind: kSymbol, obj: Obj(kind: kSymbol, s: x))273 proc mkSymbol*(x: string): Value = Value(kind: kSymbol, obj: Obj(kind: kSymbol, s: x))
@@ -445,6 +446,7 @@ proc hashValue*(v: Value): uint32 =
445 of kNil: 0'u32446 of kNil: 0'u32
446 of kBool: (if v.b: 0x9e3779b9'u32 else: 0x85ebca6b'u32)447 of kBool: (if v.b: 0x9e3779b9'u32 else: 0x85ebca6b'u32)
447 of kInt: uint32(hash(v.i))448 of kInt: uint32(hash(v.i))
449+ of kChar: mixHash(5'u32, uint32(hash(v.i)))
448 of kFloat:450 of kFloat:
449 # ints and floats compare equal across kinds, so they must hash alike451 # ints and floats compare equal across kinds, so they must hash alike
450 if v.f == float64(int64(v.f)): uint32(hash(int64(v.f)))452 if v.f == float64(int64(v.f)): uint32(hash(int64(v.f)))
@@ -536,6 +538,7 @@ proc equals*(a, b: Value): bool =
536 of kNil: true538 of kNil: true
537 of kBool: a.b == b.b539 of kBool: a.b == b.b
538 of kInt: a.i == b.i540 of kInt: a.i == b.i
541+ of kChar: a.i == b.i
539 of kFloat: a.f == b.f542 of kFloat: a.f == b.f
540 of kStr, kKeyword, kSymbol: a.s == b.s543 of kStr, kKeyword, kSymbol: a.s == b.s
541 of kSet:544 of kSet:
@@ -574,6 +577,17 @@ proc toStr*(v: Value, readable: bool): string =
574 var s = $v.f577 var s = $v.f
575 if '.' notin s and 'e' notin s and 'n' notin s and 'i' notin s: s &= ".0"578 if '.' notin s and 'e' notin s and 'n' notin s and 'i' notin s: s &= ".0"
576 s579 s
580+ of kChar:
581+ if not readable: $Rune(v.i)
582+ else:
583+ case v.i
584+ of 10: "\\newline"
585+ of 9: "\\tab"
586+ of 13: "\\return"
587+ of 8: "\\backspace"
588+ of 12: "\\formfeed"
589+ of 32: "\\space"
590+ else: "\\" & $Rune(v.i)
577 of kStr: (if readable: escapeStr(v.s) else: v.s)591 of kStr: (if readable: escapeStr(v.s) else: v.s)
578 of kKeyword: ":" & v.s592 of kKeyword: ":" & v.s
579 of kSymbol: v.s593 of kSymbol: v.s
@@ -635,11 +649,16 @@ proc call*(f: Value, args: openArray[Value]): Value =
635 # (:k m) => lookup649 # (:k m) => lookup
636 if args.len == 0: err("Wrong number of args to keyword")650 if args.len == 0: err("Wrong number of args to keyword")
637 let m = args[0]651 let m = args[0]
638- if m.isNil or m.kind != kMap: return NilV652+ if m.isNil or m.kind notin {kMap, kSet}: return NilV
639 mapGet(m.m, f, (if args.len > 1: args[1] else: NilV))653 mapGet(m.m, f, (if args.len > 1: args[1] else: NilV))
640 of kMap:654 of kMap:
641 if args.len == 0: err("Wrong number of args to map")655 if args.len == 0: err("Wrong number of args to map")
642 mapGet(f.m, args[0], (if args.len > 1: args[1] else: NilV))656 mapGet(f.m, args[0], (if args.len > 1: args[1] else: NilV))
657+ of kSet:
658+ # (#{...} x) => the element, or nil
659+ if args.len == 0: err("Wrong number of args to set")
660+ if mapContains(f.m, args[0]): args[0]
661+ else: (if args.len > 1: args[1] else: NilV)
643 of kVector:662 of kVector:
644 if args.len != 1 or args[0].kind != kInt: err("Vector lookup needs one int")663 if args.len != 1 or args[0].kind != kInt: err("Vector lookup needs one int")
645 vecNth(f.vec, int(args[0].i))664 vecNth(f.vec, int(args[0].i))
modified src/runtime_lib.nim +1 -0
@@ -24,6 +24,7 @@ proc clonim_truthy(v: Value): bool = truthy(v)
2424
2525 proc clonim_mk_int(x: int64): Value = mkInt(x)
2626 proc clonim_mk_float(x: float64): Value = mkFloat(x)
27+proc clonim_mk_char(code: int64): Value = mkChar(code)
2728 proc clonim_mk_str(x: string): Value = mkStr(x)
2829 proc clonim_mk_keyword(x: string): Value = mkKeyword(x)
2930 proc clonim_mk_symbol(x: string): Value = mkSymbol(x)
@@ -24,6 +24,7 @@ proc clonim_truthy(v: Value): bool = truthy(v)
24 24
25 proc clonim_mk_int(x: int64): Value = mkInt(x)25 proc clonim_mk_int(x: int64): Value = mkInt(x)
26 proc clonim_mk_float(x: float64): Value = mkFloat(x)26 proc clonim_mk_float(x: float64): Value = mkFloat(x)
27+proc clonim_mk_char(code: int64): Value = mkChar(code)
27 proc clonim_mk_str(x: string): Value = mkStr(x)28 proc clonim_mk_str(x: string): Value = mkStr(x)
28 proc clonim_mk_keyword(x: string): Value = mkKeyword(x)29 proc clonim_mk_keyword(x: string): Value = mkKeyword(x)
29 proc clonim_mk_symbol(x: string): Value = mkSymbol(x)30 proc clonim_mk_symbol(x: string): Value = mkSymbol(x)
modified src/runtime_types.nim +3 -3
@@ -4,7 +4,7 @@
44
55 type
66 Kind* = enum
7- kNil, kBool, kInt, kFloat, kStr, kKeyword, kSymbol,
7+ kNil, kBool, kInt, kFloat, kChar, kStr, kKeyword, kSymbol,
88 kList, kVector, kMap, kSet, kCons, kChunk, kLazy, kFn
99
1010 VNode* = ref object
@@ -53,7 +53,7 @@ type
5353 Obj* = ref ValueObj
5454 ValueObj* = object
5555 case kind*: Kind
56- of kNil, kBool, kInt, kFloat: discard
56+ of kNil, kBool, kInt, kFloat, kChar: discard
5757 of kStr, kKeyword, kSymbol: s*: string
5858 of kList: xs*: seq[Value]
5959 of kVector: vec*: PVec
@@ -133,7 +133,7 @@ proc `=destroy`*(x: var ValueObj) =
133133 `=destroy`(x.thunk)
134134 if not x.cached.isNil: pendingFree.add x.cached
135135 `=destroy`(x.cached)
136- of kNil, kBool, kInt, kFloat: discard
136+ of kNil, kBool, kInt, kFloat, kChar: discard
137137 if draining: return
138138 draining = true
139139 while pendingFree.len > 0:
@@ -4,7 +4,7 @@
4 4
5 type5 type
6 Kind* = enum6 Kind* = enum
7- kNil, kBool, kInt, kFloat, kStr, kKeyword, kSymbol,7+ kNil, kBool, kInt, kFloat, kChar, kStr, kKeyword, kSymbol,
8 kList, kVector, kMap, kSet, kCons, kChunk, kLazy, kFn8 kList, kVector, kMap, kSet, kCons, kChunk, kLazy, kFn
9 9
10 VNode* = ref object10 VNode* = ref object
@@ -53,7 +53,7 @@ type
53 Obj* = ref ValueObj53 Obj* = ref ValueObj
54 ValueObj* = object54 ValueObj* = object
55 case kind*: Kind55 case kind*: Kind
56- of kNil, kBool, kInt, kFloat: discard56+ of kNil, kBool, kInt, kFloat, kChar: discard
57 of kStr, kKeyword, kSymbol: s*: string57 of kStr, kKeyword, kSymbol: s*: string
58 of kList: xs*: seq[Value]58 of kList: xs*: seq[Value]
59 of kVector: vec*: PVec59 of kVector: vec*: PVec
@@ -133,7 +133,7 @@ proc `=destroy`*(x: var ValueObj) =
133 `=destroy`(x.thunk)133 `=destroy`(x.thunk)
134 if not x.cached.isNil: pendingFree.add x.cached134 if not x.cached.isNil: pendingFree.add x.cached
135 `=destroy`(x.cached)135 `=destroy`(x.cached)
136- of kNil, kBool, kInt, kFloat: discard136+ of kNil, kBool, kInt, kFloat, kChar: discard
137 if draining: return137 if draining: return
138 draining = true138 draining = true
139 while pendingFree.len > 0:139 while pendingFree.len > 0:
added tests/hostfns.expected +16 -0
new file mode 100644
@@ -0,0 +1,16 @@
1+a 65 B xy true
2+b true false +
3+\a \newline \tab
4+true false
5+3 -1 -56
6+255 -12
7+2.5 2.0 3.0
8+0a|:kw| 42|ab |3.14|FF|%
9+[1 :a 2 :b]
10+((1 2) (3 4) (5))
11+((1 3) (2 4) (5))
12+#{1 2} {:a 1}
13+true true true true 2
14+9 10
15+false true
16+boom
new file mode 100644
@@ -0,0 +1,16 @@
1+a 65 B xy true
2+b true false +
3+\a \newline \tab
4+true false
5+3 -1 -56
6+255 -12
7+2.5 2.0 3.0
8+0a|:kw| 42|ab |3.14|FF|%
9+[1 :a 2 :b]
10+((1 2) (3 4) (5))
11+((1 3) (2 4) (5))
12+#{1 2} {:a 1}
13+true true true true 2
14+9 10
15+false true
16+boom