nandi/clonimpublic Fork
⑂ 59d1739
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

fix: destructuring, #() and int-typed loop variables

Compiling a real program turned up four defects, each with a test:

- fn parameters could not destructure; they now bind a plain parameter and
  hand the pattern to the same code let uses. loop variables likewise.
- :as, :or and nested patterns were unsupported in every binding form.
- #(f x) read as (fn [a] f x) — a body of three forms whose value was the
  last one — so every anonymous function silently returned its argument.
  It is now one call, and %1..%9 work.
- a loop variable or parameter rebound by an inner form could still be held
  as a raw int64, because the int analysis probed recur values in the outer
  scope; such names are now demoted.

A rest binding is nil rather than () once exhausted, as on the JVM, and the
string builtins reject a non-string instead of reading through a nil object.

Also: a program that defines -main gets it called with the command-line
arguments, which are also *command-line-args*; builds enable -d:ssl so the
HTTP primitive can reach https.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandithebull committed 2026-09-21T01:23:45-07:00 Browse files
59d1739 parent: 2377830
added examples/binding_forms.clj +30 -0
new file mode 100644
@@ -0,0 +1,30 @@
1+(ns binding-forms)
2+
3+;; destructuring in fn parameters, including multiple arities
4+(println ((fn [[a b] c] [a b c]) [1 2] 3)) ; [1 2 3]
5+(println (map (fn [[k v]] (str k v)) [[:a 1] [:b 2]])) ; (:a1 :b2)
6+(defn g ([[a b]] (+ a b)) ([x y] (* x y)))
7+(println (g [3 4]) (g 3 4)) ; 7 12
8+(println ((fn [{:keys [x y]}] [x y]) {:x 1 :y 2})) ; [1 2]
9+
10+;; :as, :or, nesting, and a rest that is nil once exhausted
11+(let [[a b & more :as all] [1 2 3 4]] (println a b more all))
12+(let [[a & r] [1]] (println r (nil? r))) ; nil true
13+(let [{:keys [x y] :or {y 9} :as m} {:x 1}] (println x y m))
14+(let [[[a b] [c]] [[1 2] [3]]] (println a b c)) ; 1 2 3
15+(println ((fn [& xs] xs)) ((fn [& xs] xs) 1)) ; nil (1)
16+
17+;; loop variables destructure too, and recur assigns the whole form
18+(println (loop [[t & more] [1 2 3] acc []]
19+ (if (nil? t) acc (recur more (conj acc t))))) ; [1 2 3]
20+
21+;; #() is one call, and takes numbered arguments
22+(println (map #(format "%02x" %) [227 176])) ; (e3 b0)
23+(println (map #(* 2 %) [1 2 3])) ; (2 4 6)
24+(println (map #(+ %1 %2) [1 2] [10 20])) ; (11 22)
25+(println (#(vector % %) 7)) ; [7 7]
26+
27+;; a rebound loop variable is still a Value, not a raw int
28+(println (loop [i 0 acc 0]
29+ (if (= i 3) acc
30+ (let [[acc] [(+ acc i)]] (recur (inc i) acc))))) ; 3
new file mode 100644
@@ -0,0 +1,30 @@
1+(ns binding-forms)
2+
3+;; destructuring in fn parameters, including multiple arities
4+(println ((fn [[a b] c] [a b c]) [1 2] 3)) ; [1 2 3]
5+(println (map (fn [[k v]] (str k v)) [[:a 1] [:b 2]])) ; (:a1 :b2)
6+(defn g ([[a b]] (+ a b)) ([x y] (* x y)))
7+(println (g [3 4]) (g 3 4)) ; 7 12
8+(println ((fn [{:keys [x y]}] [x y]) {:x 1 :y 2})) ; [1 2]
9+
10+;; :as, :or, nesting, and a rest that is nil once exhausted
11+(let [[a b & more :as all] [1 2 3 4]] (println a b more all))
12+(let [[a & r] [1]] (println r (nil? r))) ; nil true
13+(let [{:keys [x y] :or {y 9} :as m} {:x 1}] (println x y m))
14+(let [[[a b] [c]] [[1 2] [3]]] (println a b c)) ; 1 2 3
15+(println ((fn [& xs] xs)) ((fn [& xs] xs) 1)) ; nil (1)
16+
17+;; loop variables destructure too, and recur assigns the whole form
18+(println (loop [[t & more] [1 2 3] acc []]
19+ (if (nil? t) acc (recur more (conj acc t))))) ; [1 2 3]
20+
21+;; #() is one call, and takes numbered arguments
22+(println (map #(format "%02x" %) [227 176])) ; (e3 b0)
23+(println (map #(* 2 %) [1 2 3])) ; (2 4 6)
24+(println (map #(+ %1 %2) [1 2] [10 20])) ; (11 22)
25+(println (#(vector % %) 7)) ; [7 7]
26+
27+;; a rebound loop variable is still a Value, not a raw int
28+(println (loop [i 0 acc 0]
29+ (if (= i 3) acc
30+ (let [[acc] [(+ acc i)]] (recur (inc i) acc))))) ; 3
added examples/cli_args.clj +7 -0
new file mode 100644
@@ -0,0 +1,7 @@
1+(ns cli-args)
2+
3+;; A program with -main gets it called with the command line, which is also
4+;; *command-line-args*.
5+(defn -main [& argv]
6+ (println "args:" (count argv) (vec argv))
7+ (println "same:" (= (seq argv) (seq *command-line-args*))))
new file mode 100644
@@ -0,0 +1,7 @@
1+(ns cli-args)
2+
3+;; A program with -main gets it called with the command line, which is also
4+;; *command-line-args*.
5+(defn -main [& argv]
6+ (println "args:" (count argv) (vec argv))
7+ (println "same:" (= (seq argv) (seq *command-line-args*))))
modified src/app_runtime.nim +2 -0
@@ -18,10 +18,12 @@ proc setVar*(name: string, v: Value): Value {.discardable, importc: "clonim_set_
1818 proc cellGet*(c: VarCell): Value {.importc: "clonim_cell_get".}
1919 proc cellIs*(c: VarCell, v: Value): bool {.importc: "clonim_cell_is".}
2020 proc getVar*(name: string): Value {.importc: "clonim_get_var".}
21+proc hasVar*(name: string): bool {.importc: "clonim_has_var".}
2122 proc call*(f: Value, args: openArray[Value]): Value {.importc: "clonim_call".}
2223 proc argAt*(args: openArray[Value], i: int): Value {.importc: "clonim_arg_at".}
2324 proc restArgs*(args: openArray[Value], i: int): Value {.importc: "clonim_rest_args".}
2425 proc seqDrop*(v: Value, n: int): Value {.importc: "clonim_seq_drop".}
26+proc seqDropOrNil*(v: Value, n: int): Value {.importc: "clonim_seq_drop_or_nil".}
2527 proc toSeq*(v: Value): seq[Value] {.importc: "clonim_to_seq".}
2628 proc truthy*(v: Value): bool {.importc: "clonim_truthy".}
2729
@@ -18,10 +18,12 @@ proc setVar*(name: string, v: Value): Value {.discardable, importc: "clonim_set_
18 proc cellGet*(c: VarCell): Value {.importc: "clonim_cell_get".}18 proc cellGet*(c: VarCell): Value {.importc: "clonim_cell_get".}
19 proc cellIs*(c: VarCell, v: Value): bool {.importc: "clonim_cell_is".}19 proc cellIs*(c: VarCell, v: Value): bool {.importc: "clonim_cell_is".}
20 proc getVar*(name: string): Value {.importc: "clonim_get_var".}20 proc getVar*(name: string): Value {.importc: "clonim_get_var".}
21+proc hasVar*(name: string): bool {.importc: "clonim_has_var".}
21 proc call*(f: Value, args: openArray[Value]): Value {.importc: "clonim_call".}22 proc call*(f: Value, args: openArray[Value]): Value {.importc: "clonim_call".}
22 proc argAt*(args: openArray[Value], i: int): Value {.importc: "clonim_arg_at".}23 proc argAt*(args: openArray[Value], i: int): Value {.importc: "clonim_arg_at".}
23 proc restArgs*(args: openArray[Value], i: int): Value {.importc: "clonim_rest_args".}24 proc restArgs*(args: openArray[Value], i: int): Value {.importc: "clonim_rest_args".}
24 proc seqDrop*(v: Value, n: int): Value {.importc: "clonim_seq_drop".}25 proc seqDrop*(v: Value, n: int): Value {.importc: "clonim_seq_drop".}
26+proc seqDropOrNil*(v: Value, n: int): Value {.importc: "clonim_seq_drop_or_nil".}
25 proc toSeq*(v: Value): seq[Value] {.importc: "clonim_to_seq".}27 proc toSeq*(v: Value): seq[Value] {.importc: "clonim_to_seq".}
26 proc truthy*(v: Value): bool {.importc: "clonim_truthy".}28 proc truthy*(v: Value): bool {.importc: "clonim_truthy".}
27 29
modified src/clonim.nim +3 -0
@@ -87,6 +87,8 @@ proc runtimeLib(release: bool): string =
8787 "--nimcache:" & installed.parentDir /
8888 (if release: "nimcache-release" else: "nimcache-debug"),
8989 "-o:" & installed]
90+ # The HTTP primitive speaks TLS; without this it can only reach http://.
91+ args.add "-d:ssl"
9092 if release: args.add "-d:release"
9193 args.add source
9294 let (output, code) = execCmdEx(args.mapIt(quoteShell(it)).join(" "))
@@ -203,6 +205,7 @@ proc main() =
203205 nimCmd.add "--passL:-Wl,--allow-multiple-definition"
204206 nimCmd.add "--passL:" & rtLib
205207 nimCmd.add "--passL:-lm"
208+ nimCmd.add "-d:ssl"
206209 if release:
207210 # `-d:release` disables Nim runtime checks but keeps DWARF and symbol
208211 # tables by default. Native deliverables should not carry that metadata.
@@ -87,6 +87,8 @@ proc runtimeLib(release: bool): string =
87 "--nimcache:" & installed.parentDir /87 "--nimcache:" & installed.parentDir /
88 (if release: "nimcache-release" else: "nimcache-debug"),88 (if release: "nimcache-release" else: "nimcache-debug"),
89 "-o:" & installed]89 "-o:" & installed]
90+ # The HTTP primitive speaks TLS; without this it can only reach http://.
91+ args.add "-d:ssl"
90 if release: args.add "-d:release"92 if release: args.add "-d:release"
91 args.add source93 args.add source
92 let (output, code) = execCmdEx(args.mapIt(quoteShell(it)).join(" "))94 let (output, code) = execCmdEx(args.mapIt(quoteShell(it)).join(" "))
@@ -203,6 +205,7 @@ proc main() =
203 nimCmd.add "--passL:-Wl,--allow-multiple-definition"205 nimCmd.add "--passL:-Wl,--allow-multiple-definition"
204 nimCmd.add "--passL:" & rtLib206 nimCmd.add "--passL:" & rtLib
205 nimCmd.add "--passL:-lm"207 nimCmd.add "--passL:-lm"
208+ nimCmd.add "-d:ssl"
206 if release:209 if release:
207 # `-d:release` disables Nim runtime checks but keeps DWARF and symbol210 # `-d:release` disables Nim runtime checks but keeps DWARF and symbol
208 # tables by default. Native deliverables should not carry that metadata.211 # tables by default. Native deliverables should not carry that metadata.
modified src/compiler.nim +175 -48
@@ -255,6 +255,7 @@ type
255255 params: seq[string]
256256 restParam: string
257257 body: seq[Value]
258+ destructures: seq[(Value, string)]
258259
259260 proc parseParams(v: Value): FnClause =
260261 if v.isNil or v.kind != kVector: err("Parameter list must be a vector, got: " & prStr(v))
@@ -264,9 +265,20 @@ proc parseParams(v: Value): FnClause =
264265 let p = v.items[i]
265266 if isSym(p, "&"):
266267 if i + 1 >= v.items.len: err("Missing symbol after &")
267- result.restParam = symName(v.items[i + 1])
268+ let rest = v.items[i + 1]
269+ if rest.kind == kSymbol: result.restParam = symName(rest)
270+ else:
271+ result.restParam = "destr__rest"
272+ result.destructures.add (rest, result.restParam)
268273 break
269- result.params.add symName(p)
274+ if p.kind == kSymbol:
275+ result.params.add symName(p)
276+ else:
277+ # A destructuring parameter binds a plain one, then lets the existing
278+ # let machinery take it apart at the top of the body.
279+ let nm = "destr__" & $i
280+ result.params.add nm
281+ result.destructures.add (p, nm)
270282 inc i
271283
272284 proc genClauseBody(cl: FnClause, name, selfIdent: string, env: Env, c: Ctx,
@@ -320,6 +332,51 @@ proc collectRecurs(forms: seq[Value], into: var seq[seq[Value]]) =
320332 if coreName(head.s) in ["loop", "loop*", "fn", "fn*", "defn", "defn-"]: continue
321333 collectRecurs(f.items, into)
322334
335+proc patternNames(v: Value, into: var HashSet[string]) =
336+ ## Names a binding pattern introduces. Lenient: the resolver has already
337+ ## rejected malformed patterns, and a name too many only costs an
338+ ## optimisation.
339+ if v.isNil: return
340+ case v.kind
341+ of kSymbol: (if v.s != "&": into.incl v.s)
342+ of kVector:
343+ for x in v.items: patternNames(x, into)
344+ of kMap:
345+ for (k, value) in v.pairs:
346+ if k.kind == kKeyword:
347+ case k.s
348+ of "keys", "syms", "strs":
349+ for x in value.items: (if x.kind == kSymbol: into.incl x.s)
350+ of "as": patternNames(value, into)
351+ else: discard
352+ else: patternNames(k, into)
353+ else: discard
354+
355+proc collectBoundSymbols(v: Value, into: var HashSet[string]) =
356+ ## Every name a binding form inside these forms introduces. A loop variable
357+ ## or parameter that some inner form rebinds cannot be held as a raw int64:
358+ ## the int analysis probes recur values in the outer scope, where the name
359+ ## still looks integral even though the recur sees the inner Value binding.
360+ if v.isNil or v.kind != kList or v.items.len == 0: return
361+ let head = v.items[0]
362+ if head.kind == kSymbol:
363+ let h = coreName(head.s)
364+ if h in ["let", "let*", "loop", "loop*", "when-let", "if-let", "doseq",
365+ "dotimes", "binding"] and v.items.len > 1 and v.items[1].kind == kVector:
366+ var i = 0
367+ while i < v.items[1].items.len:
368+ patternNames(v.items[1].items[i], into)
369+ i += 2
370+ elif h in ["fn", "fn*", "defn", "defn-"]:
371+ for x in v.items:
372+ if x.kind == kVector: patternNames(x, into)
373+ elif x.kind == kList and x.items.len > 0 and x.items[0].kind == kVector:
374+ patternNames(x.items[0], into)
375+ for x in v.items: collectBoundSymbols(x, into)
376+
377+proc shadowedNames(forms: seq[Value]): HashSet[string] =
378+ for f in forms: collectBoundSymbols(f, result)
379+
323380 proc usesIntPrim(c: Ctx, forms: seq[Value]): bool =
324381 ## Whether an int-specialised twin could differ from the generic proc at all.
325382 ## Emitting one for a fn that never does arithmetic just doubles the work Nim
@@ -377,6 +434,9 @@ proc genFn(name: string, clauses: seq[FnClause], selfIdent: string, env: Env,
377434 probe.ints.incl p
378435 probe.intFns[key] = iprc
379436 var recurSafe = true
437+ let shadowed = shadowedNames(cl.body)
438+ for p in cl.params:
439+ if p in shadowed: recurSafe = false
380440 var myRecurs: seq[seq[Value]] = @[]
381441 collectRecurs(cl.body, myRecurs)
382442 for r in myRecurs:
@@ -442,6 +502,15 @@ proc genFn(name: string, clauses: seq[FnClause], selfIdent: string, env: Env,
442502 c.pop
443503 c.line(")")
444504
505+proc withDestructuring(cl: FnClause): FnClause =
506+ result = cl
507+ if cl.destructures.len == 0: return
508+ var bs: seq[Value] = @[]
509+ for (pattern, nm) in cl.destructures:
510+ bs.add pattern
511+ bs.add mkSymbol(nm)
512+ result.body = @[mkList(@[mkSymbol("let"), mkVector(bs)] & cl.body)]
513+
445514 proc genFnForm(args: seq[Value], env: Env, c: Ctx, dst: string, defName: string,
446515 directEnv: Env = nil, cell = "") =
447516 ## (fn name? [params] body...) or (fn name? ([params] body...) ...)
@@ -454,14 +523,14 @@ proc genFnForm(args: seq[Value], env: Env, c: Ctx, dst: string, defName: string,
454523 if i < args.len and args[i].kind == kVector:
455524 var cl = parseParams(args[i])
456525 cl.body = args[i + 1 .. ^1]
457- clauses.add cl
526+ clauses.add withDestructuring(cl)
458527 else:
459528 while i < args.len:
460529 let cf = args[i]
461530 if cf.kind != kList or cf.items.len == 0: err("Bad fn arity form: " & prStr(cf))
462531 var cl = parseParams(cf.items[0])
463532 cl.body = cf.items[1 .. ^1]
464- clauses.add cl
533+ clauses.add withDestructuring(cl)
465534 inc i
466535 if clauses.len == 0: err("fn requires at least one arity")
467536 if name.len > 0:
@@ -473,6 +542,70 @@ proc genFnForm(args: seq[Value], env: Env, c: Ctx, dst: string, defName: string,
473542 else:
474543 genFn("fn", clauses, "", env, c, dst)
475544
545+proc bindPattern(target: Value, v: string, lenv: Env, c: Ctx) =
546+ ## Bind one binding form -- a symbol or a destructuring pattern -- to the
547+ ## value already computed into `v`.
548+ if target.kind == kSymbol:
549+ let id = c.gensym("l" & mangle(target.s))
550+ c.line("var " & id & ": Value = " & v)
551+ lenv.locals[target.s] = id
552+ elif target.kind == kVector:
553+ # sequential destructuring: [a b & rest :as whole]
554+ var idx = 0
555+ var j = 0
556+ while j < target.items.len:
557+ let p = target.items[j]
558+ if p.kind == kKeyword and p.s == "as":
559+ if j + 1 >= target.items.len: err("Missing symbol after :as")
560+ bindPattern(target.items[j + 1], v, lenv, c)
561+ j += 2
562+ continue
563+ if isSym(p, "&"):
564+ if j + 1 >= target.items.len: err("Missing binding form after &")
565+ let id = c.gensym("lrest")
566+ c.line("var " & id & ": Value = seqDropOrNil(" & v & ", " & $idx & ")")
567+ bindPattern(target.items[j + 1], id, lenv, c)
568+ j += 2
569+ continue
570+ let id = c.gensym("lnth")
571+ c.line("var " & id & ": Value = call(getVar(\"clojure.core/nth\"), [" & v & ", mkInt(" &
572+ $idx & "), NilV])")
573+ bindPattern(p, id, lenv, c)
574+ inc idx; inc j
575+ elif target.kind == kMap:
576+ # associative destructuring: {a :a, :keys [b c], :or {b 1}, :as m}
577+ var defaults = NilV
578+ for (k, valForm) in target.pairs:
579+ if k.kind == kKeyword and k.s == "or": defaults = valForm
580+ proc bindKey(nm: string, key: string) =
581+ let id = c.gensym("l" & mangle(nm))
582+ var default = "NilV"
583+ if defaults.kind == kMap:
584+ for (dk, dv) in defaults.pairs:
585+ if dk.kind == kSymbol and dk.s == nm: default = genExpr(dv, lenv, c)
586+ c.line("var " & id & ": Value = call(getVar(\"clojure.core/get\"), [" & v &
587+ ", mkKeyword(" & nimStr(key) & "), " & default & "])")
588+ lenv.locals[nm] = id
589+ for (k, valForm) in target.pairs:
590+ if k.kind == kKeyword:
591+ case k.s
592+ of "or": discard
593+ of "as": bindPattern(valForm, v, lenv, c)
594+ of "keys":
595+ for ks in valForm.items: bindKey(symName(ks), symName(ks))
596+ of "strs":
597+ for ks in valForm.items: bindKey(symName(ks), symName(ks))
598+ else: err("Unsupported destructuring directive: :" & k.s)
599+ elif valForm.kind == kKeyword and k.kind == kSymbol:
600+ bindKey(symName(k), valForm.s)
601+ else:
602+ let id = c.gensym("l" & mangle(symName(k)))
603+ let kv = genExpr(valForm, lenv, c)
604+ c.line("var " & id & ": Value = call(getVar(\"clojure.core/get\"), [" & v & ", " & kv & "])")
605+ lenv.locals[symName(k)] = id
606+ else:
607+ err("Unsupported binding form: " & prStr(target))
608+
476609 proc genLet(bindings: Value, body: seq[Value], dst: string, env: Env, c: Ctx) =
477610 if bindings.isNil or bindings.kind != kVector:
478611 err("let requires a vector for its bindings")
@@ -481,57 +614,35 @@ proc genLet(bindings: Value, body: seq[Value], dst: string, env: Env, c: Ctx) =
481614 let lenv = newEnv(env)
482615 var i = 0
483616 while i < bindings.items.len:
484- let target = bindings.items[i]
485- let initForm = bindings.items[i + 1]
486- let v = genExpr(initForm, lenv, c)
487- if target.kind == kSymbol:
488- let id = c.gensym("l" & mangle(target.s))
489- c.line("var " & id & ": Value = " & v)
490- lenv.locals[target.s] = id
491- elif target.kind == kVector:
492- # sequential destructuring: [a b & rest]
493- var idx = 0
494- var j = 0
495- while j < target.items.len:
496- let p = target.items[j]
497- if isSym(p, "&"):
498- let restSym = symName(target.items[j + 1])
499- let id = c.gensym("l" & mangle(restSym))
500- c.line("var " & id & ": Value = seqDrop(" & v & ", " & $idx & ")")
501- lenv.locals[restSym] = id
502- break
503- let id = c.gensym("l" & mangle(symName(p)))
504- c.line("var " & id & ": Value = call(getVar(\"clojure.core/nth\"), [" & v & ", mkInt(" &
505- $idx & "), NilV])")
506- lenv.locals[symName(p)] = id
507- inc idx; inc j
508- elif target.kind == kMap:
509- # associative destructuring: {a :a, :keys [b c]}
510- for (k, valForm) in target.pairs:
511- if k.kind == kKeyword and k.s == "keys":
512- for ks in valForm.items:
513- let nm = symName(ks)
514- let id = c.gensym("l" & mangle(nm))
515- c.line("var " & id & ": Value = call(getVar(\"clojure.core/get\"), [" & v &
516- ", mkKeyword(" & nimStr(nm) & ")])")
517- lenv.locals[nm] = id
518- else:
519- let nm = symName(k)
520- let id = c.gensym("l" & mangle(nm))
521- let kv = genExpr(valForm, lenv, c)
522- c.line("var " & id & ": Value = call(getVar(\"clojure.core/get\"), [" & v & ", " & kv & "])")
523- lenv.locals[nm] = id
524- else:
525- err("Unsupported binding form: " & prStr(target))
617+ let v = genExpr(bindings.items[i + 1], lenv, c)
618+ bindPattern(bindings.items[i], v, lenv, c)
526619 i += 2
527620 genBody(body, dst, lenv, c)
528621
529622 proc genLoop(bindings: Value, body: seq[Value], dst: string, env: Env, c: Ctx) =
530623 if bindings.isNil or bindings.kind != kVector or bindings.items.len mod 2 != 0:
531624 err("loop requires an even-sized binding vector")
625+ # A destructuring loop variable becomes a plain one that recur can assign,
626+ # taken apart again by a let at the top of the body.
627+ var bindings = bindings
628+ var body = body
629+ var patterns: seq[Value] = @[]
630+ var i = 0
631+ while i < bindings.items.len:
632+ if bindings.items[i].kind != kSymbol:
633+ let nm = mkSymbol("loop__" & $i)
634+ patterns.add bindings.items[i]
635+ patterns.add nm
636+ var xs = bindings.items
637+ xs[i] = nm
638+ bindings = mkVector(xs)
639+ i += 2
640+ if patterns.len > 0:
641+ body = @[mkList(@[mkSymbol("let"), mkVector(patterns)] & body)]
642+
532643 var names: seq[string] = @[]
533644 var inits: seq[Value] = @[]
534- var i = 0
645+ i = 0
535646 while i < bindings.items.len:
536647 names.add symName(bindings.items[i])
537648 inits.add bindings.items[i + 1]
@@ -545,8 +656,9 @@ proc genLoop(bindings: Value, body: seq[Value], dst: string, env: Env, c: Ctx) =
545656 collectRecurs(body, recurs)
546657 for r in recurs:
547658 if r.len != names.len: recurs = @[]; break # arity error, reported later
659+ let shadowed = shadowedNames(body)
548660 var isInt: seq[bool] = @[]
549- for n in names: isInt.add true
661+ for n in names: isInt.add n notin shadowed
550662 var probeIdents: seq[string] = @[]
551663 for n in names: probeIdents.add "probe"
552664 while true:
@@ -1160,6 +1272,7 @@ proc genInto(f: Value, dst: string, env: Env, c: Ctx) =
11601272 # ------------------------------------------------------------- entry point
11611273 const preamble = """
11621274 ## Generated by clonim. Do not edit.
1275+import std/os
11631276 import app_runtime
11641277
11651278 proc cljMain() =
@@ -1191,6 +1304,20 @@ proc compileForms*(forms: seq[Value]): string =
11911304 c.line("var " & t & ": Value = NilV")
11921305 genInto(f, t, env, c)
11931306 c.line("discard " & t)
1307+ # A program that defines -main gets it called with the command-line
1308+ # arguments once the top-level forms have run, as on the JVM.
1309+ var mainVar = ""
1310+ for name, _ in c.defCounts:
1311+ if name == "-main" or name.endsWith("/-main"): mainVar = name
1312+ c.line("var clonimArgv: seq[Value] = @[]")
1313+ c.line("for a in commandLineParams(): clonimArgv.add mkStr(a)")
1314+ c.line("setVar(\"clojure.core/*command-line-args*\", " &
1315+ "(if clonimArgv.len == 0: NilV else: mkList(clonimArgv)))")
1316+ if mainVar.len > 0:
1317+ c.line("if hasVar(" & nimStr(mainVar) & "):")
1318+ c.push
1319+ c.line("discard call(getVar(" & nimStr(mainVar) & "), clonimArgv)")
1320+ c.pop
11941321 var src = preamble & " initClonimRuntime()\n registerCore()\n registerNamespaceCore()\n" & c.prelude.join("\n") & "\n" &
11951322 c.body.join("\n") & "\n\n"
11961323 src &= """
@@ -255,6 +255,7 @@ type
255 params: seq[string]255 params: seq[string]
256 restParam: string256 restParam: string
257 body: seq[Value]257 body: seq[Value]
258+ destructures: seq[(Value, string)]
258 259
259 proc parseParams(v: Value): FnClause =260 proc parseParams(v: Value): FnClause =
260 if v.isNil or v.kind != kVector: err("Parameter list must be a vector, got: " & prStr(v))261 if v.isNil or v.kind != kVector: err("Parameter list must be a vector, got: " & prStr(v))
@@ -264,9 +265,20 @@ proc parseParams(v: Value): FnClause =
264 let p = v.items[i]265 let p = v.items[i]
265 if isSym(p, "&"):266 if isSym(p, "&"):
266 if i + 1 >= v.items.len: err("Missing symbol after &")267 if i + 1 >= v.items.len: err("Missing symbol after &")
267- result.restParam = symName(v.items[i + 1])268+ let rest = v.items[i + 1]
269+ if rest.kind == kSymbol: result.restParam = symName(rest)
270+ else:
271+ result.restParam = "destr__rest"
272+ result.destructures.add (rest, result.restParam)
268 break273 break
269- result.params.add symName(p)274+ if p.kind == kSymbol:
275+ result.params.add symName(p)
276+ else:
277+ # A destructuring parameter binds a plain one, then lets the existing
278+ # let machinery take it apart at the top of the body.
279+ let nm = "destr__" & $i
280+ result.params.add nm
281+ result.destructures.add (p, nm)
270 inc i282 inc i
271 283
272 proc genClauseBody(cl: FnClause, name, selfIdent: string, env: Env, c: Ctx,284 proc genClauseBody(cl: FnClause, name, selfIdent: string, env: Env, c: Ctx,
@@ -320,6 +332,51 @@ proc collectRecurs(forms: seq[Value], into: var seq[seq[Value]]) =
320 if coreName(head.s) in ["loop", "loop*", "fn", "fn*", "defn", "defn-"]: continue332 if coreName(head.s) in ["loop", "loop*", "fn", "fn*", "defn", "defn-"]: continue
321 collectRecurs(f.items, into)333 collectRecurs(f.items, into)
322 334
335+proc patternNames(v: Value, into: var HashSet[string]) =
336+ ## Names a binding pattern introduces. Lenient: the resolver has already
337+ ## rejected malformed patterns, and a name too many only costs an
338+ ## optimisation.
339+ if v.isNil: return
340+ case v.kind
341+ of kSymbol: (if v.s != "&": into.incl v.s)
342+ of kVector:
343+ for x in v.items: patternNames(x, into)
344+ of kMap:
345+ for (k, value) in v.pairs:
346+ if k.kind == kKeyword:
347+ case k.s
348+ of "keys", "syms", "strs":
349+ for x in value.items: (if x.kind == kSymbol: into.incl x.s)
350+ of "as": patternNames(value, into)
351+ else: discard
352+ else: patternNames(k, into)
353+ else: discard
354+
355+proc collectBoundSymbols(v: Value, into: var HashSet[string]) =
356+ ## Every name a binding form inside these forms introduces. A loop variable
357+ ## or parameter that some inner form rebinds cannot be held as a raw int64:
358+ ## the int analysis probes recur values in the outer scope, where the name
359+ ## still looks integral even though the recur sees the inner Value binding.
360+ if v.isNil or v.kind != kList or v.items.len == 0: return
361+ let head = v.items[0]
362+ if head.kind == kSymbol:
363+ let h = coreName(head.s)
364+ if h in ["let", "let*", "loop", "loop*", "when-let", "if-let", "doseq",
365+ "dotimes", "binding"] and v.items.len > 1 and v.items[1].kind == kVector:
366+ var i = 0
367+ while i < v.items[1].items.len:
368+ patternNames(v.items[1].items[i], into)
369+ i += 2
370+ elif h in ["fn", "fn*", "defn", "defn-"]:
371+ for x in v.items:
372+ if x.kind == kVector: patternNames(x, into)
373+ elif x.kind == kList and x.items.len > 0 and x.items[0].kind == kVector:
374+ patternNames(x.items[0], into)
375+ for x in v.items: collectBoundSymbols(x, into)
376+
377+proc shadowedNames(forms: seq[Value]): HashSet[string] =
378+ for f in forms: collectBoundSymbols(f, result)
379+
323 proc usesIntPrim(c: Ctx, forms: seq[Value]): bool =380 proc usesIntPrim(c: Ctx, forms: seq[Value]): bool =
324 ## Whether an int-specialised twin could differ from the generic proc at all.381 ## Whether an int-specialised twin could differ from the generic proc at all.
325 ## Emitting one for a fn that never does arithmetic just doubles the work Nim382 ## Emitting one for a fn that never does arithmetic just doubles the work Nim
@@ -377,6 +434,9 @@ proc genFn(name: string, clauses: seq[FnClause], selfIdent: string, env: Env,
377 probe.ints.incl p434 probe.ints.incl p
378 probe.intFns[key] = iprc435 probe.intFns[key] = iprc
379 var recurSafe = true436 var recurSafe = true
437+ let shadowed = shadowedNames(cl.body)
438+ for p in cl.params:
439+ if p in shadowed: recurSafe = false
380 var myRecurs: seq[seq[Value]] = @[]440 var myRecurs: seq[seq[Value]] = @[]
381 collectRecurs(cl.body, myRecurs)441 collectRecurs(cl.body, myRecurs)
382 for r in myRecurs:442 for r in myRecurs:
@@ -442,6 +502,15 @@ proc genFn(name: string, clauses: seq[FnClause], selfIdent: string, env: Env,
442 c.pop502 c.pop
443 c.line(")")503 c.line(")")
444 504
505+proc withDestructuring(cl: FnClause): FnClause =
506+ result = cl
507+ if cl.destructures.len == 0: return
508+ var bs: seq[Value] = @[]
509+ for (pattern, nm) in cl.destructures:
510+ bs.add pattern
511+ bs.add mkSymbol(nm)
512+ result.body = @[mkList(@[mkSymbol("let"), mkVector(bs)] & cl.body)]
513+
445 proc genFnForm(args: seq[Value], env: Env, c: Ctx, dst: string, defName: string,514 proc genFnForm(args: seq[Value], env: Env, c: Ctx, dst: string, defName: string,
446 directEnv: Env = nil, cell = "") =515 directEnv: Env = nil, cell = "") =
447 ## (fn name? [params] body...) or (fn name? ([params] body...) ...)516 ## (fn name? [params] body...) or (fn name? ([params] body...) ...)
@@ -454,14 +523,14 @@ proc genFnForm(args: seq[Value], env: Env, c: Ctx, dst: string, defName: string,
454 if i < args.len and args[i].kind == kVector:523 if i < args.len and args[i].kind == kVector:
455 var cl = parseParams(args[i])524 var cl = parseParams(args[i])
456 cl.body = args[i + 1 .. ^1]525 cl.body = args[i + 1 .. ^1]
457- clauses.add cl526+ clauses.add withDestructuring(cl)
458 else:527 else:
459 while i < args.len:528 while i < args.len:
460 let cf = args[i]529 let cf = args[i]
461 if cf.kind != kList or cf.items.len == 0: err("Bad fn arity form: " & prStr(cf))530 if cf.kind != kList or cf.items.len == 0: err("Bad fn arity form: " & prStr(cf))
462 var cl = parseParams(cf.items[0])531 var cl = parseParams(cf.items[0])
463 cl.body = cf.items[1 .. ^1]532 cl.body = cf.items[1 .. ^1]
464- clauses.add cl533+ clauses.add withDestructuring(cl)
465 inc i534 inc i
466 if clauses.len == 0: err("fn requires at least one arity")535 if clauses.len == 0: err("fn requires at least one arity")
467 if name.len > 0:536 if name.len > 0:
@@ -473,6 +542,70 @@ proc genFnForm(args: seq[Value], env: Env, c: Ctx, dst: string, defName: string,
473 else:542 else:
474 genFn("fn", clauses, "", env, c, dst)543 genFn("fn", clauses, "", env, c, dst)
475 544
545+proc bindPattern(target: Value, v: string, lenv: Env, c: Ctx) =
546+ ## Bind one binding form -- a symbol or a destructuring pattern -- to the
547+ ## value already computed into `v`.
548+ if target.kind == kSymbol:
549+ let id = c.gensym("l" & mangle(target.s))
550+ c.line("var " & id & ": Value = " & v)
551+ lenv.locals[target.s] = id
552+ elif target.kind == kVector:
553+ # sequential destructuring: [a b & rest :as whole]
554+ var idx = 0
555+ var j = 0
556+ while j < target.items.len:
557+ let p = target.items[j]
558+ if p.kind == kKeyword and p.s == "as":
559+ if j + 1 >= target.items.len: err("Missing symbol after :as")
560+ bindPattern(target.items[j + 1], v, lenv, c)
561+ j += 2
562+ continue
563+ if isSym(p, "&"):
564+ if j + 1 >= target.items.len: err("Missing binding form after &")
565+ let id = c.gensym("lrest")
566+ c.line("var " & id & ": Value = seqDropOrNil(" & v & ", " & $idx & ")")
567+ bindPattern(target.items[j + 1], id, lenv, c)
568+ j += 2
569+ continue
570+ let id = c.gensym("lnth")
571+ c.line("var " & id & ": Value = call(getVar(\"clojure.core/nth\"), [" & v & ", mkInt(" &
572+ $idx & "), NilV])")
573+ bindPattern(p, id, lenv, c)
574+ inc idx; inc j
575+ elif target.kind == kMap:
576+ # associative destructuring: {a :a, :keys [b c], :or {b 1}, :as m}
577+ var defaults = NilV
578+ for (k, valForm) in target.pairs:
579+ if k.kind == kKeyword and k.s == "or": defaults = valForm
580+ proc bindKey(nm: string, key: string) =
581+ let id = c.gensym("l" & mangle(nm))
582+ var default = "NilV"
583+ if defaults.kind == kMap:
584+ for (dk, dv) in defaults.pairs:
585+ if dk.kind == kSymbol and dk.s == nm: default = genExpr(dv, lenv, c)
586+ c.line("var " & id & ": Value = call(getVar(\"clojure.core/get\"), [" & v &
587+ ", mkKeyword(" & nimStr(key) & "), " & default & "])")
588+ lenv.locals[nm] = id
589+ for (k, valForm) in target.pairs:
590+ if k.kind == kKeyword:
591+ case k.s
592+ of "or": discard
593+ of "as": bindPattern(valForm, v, lenv, c)
594+ of "keys":
595+ for ks in valForm.items: bindKey(symName(ks), symName(ks))
596+ of "strs":
597+ for ks in valForm.items: bindKey(symName(ks), symName(ks))
598+ else: err("Unsupported destructuring directive: :" & k.s)
599+ elif valForm.kind == kKeyword and k.kind == kSymbol:
600+ bindKey(symName(k), valForm.s)
601+ else:
602+ let id = c.gensym("l" & mangle(symName(k)))
603+ let kv = genExpr(valForm, lenv, c)
604+ c.line("var " & id & ": Value = call(getVar(\"clojure.core/get\"), [" & v & ", " & kv & "])")
605+ lenv.locals[symName(k)] = id
606+ else:
607+ err("Unsupported binding form: " & prStr(target))
608+
476 proc genLet(bindings: Value, body: seq[Value], dst: string, env: Env, c: Ctx) =609 proc genLet(bindings: Value, body: seq[Value], dst: string, env: Env, c: Ctx) =
477 if bindings.isNil or bindings.kind != kVector:610 if bindings.isNil or bindings.kind != kVector:
478 err("let requires a vector for its bindings")611 err("let requires a vector for its bindings")
@@ -481,57 +614,35 @@ proc genLet(bindings: Value, body: seq[Value], dst: string, env: Env, c: Ctx) =
481 let lenv = newEnv(env)614 let lenv = newEnv(env)
482 var i = 0615 var i = 0
483 while i < bindings.items.len:616 while i < bindings.items.len:
484- let target = bindings.items[i]617+ let v = genExpr(bindings.items[i + 1], lenv, c)
485- let initForm = bindings.items[i + 1]618+ bindPattern(bindings.items[i], v, lenv, c)
486- let v = genExpr(initForm, lenv, c)
487- if target.kind == kSymbol:
488- let id = c.gensym("l" & mangle(target.s))
489- c.line("var " & id & ": Value = " & v)
490- lenv.locals[target.s] = id
491- elif target.kind == kVector:
492- # sequential destructuring: [a b & rest]
493- var idx = 0
494- var j = 0
495- while j < target.items.len:
496- let p = target.items[j]
497- if isSym(p, "&"):
498- let restSym = symName(target.items[j + 1])
499- let id = c.gensym("l" & mangle(restSym))
500- c.line("var " & id & ": Value = seqDrop(" & v & ", " & $idx & ")")
501- lenv.locals[restSym] = id
502- break
503- let id = c.gensym("l" & mangle(symName(p)))
504- c.line("var " & id & ": Value = call(getVar(\"clojure.core/nth\"), [" & v & ", mkInt(" &
505- $idx & "), NilV])")
506- lenv.locals[symName(p)] = id
507- inc idx; inc j
508- elif target.kind == kMap:
509- # associative destructuring: {a :a, :keys [b c]}
510- for (k, valForm) in target.pairs:
511- if k.kind == kKeyword and k.s == "keys":
512- for ks in valForm.items:
513- let nm = symName(ks)
514- let id = c.gensym("l" & mangle(nm))
515- c.line("var " & id & ": Value = call(getVar(\"clojure.core/get\"), [" & v &
516- ", mkKeyword(" & nimStr(nm) & ")])")
517- lenv.locals[nm] = id
518- else:
519- let nm = symName(k)
520- let id = c.gensym("l" & mangle(nm))
521- let kv = genExpr(valForm, lenv, c)
522- c.line("var " & id & ": Value = call(getVar(\"clojure.core/get\"), [" & v & ", " & kv & "])")
523- lenv.locals[nm] = id
524- else:
525- err("Unsupported binding form: " & prStr(target))
526 i += 2619 i += 2
527 genBody(body, dst, lenv, c)620 genBody(body, dst, lenv, c)
528 621
529 proc genLoop(bindings: Value, body: seq[Value], dst: string, env: Env, c: Ctx) =622 proc genLoop(bindings: Value, body: seq[Value], dst: string, env: Env, c: Ctx) =
530 if bindings.isNil or bindings.kind != kVector or bindings.items.len mod 2 != 0:623 if bindings.isNil or bindings.kind != kVector or bindings.items.len mod 2 != 0:
531 err("loop requires an even-sized binding vector")624 err("loop requires an even-sized binding vector")
625+ # A destructuring loop variable becomes a plain one that recur can assign,
626+ # taken apart again by a let at the top of the body.
627+ var bindings = bindings
628+ var body = body
629+ var patterns: seq[Value] = @[]
630+ var i = 0
631+ while i < bindings.items.len:
632+ if bindings.items[i].kind != kSymbol:
633+ let nm = mkSymbol("loop__" & $i)
634+ patterns.add bindings.items[i]
635+ patterns.add nm
636+ var xs = bindings.items
637+ xs[i] = nm
638+ bindings = mkVector(xs)
639+ i += 2
640+ if patterns.len > 0:
641+ body = @[mkList(@[mkSymbol("let"), mkVector(patterns)] & body)]
642+
532 var names: seq[string] = @[]643 var names: seq[string] = @[]
533 var inits: seq[Value] = @[]644 var inits: seq[Value] = @[]
534- var i = 0645+ i = 0
535 while i < bindings.items.len:646 while i < bindings.items.len:
536 names.add symName(bindings.items[i])647 names.add symName(bindings.items[i])
537 inits.add bindings.items[i + 1]648 inits.add bindings.items[i + 1]
@@ -545,8 +656,9 @@ proc genLoop(bindings: Value, body: seq[Value], dst: string, env: Env, c: Ctx) =
545 collectRecurs(body, recurs)656 collectRecurs(body, recurs)
546 for r in recurs:657 for r in recurs:
547 if r.len != names.len: recurs = @[]; break # arity error, reported later658 if r.len != names.len: recurs = @[]; break # arity error, reported later
659+ let shadowed = shadowedNames(body)
548 var isInt: seq[bool] = @[]660 var isInt: seq[bool] = @[]
549- for n in names: isInt.add true661+ for n in names: isInt.add n notin shadowed
550 var probeIdents: seq[string] = @[]662 var probeIdents: seq[string] = @[]
551 for n in names: probeIdents.add "probe"663 for n in names: probeIdents.add "probe"
552 while true:664 while true:
@@ -1160,6 +1272,7 @@ proc genInto(f: Value, dst: string, env: Env, c: Ctx) =
1160 # ------------------------------------------------------------- entry point1272 # ------------------------------------------------------------- entry point
1161 const preamble = """1273 const preamble = """
1162 ## Generated by clonim. Do not edit.1274 ## Generated by clonim. Do not edit.
1275+import std/os
1163 import app_runtime1276 import app_runtime
1164 1277
1165 proc cljMain() =1278 proc cljMain() =
@@ -1191,6 +1304,20 @@ proc compileForms*(forms: seq[Value]): string =
1191 c.line("var " & t & ": Value = NilV")1304 c.line("var " & t & ": Value = NilV")
1192 genInto(f, t, env, c)1305 genInto(f, t, env, c)
1193 c.line("discard " & t)1306 c.line("discard " & t)
1307+ # A program that defines -main gets it called with the command-line
1308+ # arguments once the top-level forms have run, as on the JVM.
1309+ var mainVar = ""
1310+ for name, _ in c.defCounts:
1311+ if name == "-main" or name.endsWith("/-main"): mainVar = name
1312+ c.line("var clonimArgv: seq[Value] = @[]")
1313+ c.line("for a in commandLineParams(): clonimArgv.add mkStr(a)")
1314+ c.line("setVar(\"clojure.core/*command-line-args*\", " &
1315+ "(if clonimArgv.len == 0: NilV else: mkList(clonimArgv)))")
1316+ if mainVar.len > 0:
1317+ c.line("if hasVar(" & nimStr(mainVar) & "):")
1318+ c.push
1319+ c.line("discard call(getVar(" & nimStr(mainVar) & "), clonimArgv)")
1320+ c.pop
1194 var src = preamble & " initClonimRuntime()\n registerCore()\n registerNamespaceCore()\n" & c.prelude.join("\n") & "\n" &1321 var src = preamble & " initClonimRuntime()\n registerCore()\n registerNamespaceCore()\n" & c.prelude.join("\n") & "\n" &
1195 c.body.join("\n") & "\n\n"1322 c.body.join("\n") & "\n\n"
1196 src &= """1323 src &= """
modified src/core.nim +34 -25
@@ -401,6 +401,12 @@ proc fusedCount*(base: Value, ops: openArray[FusedOp]): Value =
401401 inc n
402402 mkInt(n)
403403
404+proc sOf(v: Value): string =
405+ ## A string argument, checked: reaching into a nil or a number here would
406+ ## read through a nil object pointer.
407+ if v.isNil or v.kind != kStr: err("Expected a string, got: " & prStr(v))
408+ v.s
409+
404410 proc parseIntRadix(s: string, radix: int): int64 =
405411 ## Java's Integer/Long.parseInt: an optional sign, then digits in `radix`.
406412 var i = 0
@@ -639,6 +645,7 @@ proc registerCore*() =
639645 def "*out*", mkKeyword("stdout")
640646 def "*err*", mkKeyword("stderr")
641647 def "*in*", mkKeyword("stdin")
648+ def "*command-line-args*", NilV
642649 def "println", proc (a: openArray[Value]): Value =
643650 var parts: seq[string] = @[]
644651 for x in a: parts.add str(x)
@@ -674,25 +681,25 @@ proc registerCore*() =
674681 def "keyword", proc (a: openArray[Value]): Value = mkKeyword(str(a[0]))
675682 def "symbol", proc (a: openArray[Value]): Value = mkSymbol(str(a[0]))
676683 def "subs", proc (a: openArray[Value]): Value =
677- let s = a[0].s
684+ let s = sOf(a[0])
678685 let st = int(intOf(a[1]))
679686 let en = (if a.len > 2: int(intOf(a[2])) else: s.len)
680687 mkStr(s[st ..< en])
681- def "clojure.string/upper-case", proc (a: openArray[Value]): Value = mkStr(a[0].s.toUpperAscii)
682- def "clojure.string/lower-case", proc (a: openArray[Value]): Value = mkStr(a[0].s.toLowerAscii)
683- def "clojure.string/trim", proc (a: openArray[Value]): Value = mkStr(a[0].s.strip)
688+ def "clojure.string/upper-case", proc (a: openArray[Value]): Value = mkStr(sOf(a[0]).toUpperAscii)
689+ def "clojure.string/lower-case", proc (a: openArray[Value]): Value = mkStr(sOf(a[0]).toLowerAscii)
690+ def "clojure.string/trim", proc (a: openArray[Value]): Value = mkStr(sOf(a[0]).strip)
684691 def "re-find", proc (a: openArray[Value]): Value =
685- let pattern = re(a[0].s)
686- let start = find(a[1].s, pattern)
692+ let pattern = re(sOf(a[0]))
693+ let start = find(sOf(a[1]), pattern)
687694 if start < 0: NilV
688695 else:
689- let n = matchLen(a[1].s, pattern, start)
690- mkStr(a[1].s[start ..< start + n])
696+ let n = matchLen(sOf(a[1]), pattern, start)
697+ mkStr(sOf(a[1])[start ..< start + n])
691698 def "clojure.string/replace", proc (a: openArray[Value]): Value =
692- mkStr(a[0].s.replace(re(a[1].s), a[2].s))
699+ mkStr(sOf(a[0]).replace(re(sOf(a[1])), sOf(a[2])))
693700 def "clojure.string/split", proc (a: openArray[Value]): Value =
694701 var r: seq[Value] = @[]
695- for piece in a[0].s.split(a[1].s): r.add mkStr(piece)
702+ for piece in sOf(a[0]).split(sOf(a[1])): r.add mkStr(piece)
696703 mkVector(r)
697704 def "clojure.string/join", proc (a: openArray[Value]): Value =
698705 let sep = (if a.len > 1: str(a[0]) else: "")
@@ -702,9 +709,11 @@ proc registerCore*() =
702709 mkStr(parts.join(sep))
703710 def "read-line", proc (a: openArray[Value]): Value =
704711 try: mkStr(stdin.readLine()) except CatchableError: NilV
705- def "slurp", proc (a: openArray[Value]): Value = mkStr(readFile(a[0].s))
712+ def "slurp", proc (a: openArray[Value]): Value =
713+ (if a[0].kind == kKeyword and a[0].s == "stdin": mkStr(stdin.readAll)
714+ else: mkStr(readFile(sOf(a[0]))))
706715 def "spit", proc (a: openArray[Value]): Value =
707- writeFile(a[0].s, str(a[1])); NilV
716+ writeFile(sOf(a[0]), str(a[1])); NilV
708717 def "int-array", proc (a: openArray[Value]): Value =
709718 if a.len == 1 and a[0].kind == kInt: mkList(newSeqWith(int(a[0].i), mkInt(0)))
710719 elif a.len == 1: mkList(toSeq(a[0])) else: mkList(a)
@@ -726,7 +735,7 @@ proc registerCore*() =
726735 NilV
727736 def ".getBytes", proc (a: openArray[Value]): Value =
728737 var xs: seq[Value] = @[]
729- for ch in a[0].s: xs.add mkInt(ord(ch))
738+ for ch in sOf(a[0]): xs.add mkInt(ord(ch))
730739 mkList(xs)
731740 def "StringBuilder.", proc (a: openArray[Value]): Value =
732741 ## A mutable string: the one place a string value is written in place.
@@ -736,11 +745,11 @@ proc registerCore*() =
736745 a[0].obj.s.add(str(a[1]))
737746 a[0]
738747 def ".toString", proc (a: openArray[Value]): Value = mkStr(str(a[0]))
739- def ".length", proc (a: openArray[Value]): Value = mkInt(int64(a[0].s.len))
748+ def ".length", proc (a: openArray[Value]): Value = mkInt(int64(sOf(a[0]).len))
740749 def "*output-stream*", proc (a: openArray[Value]): Value =
741750 ## A write handle, driven by keyword messages so that it stays an
742751 ## ordinary value: (o :write bytes) and (o :close).
743- let path = a[0].s
752+ let path = sOf(a[0])
744753 var f: File
745754 if not f.open(path, fmWrite): err("Cannot open for writing: " & path)
746755 var closed = false
@@ -763,7 +772,7 @@ proc registerCore*() =
763772 (if a[0].kind == kFn: call(a[0], [mkKeyword("close")]) else: NilV)
764773 def "*delete-file*", proc (a: openArray[Value]): Value =
765774 try:
766- removeFile(a[0].s)
775+ removeFile(sOf(a[0]))
767776 TrueV
768777 except OSError:
769778 if a.len > 1 and truthy(a[1]): FalseV
@@ -771,8 +780,8 @@ proc registerCore*() =
771780 def "*http-fetch*", proc (a: openArray[Value]): Value =
772781 ## Native replacement for the narrow Jolt HTTP API used by Freeqsay.
773782 try:
774- let response = newHttpClient().get(a[0].s)
775- writeFile(a[1].s, response.body)
783+ let response = newHttpClient().get(sOf(a[0]))
784+ writeFile(sOf(a[1]), response.body)
776785 mkMap(@[(mkKeyword("outcome"), mkKeyword("ok")),
777786 (mkKeyword("status"), mkInt(response.code.int)),
778787 (mkKeyword("error"), NilV)])
@@ -1101,29 +1110,29 @@ proc registerCore*() =
11011110 def "class", proc (a: openArray[Value]): Value = mkKeyword($a[0].kind)
11021111 def "instance?", proc (a: openArray[Value]): Value =
11031112 mkBool(a[0].kind == kKeyword and a[0].s == $a[1].kind)
1104- def "format", proc (a: openArray[Value]): Value = mkStr(javaFormat(a[0].s, a[1 .. ^1]))
1113+ def "format", proc (a: openArray[Value]): Value = mkStr(javaFormat(sOf(a[0]), a[1 .. ^1]))
11051114 def "System/exit", proc (a: openArray[Value]): Value =
11061115 quit(if a.len > 0: int(intOf(a[0])) else: 0)
11071116 def "System/currentTimeMillis", proc (a: openArray[Value]): Value =
11081117 mkInt(int64(epochTime() * 1000))
11091118 def "System/getProperty", proc (a: openArray[Value]): Value =
11101119 ## Only the properties a hosted program can reasonably expect here.
1111- case a[0].s
1120+ case sOf(a[0])
11121121 of "java.io.tmpdir": mkStr(getTempDir().strip(leading = false, chars = {'/'}))
11131122 of "user.dir": mkStr(getCurrentDir())
11141123 of "user.name": mkStr(getEnv("USER"))
11151124 of "line.separator": mkStr("\n")
11161125 else: (if a.len > 1: a[1] else: NilV)
11171126 def "clojure.string/blank?", proc (a: openArray[Value]): Value =
1118- mkBool(a[0].kind == kNil or a[0].s.strip.len == 0)
1127+ mkBool(a[0].kind == kNil or sOf(a[0]).strip.len == 0)
11191128 def "clojure.string/starts-with?", proc (a: openArray[Value]): Value =
1120- mkBool(a[0].s.startsWith(a[1].s))
1129+ mkBool(sOf(a[0]).startsWith(sOf(a[1])))
11211130 def "clojure.string/ends-with?", proc (a: openArray[Value]): Value =
1122- mkBool(a[0].s.endsWith(a[1].s))
1131+ mkBool(sOf(a[0]).endsWith(sOf(a[1])))
11231132 def "clojure.string/includes?", proc (a: openArray[Value]): Value =
1124- mkBool(a[1].s in a[0].s)
1133+ mkBool(sOf(a[1]) in sOf(a[0]))
11251134 def "clojure.string/index-of", proc (a: openArray[Value]): Value =
1126- let i = a[0].s.find(a[1].s)
1135+ let i = sOf(a[0]).find(sOf(a[1]))
11271136 (if i < 0: NilV else: mkInt(int64(i)))
11281137 def "throw", proc (a: openArray[Value]): Value = err(str(a[0]))
11291138 def "ex-info", proc (a: openArray[Value]): Value = mkStr(str(a[0]))
@@ -401,6 +401,12 @@ proc fusedCount*(base: Value, ops: openArray[FusedOp]): Value =
401 inc n401 inc n
402 mkInt(n)402 mkInt(n)
403 403
404+proc sOf(v: Value): string =
405+ ## A string argument, checked: reaching into a nil or a number here would
406+ ## read through a nil object pointer.
407+ if v.isNil or v.kind != kStr: err("Expected a string, got: " & prStr(v))
408+ v.s
409+
404 proc parseIntRadix(s: string, radix: int): int64 =410 proc parseIntRadix(s: string, radix: int): int64 =
405 ## Java's Integer/Long.parseInt: an optional sign, then digits in `radix`.411 ## Java's Integer/Long.parseInt: an optional sign, then digits in `radix`.
406 var i = 0412 var i = 0
@@ -639,6 +645,7 @@ proc registerCore*() =
639 def "*out*", mkKeyword("stdout")645 def "*out*", mkKeyword("stdout")
640 def "*err*", mkKeyword("stderr")646 def "*err*", mkKeyword("stderr")
641 def "*in*", mkKeyword("stdin")647 def "*in*", mkKeyword("stdin")
648+ def "*command-line-args*", NilV
642 def "println", proc (a: openArray[Value]): Value =649 def "println", proc (a: openArray[Value]): Value =
643 var parts: seq[string] = @[]650 var parts: seq[string] = @[]
644 for x in a: parts.add str(x)651 for x in a: parts.add str(x)
@@ -674,25 +681,25 @@ proc registerCore*() =
674 def "keyword", proc (a: openArray[Value]): Value = mkKeyword(str(a[0]))681 def "keyword", proc (a: openArray[Value]): Value = mkKeyword(str(a[0]))
675 def "symbol", proc (a: openArray[Value]): Value = mkSymbol(str(a[0]))682 def "symbol", proc (a: openArray[Value]): Value = mkSymbol(str(a[0]))
676 def "subs", proc (a: openArray[Value]): Value =683 def "subs", proc (a: openArray[Value]): Value =
677- let s = a[0].s684+ let s = sOf(a[0])
678 let st = int(intOf(a[1]))685 let st = int(intOf(a[1]))
679 let en = (if a.len > 2: int(intOf(a[2])) else: s.len)686 let en = (if a.len > 2: int(intOf(a[2])) else: s.len)
680 mkStr(s[st ..< en])687 mkStr(s[st ..< en])
681- def "clojure.string/upper-case", proc (a: openArray[Value]): Value = mkStr(a[0].s.toUpperAscii)688+ def "clojure.string/upper-case", proc (a: openArray[Value]): Value = mkStr(sOf(a[0]).toUpperAscii)
682- def "clojure.string/lower-case", proc (a: openArray[Value]): Value = mkStr(a[0].s.toLowerAscii)689+ def "clojure.string/lower-case", proc (a: openArray[Value]): Value = mkStr(sOf(a[0]).toLowerAscii)
683- def "clojure.string/trim", proc (a: openArray[Value]): Value = mkStr(a[0].s.strip)690+ def "clojure.string/trim", proc (a: openArray[Value]): Value = mkStr(sOf(a[0]).strip)
684 def "re-find", proc (a: openArray[Value]): Value =691 def "re-find", proc (a: openArray[Value]): Value =
685- let pattern = re(a[0].s)692+ let pattern = re(sOf(a[0]))
686- let start = find(a[1].s, pattern)693+ let start = find(sOf(a[1]), pattern)
687 if start < 0: NilV694 if start < 0: NilV
688 else:695 else:
689- let n = matchLen(a[1].s, pattern, start)696+ let n = matchLen(sOf(a[1]), pattern, start)
690- mkStr(a[1].s[start ..< start + n])697+ mkStr(sOf(a[1])[start ..< start + n])
691 def "clojure.string/replace", proc (a: openArray[Value]): Value =698 def "clojure.string/replace", proc (a: openArray[Value]): Value =
692- mkStr(a[0].s.replace(re(a[1].s), a[2].s))699+ mkStr(sOf(a[0]).replace(re(sOf(a[1])), sOf(a[2])))
693 def "clojure.string/split", proc (a: openArray[Value]): Value =700 def "clojure.string/split", proc (a: openArray[Value]): Value =
694 var r: seq[Value] = @[]701 var r: seq[Value] = @[]
695- for piece in a[0].s.split(a[1].s): r.add mkStr(piece)702+ for piece in sOf(a[0]).split(sOf(a[1])): r.add mkStr(piece)
696 mkVector(r)703 mkVector(r)
697 def "clojure.string/join", proc (a: openArray[Value]): Value =704 def "clojure.string/join", proc (a: openArray[Value]): Value =
698 let sep = (if a.len > 1: str(a[0]) else: "")705 let sep = (if a.len > 1: str(a[0]) else: "")
@@ -702,9 +709,11 @@ proc registerCore*() =
702 mkStr(parts.join(sep))709 mkStr(parts.join(sep))
703 def "read-line", proc (a: openArray[Value]): Value =710 def "read-line", proc (a: openArray[Value]): Value =
704 try: mkStr(stdin.readLine()) except CatchableError: NilV711 try: mkStr(stdin.readLine()) except CatchableError: NilV
705- def "slurp", proc (a: openArray[Value]): Value = mkStr(readFile(a[0].s))712+ def "slurp", proc (a: openArray[Value]): Value =
713+ (if a[0].kind == kKeyword and a[0].s == "stdin": mkStr(stdin.readAll)
714+ else: mkStr(readFile(sOf(a[0]))))
706 def "spit", proc (a: openArray[Value]): Value =715 def "spit", proc (a: openArray[Value]): Value =
707- writeFile(a[0].s, str(a[1])); NilV716+ writeFile(sOf(a[0]), str(a[1])); NilV
708 def "int-array", proc (a: openArray[Value]): Value =717 def "int-array", proc (a: openArray[Value]): Value =
709 if a.len == 1 and a[0].kind == kInt: mkList(newSeqWith(int(a[0].i), mkInt(0)))718 if a.len == 1 and a[0].kind == kInt: mkList(newSeqWith(int(a[0].i), mkInt(0)))
710 elif a.len == 1: mkList(toSeq(a[0])) else: mkList(a)719 elif a.len == 1: mkList(toSeq(a[0])) else: mkList(a)
@@ -726,7 +735,7 @@ proc registerCore*() =
726 NilV735 NilV
727 def ".getBytes", proc (a: openArray[Value]): Value =736 def ".getBytes", proc (a: openArray[Value]): Value =
728 var xs: seq[Value] = @[]737 var xs: seq[Value] = @[]
729- for ch in a[0].s: xs.add mkInt(ord(ch))738+ for ch in sOf(a[0]): xs.add mkInt(ord(ch))
730 mkList(xs)739 mkList(xs)
731 def "StringBuilder.", proc (a: openArray[Value]): Value =740 def "StringBuilder.", proc (a: openArray[Value]): Value =
732 ## A mutable string: the one place a string value is written in place.741 ## A mutable string: the one place a string value is written in place.
@@ -736,11 +745,11 @@ proc registerCore*() =
736 a[0].obj.s.add(str(a[1]))745 a[0].obj.s.add(str(a[1]))
737 a[0]746 a[0]
738 def ".toString", proc (a: openArray[Value]): Value = mkStr(str(a[0]))747 def ".toString", proc (a: openArray[Value]): Value = mkStr(str(a[0]))
739- def ".length", proc (a: openArray[Value]): Value = mkInt(int64(a[0].s.len))748+ def ".length", proc (a: openArray[Value]): Value = mkInt(int64(sOf(a[0]).len))
740 def "*output-stream*", proc (a: openArray[Value]): Value =749 def "*output-stream*", proc (a: openArray[Value]): Value =
741 ## A write handle, driven by keyword messages so that it stays an750 ## A write handle, driven by keyword messages so that it stays an
742 ## ordinary value: (o :write bytes) and (o :close).751 ## ordinary value: (o :write bytes) and (o :close).
743- let path = a[0].s752+ let path = sOf(a[0])
744 var f: File753 var f: File
745 if not f.open(path, fmWrite): err("Cannot open for writing: " & path)754 if not f.open(path, fmWrite): err("Cannot open for writing: " & path)
746 var closed = false755 var closed = false
@@ -763,7 +772,7 @@ proc registerCore*() =
763 (if a[0].kind == kFn: call(a[0], [mkKeyword("close")]) else: NilV)772 (if a[0].kind == kFn: call(a[0], [mkKeyword("close")]) else: NilV)
764 def "*delete-file*", proc (a: openArray[Value]): Value =773 def "*delete-file*", proc (a: openArray[Value]): Value =
765 try:774 try:
766- removeFile(a[0].s)775+ removeFile(sOf(a[0]))
767 TrueV776 TrueV
768 except OSError:777 except OSError:
769 if a.len > 1 and truthy(a[1]): FalseV778 if a.len > 1 and truthy(a[1]): FalseV
@@ -771,8 +780,8 @@ proc registerCore*() =
771 def "*http-fetch*", proc (a: openArray[Value]): Value =780 def "*http-fetch*", proc (a: openArray[Value]): Value =
772 ## Native replacement for the narrow Jolt HTTP API used by Freeqsay.781 ## Native replacement for the narrow Jolt HTTP API used by Freeqsay.
773 try:782 try:
774- let response = newHttpClient().get(a[0].s)783+ let response = newHttpClient().get(sOf(a[0]))
775- writeFile(a[1].s, response.body)784+ writeFile(sOf(a[1]), response.body)
776 mkMap(@[(mkKeyword("outcome"), mkKeyword("ok")),785 mkMap(@[(mkKeyword("outcome"), mkKeyword("ok")),
777 (mkKeyword("status"), mkInt(response.code.int)),786 (mkKeyword("status"), mkInt(response.code.int)),
778 (mkKeyword("error"), NilV)])787 (mkKeyword("error"), NilV)])
@@ -1101,29 +1110,29 @@ proc registerCore*() =
1101 def "class", proc (a: openArray[Value]): Value = mkKeyword($a[0].kind)1110 def "class", proc (a: openArray[Value]): Value = mkKeyword($a[0].kind)
1102 def "instance?", proc (a: openArray[Value]): Value =1111 def "instance?", proc (a: openArray[Value]): Value =
1103 mkBool(a[0].kind == kKeyword and a[0].s == $a[1].kind)1112 mkBool(a[0].kind == kKeyword and a[0].s == $a[1].kind)
1104- def "format", proc (a: openArray[Value]): Value = mkStr(javaFormat(a[0].s, a[1 .. ^1]))1113+ def "format", proc (a: openArray[Value]): Value = mkStr(javaFormat(sOf(a[0]), a[1 .. ^1]))
1105 def "System/exit", proc (a: openArray[Value]): Value =1114 def "System/exit", proc (a: openArray[Value]): Value =
1106 quit(if a.len > 0: int(intOf(a[0])) else: 0)1115 quit(if a.len > 0: int(intOf(a[0])) else: 0)
1107 def "System/currentTimeMillis", proc (a: openArray[Value]): Value =1116 def "System/currentTimeMillis", proc (a: openArray[Value]): Value =
1108 mkInt(int64(epochTime() * 1000))1117 mkInt(int64(epochTime() * 1000))
1109 def "System/getProperty", proc (a: openArray[Value]): Value =1118 def "System/getProperty", proc (a: openArray[Value]): Value =
1110 ## Only the properties a hosted program can reasonably expect here.1119 ## Only the properties a hosted program can reasonably expect here.
1111- case a[0].s1120+ case sOf(a[0])
1112 of "java.io.tmpdir": mkStr(getTempDir().strip(leading = false, chars = {'/'}))1121 of "java.io.tmpdir": mkStr(getTempDir().strip(leading = false, chars = {'/'}))
1113 of "user.dir": mkStr(getCurrentDir())1122 of "user.dir": mkStr(getCurrentDir())
1114 of "user.name": mkStr(getEnv("USER"))1123 of "user.name": mkStr(getEnv("USER"))
1115 of "line.separator": mkStr("\n")1124 of "line.separator": mkStr("\n")
1116 else: (if a.len > 1: a[1] else: NilV)1125 else: (if a.len > 1: a[1] else: NilV)
1117 def "clojure.string/blank?", proc (a: openArray[Value]): Value =1126 def "clojure.string/blank?", proc (a: openArray[Value]): Value =
1118- mkBool(a[0].kind == kNil or a[0].s.strip.len == 0)1127+ mkBool(a[0].kind == kNil or sOf(a[0]).strip.len == 0)
1119 def "clojure.string/starts-with?", proc (a: openArray[Value]): Value =1128 def "clojure.string/starts-with?", proc (a: openArray[Value]): Value =
1120- mkBool(a[0].s.startsWith(a[1].s))1129+ mkBool(sOf(a[0]).startsWith(sOf(a[1])))
1121 def "clojure.string/ends-with?", proc (a: openArray[Value]): Value =1130 def "clojure.string/ends-with?", proc (a: openArray[Value]): Value =
1122- mkBool(a[0].s.endsWith(a[1].s))1131+ mkBool(sOf(a[0]).endsWith(sOf(a[1])))
1123 def "clojure.string/includes?", proc (a: openArray[Value]): Value =1132 def "clojure.string/includes?", proc (a: openArray[Value]): Value =
1124- mkBool(a[1].s in a[0].s)1133+ mkBool(sOf(a[1]) in sOf(a[0]))
1125 def "clojure.string/index-of", proc (a: openArray[Value]): Value =1134 def "clojure.string/index-of", proc (a: openArray[Value]): Value =
1126- let i = a[0].s.find(a[1].s)1135+ let i = sOf(a[0]).find(sOf(a[1]))
1127 (if i < 0: NilV else: mkInt(int64(i)))1136 (if i < 0: NilV else: mkInt(int64(i)))
1128 def "throw", proc (a: openArray[Value]): Value = err(str(a[0]))1137 def "throw", proc (a: openArray[Value]): Value = err(str(a[0]))
1129 def "ex-info", proc (a: openArray[Value]): Value = mkStr(str(a[0]))1138 def "ex-info", proc (a: openArray[Value]): Value = mkStr(str(a[0]))
modified src/reader.nim +32 -3
@@ -37,8 +37,18 @@ proc skipWs(r: var Reader) =
3737
3838 proc readForm(r: var Reader): Value
3939
40+proc anonArgIndex(name: string): int =
41+ ## % and %1 are the first argument, %2 the second, and so on. -1 for
42+ ## anything that is not an anonymous-argument symbol.
43+ if name.len == 0 or name[0] != '%': return -1
44+ if name.len == 1: return 1
45+ try: parseInt(name[1 .. ^1])
46+ except ValueError: -1
47+
4048 proc replaceAnonArg(v: Value, arg: string): Value =
41- if v.kind == kSymbol and v.s == "%": return mkSymbol(arg)
49+ if v.kind == kSymbol:
50+ let n = anonArgIndex(v.s)
51+ if n > 0: return mkSymbol(arg & "_" & $n)
4252 case v.kind
4353 of kList: mkList(v.items.mapIt(replaceAnonArg(it, arg)))
4454 of kVector: mkVector(v.items.mapIt(replaceAnonArg(it, arg)))
@@ -49,6 +59,20 @@ proc replaceAnonArg(v: Value, arg: string): Value =
4959 mkMap(pairs)
5060 else: v
5161
62+proc countAnonArgs(v: Value, arity: var int) =
63+ if v.isNil: return
64+ if v.kind == kSymbol:
65+ let n = anonArgIndex(v.s)
66+ if n > arity: arity = n
67+ return
68+ case v.kind
69+ of kList, kVector, kSet:
70+ for x in v.items: countAnonArgs(x, arity)
71+ of kMap:
72+ for (k, val) in v.pairs:
73+ countAnonArgs(k, arity); countAnonArgs(val, arity)
74+ else: discard
75+
5276 proc readDelimited(r: var Reader, closing: char): seq[Value] =
5377 result = @[]
5478 while true:
@@ -196,8 +220,13 @@ proc readForm(r: var Reader): Value =
196220 inc r.anonId
197221 let arg = "anon_arg_" & $r.anonId
198222 let body = r.readDelimited(')')
199- return mkList(@[mkSymbol("fn"), mkVector(@[mkSymbol(arg)])] &
200- body.mapIt(replaceAnonArg(it, arg)))
223+ # The contents are one call, not a body: #(f x) is (fn [a] (f x)).
224+ let form = replaceAnonArg(mkList(body), arg)
225+ var arity = 0
226+ countAnonArgs(mkList(body), arity)
227+ var params: seq[Value] = @[]
228+ for i in 1 .. arity: params.add mkSymbol(arg & "_" & $i)
229+ return mkList(@[mkSymbol("fn"), mkVector(params), form])
201230 r.readerErr("Unsupported dispatch: #" & r.peek2
202231 )
203232 else:
@@ -37,8 +37,18 @@ proc skipWs(r: var Reader) =
37 37
38 proc readForm(r: var Reader): Value38 proc readForm(r: var Reader): Value
39 39
40+proc anonArgIndex(name: string): int =
41+ ## % and %1 are the first argument, %2 the second, and so on. -1 for
42+ ## anything that is not an anonymous-argument symbol.
43+ if name.len == 0 or name[0] != '%': return -1
44+ if name.len == 1: return 1
45+ try: parseInt(name[1 .. ^1])
46+ except ValueError: -1
47+
40 proc replaceAnonArg(v: Value, arg: string): Value =48 proc replaceAnonArg(v: Value, arg: string): Value =
41- if v.kind == kSymbol and v.s == "%": return mkSymbol(arg)49+ if v.kind == kSymbol:
50+ let n = anonArgIndex(v.s)
51+ if n > 0: return mkSymbol(arg & "_" & $n)
42 case v.kind52 case v.kind
43 of kList: mkList(v.items.mapIt(replaceAnonArg(it, arg)))53 of kList: mkList(v.items.mapIt(replaceAnonArg(it, arg)))
44 of kVector: mkVector(v.items.mapIt(replaceAnonArg(it, arg)))54 of kVector: mkVector(v.items.mapIt(replaceAnonArg(it, arg)))
@@ -49,6 +59,20 @@ proc replaceAnonArg(v: Value, arg: string): Value =
49 mkMap(pairs)59 mkMap(pairs)
50 else: v60 else: v
51 61
62+proc countAnonArgs(v: Value, arity: var int) =
63+ if v.isNil: return
64+ if v.kind == kSymbol:
65+ let n = anonArgIndex(v.s)
66+ if n > arity: arity = n
67+ return
68+ case v.kind
69+ of kList, kVector, kSet:
70+ for x in v.items: countAnonArgs(x, arity)
71+ of kMap:
72+ for (k, val) in v.pairs:
73+ countAnonArgs(k, arity); countAnonArgs(val, arity)
74+ else: discard
75+
52 proc readDelimited(r: var Reader, closing: char): seq[Value] =76 proc readDelimited(r: var Reader, closing: char): seq[Value] =
53 result = @[]77 result = @[]
54 while true:78 while true:
@@ -196,8 +220,13 @@ proc readForm(r: var Reader): Value =
196 inc r.anonId220 inc r.anonId
197 let arg = "anon_arg_" & $r.anonId221 let arg = "anon_arg_" & $r.anonId
198 let body = r.readDelimited(')')222 let body = r.readDelimited(')')
199- return mkList(@[mkSymbol("fn"), mkVector(@[mkSymbol(arg)])] &223+ # The contents are one call, not a body: #(f x) is (fn [a] (f x)).
200- body.mapIt(replaceAnonArg(it, arg)))224+ let form = replaceAnonArg(mkList(body), arg)
225+ var arity = 0
226+ countAnonArgs(mkList(body), arity)
227+ var params: seq[Value] = @[]
228+ for i in 1 .. arity: params.add mkSymbol(arg & "_" & $i)
229+ return mkList(@[mkSymbol("fn"), mkVector(params), form])
201 r.readerErr("Unsupported dispatch: #" & r.peek2230 r.readerErr("Unsupported dispatch: #" & r.peek2
202 )231 )
203 else:232 else:
modified src/runtime.nim +6 -0
@@ -440,6 +440,12 @@ proc seqDrop*(v: Value, n: int): Value =
440440 result = seqRest(result)
441441 dec k
442442
443+proc seqDropOrNil*(v: Value, n: int): Value =
444+ ## What `& rest` binds: the remainder, or nil once it is exhausted, as on
445+ ## the JVM, where an empty rest is nil rather than an empty seq.
446+ result = seqDrop(v, n)
447+ if seqIsEmpty(result): result = NilV
448+
443449 proc hashValue*(v: Value): uint32 =
444450 if v.isNil: return 0
445451 case v.kind
@@ -440,6 +440,12 @@ proc seqDrop*(v: Value, n: int): Value =
440 result = seqRest(result)440 result = seqRest(result)
441 dec k441 dec k
442 442
443+proc seqDropOrNil*(v: Value, n: int): Value =
444+ ## What `& rest` binds: the remainder, or nil once it is exhausted, as on
445+ ## the JVM, where an empty rest is nil rather than an empty seq.
446+ result = seqDrop(v, n)
447+ if seqIsEmpty(result): result = NilV
448+
443 proc hashValue*(v: Value): uint32 =449 proc hashValue*(v: Value): uint32 =
444 if v.isNil: return 0450 if v.isNil: return 0
445 case v.kind451 case v.kind
modified src/runtime_lib.nim +2 -0
@@ -15,10 +15,12 @@ proc clonim_set_var(name: string, v: Value): Value = setVar(name, v)
1515 proc clonim_cell_get(c: VarCell): Value = cellGet(c)
1616 proc clonim_cell_is(c: VarCell, v: Value): bool = cellIs(c, v)
1717 proc clonim_get_var(name: string): Value = getVar(name)
18+proc clonim_has_var(name: string): bool = hasVar(name)
1819 proc clonim_call(f: Value, args: openArray[Value]): Value = call(f, args)
1920 proc clonim_arg_at(args: openArray[Value], i: int): Value = argAt(args, i)
2021 proc clonim_rest_args(args: openArray[Value], i: int): Value = restArgs(args, i)
2122 proc clonim_seq_drop(v: Value, n: int): Value = seqDrop(v, n)
23+proc clonim_seq_drop_or_nil(v: Value, n: int): Value = seqDropOrNil(v, n)
2224 proc clonim_to_seq(v: Value): seq[Value] = toSeq(v)
2325 proc clonim_truthy(v: Value): bool = truthy(v)
2426
@@ -15,10 +15,12 @@ proc clonim_set_var(name: string, v: Value): Value = setVar(name, v)
15 proc clonim_cell_get(c: VarCell): Value = cellGet(c)15 proc clonim_cell_get(c: VarCell): Value = cellGet(c)
16 proc clonim_cell_is(c: VarCell, v: Value): bool = cellIs(c, v)16 proc clonim_cell_is(c: VarCell, v: Value): bool = cellIs(c, v)
17 proc clonim_get_var(name: string): Value = getVar(name)17 proc clonim_get_var(name: string): Value = getVar(name)
18+proc clonim_has_var(name: string): bool = hasVar(name)
18 proc clonim_call(f: Value, args: openArray[Value]): Value = call(f, args)19 proc clonim_call(f: Value, args: openArray[Value]): Value = call(f, args)
19 proc clonim_arg_at(args: openArray[Value], i: int): Value = argAt(args, i)20 proc clonim_arg_at(args: openArray[Value], i: int): Value = argAt(args, i)
20 proc clonim_rest_args(args: openArray[Value], i: int): Value = restArgs(args, i)21 proc clonim_rest_args(args: openArray[Value], i: int): Value = restArgs(args, i)
21 proc clonim_seq_drop(v: Value, n: int): Value = seqDrop(v, n)22 proc clonim_seq_drop(v: Value, n: int): Value = seqDrop(v, n)
23+proc clonim_seq_drop_or_nil(v: Value, n: int): Value = seqDropOrNil(v, n)
22 proc clonim_to_seq(v: Value): seq[Value] = toSeq(v)24 proc clonim_to_seq(v: Value): seq[Value] = toSeq(v)
23 proc clonim_truthy(v: Value): bool = truthy(v)25 proc clonim_truthy(v: Value): bool = truthy(v)
24 26
added tests/binding_forms.expected +15 -0
new file mode 100644
@@ -0,0 +1,15 @@
1+[1 2 3]
2+(:a1 :b2)
3+7 12
4+[1 2]
5+1 2 (3 4) [1 2 3 4]
6+nil true
7+1 9 {:x 1}
8+1 2 3
9+nil (1)
10+[1 2 3]
11+(e3 b0)
12+(2 4 6)
13+(11 22)
14+[7 7]
15+3
new file mode 100644
@@ -0,0 +1,15 @@
1+[1 2 3]
2+(:a1 :b2)
3+7 12
4+[1 2]
5+1 2 (3 4) [1 2 3 4]
6+nil true
7+1 9 {:x 1}
8+1 2 3
9+nil (1)
10+[1 2 3]
11+(e3 b0)
12+(2 4 6)
13+(11 22)
14+[7 7]
15+3
added tests/cli_args.expected +2 -0
new file mode 100644
@@ -0,0 +1,2 @@
1+args: 0 []
2+same: true
new file mode 100644
@@ -0,0 +1,2 @@
1+args: 0 []
2+same: true