fix: general equality (=) for all types, add type predicates & collection fns, test suite docs
- Fix: = now uses cljEqual/cljMultiEqual instead of numeric-only cljNumEq, so (= :a :a), (= "a" "a"), (= [1 2] [1 2]), (= nil nil) etc return true - Fix: ns forms inside unwrapped (do ...) are skipped instead of emitting broken Nim comments inside cljRepr() - Fix: defmacro forms go to defs section, not wrapped in discard cljRepr() - Add 18 type predicates: keyword?, symbol?, string?, number?, integer?, float?, vector?, map?, set?, list?, seq?, coll?, sequential?, fn?, boolean?, true?, false?, some? - Add collection fns: second, ffirst, nfirst, peek, pop - Add keyword/symbol ops: keyword, symbol, name, namespace, key, val - Update eval interpreter = to handle structural collection equality - Add test_single.py + test_vals.clj for Clojure test suite runner - Add docs/en/07 and docs/bg/07 for cross-dialect test suite compatibility - Update README with test suite documentation link
This commit is contained in:
+177
-52
@@ -41,10 +41,12 @@ proc mangleName*(name: string): string =
|
||||
of '.': result.add('_')
|
||||
of '\'': result.add("_QUOTE_")
|
||||
of '&': result.add("_AMP_")
|
||||
of '#': result.add("_HASH")
|
||||
else: result.add(c)
|
||||
|
||||
proc emitExpr*(v: CljVal, indent: int = 0): string
|
||||
proc emitBlock*(items: seq[CljVal], indent: int): string
|
||||
proc emitBlock*(items: seq[CljVal], indent: int, useResult: bool = false): string
|
||||
proc emitQuotedForm*(v: CljVal): string
|
||||
|
||||
proc indentStr(indent: int): string =
|
||||
" ".repeat(indent)
|
||||
@@ -55,7 +57,8 @@ proc runtimeName(op: string): string =
|
||||
of "-": "cljSub"
|
||||
of "*": "cljMul"
|
||||
of "/": "cljDiv"
|
||||
of "=": "cljNumEq"
|
||||
of "=": "cljMultiEqual"
|
||||
of "==": "cljNumEq"
|
||||
of ">": "cljGt"
|
||||
of "<": "cljLt"
|
||||
of ">=": "cljGe"
|
||||
@@ -74,6 +77,9 @@ proc runtimeName(op: string): string =
|
||||
of "even?": "cljEven"
|
||||
of "odd?": "cljOdd"
|
||||
of "first": "cljFirst"
|
||||
of "second": "cljSecond"
|
||||
of "ffirst": "cljFfirst"
|
||||
of "nfirst": "cljNfirst"
|
||||
of "rest": "cljRest"
|
||||
of "next": "cljNext"
|
||||
of "last": "cljLast"
|
||||
@@ -113,6 +119,34 @@ proc runtimeName(op: string): string =
|
||||
of "deref": "cljDeref"
|
||||
of "reset!": "cljReset"
|
||||
of "swap!": "cljSwap"
|
||||
of "zipmap": "cljZipmap"
|
||||
# ---- Type predicates ----
|
||||
of "nil?": "cljIsNilP"
|
||||
of "some?": "cljIsSome"
|
||||
of "keyword?": "cljIsKeyword"
|
||||
of "symbol?": "cljIsSymbol"
|
||||
of "string?": "cljIsString"
|
||||
of "number?": "cljIsNumber"
|
||||
of "integer?": "cljIsInteger"
|
||||
of "float?": "cljIsFloat"
|
||||
of "vector?": "cljIsVector"
|
||||
of "map?": "cljIsMap"
|
||||
of "set?": "cljIsSet"
|
||||
of "list?": "cljIsList"
|
||||
of "seq?": "cljIsSeq"
|
||||
of "coll?": "cljIsColl"
|
||||
of "sequential?": "cljIsSequential"
|
||||
of "fn?": "cljIsFn"
|
||||
of "boolean?": "cljIsBool"
|
||||
of "true?": "cljIsTrueP"
|
||||
of "false?": "cljIsFalseP"
|
||||
# ---- Keyword/Symbol ops ----
|
||||
of "keyword": "cljKeywordFn"
|
||||
of "symbol": "cljSymbolFn"
|
||||
of "name": "cljName"
|
||||
of "namespace": "cljNamespace"
|
||||
of "key": "cljKey"
|
||||
of "val": "cljEntryVal"
|
||||
# ---- File operations ----
|
||||
of "file/read": "cljFileRead"
|
||||
of "file/write": "cljFileWrite"
|
||||
@@ -126,6 +160,8 @@ proc runtimeName(op: string): string =
|
||||
of "git/diff": "cljGitDiff"
|
||||
of "git/log": "cljGitLog"
|
||||
of "disj": "cljDisj"
|
||||
of "peek": "cljPeek"
|
||||
of "pop": "cljPop"
|
||||
of "slurp": "cljFileRead"
|
||||
of "spit": "cljFileWrite"
|
||||
of "read-line": "cljReadLine"
|
||||
@@ -244,6 +280,9 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
# quote
|
||||
if hName == "quote" and form.items.len == 2:
|
||||
return form.items[1]
|
||||
# syntax-quote
|
||||
if hName == "syntax-quote" and form.items.len == 2:
|
||||
return eval(expandSyntaxQuote(form.items[1]))
|
||||
# list
|
||||
if hName == "list":
|
||||
var evaluated: seq[CljVal] = @[]
|
||||
@@ -316,7 +355,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
let name = items[1]
|
||||
if name.kind != ckSymbol:
|
||||
raise newException(EmitterError, "def name must be a symbol")
|
||||
return sp & "let " & mangleName(name.symName) & " = " & emitExpr(items[2], 0)
|
||||
return sp & "let " & mangleName(name.symName) & " = " & emitExpr(items[2], indent)
|
||||
|
||||
of "defn":
|
||||
if items.len < 4:
|
||||
@@ -338,9 +377,6 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
bodyCode = emitExpr(body[0], indent + 1)
|
||||
else:
|
||||
bodyCode = emitBlock(body, indent + 1)
|
||||
# In a proc body, replace "discard " with "result = " so that
|
||||
# if/when branches return their last expression properly.
|
||||
bodyCode = bodyCode.replace("discard ", "result = ")
|
||||
let procName = mangleName(name.symName)
|
||||
let exportMarker = if emitLibMode: "*" else: ""
|
||||
return sp & "proc " & procName & exportMarker & "(" & paramNames.join(", ") & "): CljVal =\n" & bodyCode
|
||||
@@ -425,37 +461,23 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
raise newException(EmitterError, "if requires condition, then, and optional else")
|
||||
let condCode = emitExpr(items[1], 0)
|
||||
let thenCode = emitExpr(items[2], indent + 1)
|
||||
let thenStripped = thenCode.strip()
|
||||
var thenFinal = thenCode
|
||||
if not (thenStripped.startsWith("echo ") or thenStripped.startsWith("discard ") or
|
||||
thenStripped.startsWith("block:") or thenStripped.startsWith("if ") or
|
||||
thenStripped.startsWith("try:") or thenStripped.startsWith("var ") or
|
||||
thenStripped.startsWith("let ") or thenStripped.startsWith("proc ") or
|
||||
thenStripped.contains("\n") or thenStripped.contains(" = ")):
|
||||
thenFinal = indentStr(indent + 1) & "discard " & thenStripped
|
||||
var ifBlock = sp & "if cljIsTruthy(" & condCode & "):\n" & thenFinal
|
||||
var ifBlock = sp & "if cljIsTruthy(" & condCode.strip() & "):\n" & thenCode
|
||||
if items.len == 4:
|
||||
let elseCode = emitExpr(items[3], indent + 1)
|
||||
let elseStripped = elseCode.strip()
|
||||
var elseFinal = elseCode
|
||||
if not (elseStripped.startsWith("echo ") or elseStripped.startsWith("discard ") or
|
||||
elseStripped.startsWith("block:") or elseStripped.startsWith("if ") or
|
||||
elseStripped.startsWith("try:") or elseStripped.startsWith("var ") or
|
||||
elseStripped.startsWith("let ") or elseStripped.startsWith("proc ") or
|
||||
elseStripped.contains("\n") or elseStripped.contains(" = ")):
|
||||
elseFinal = indentStr(indent + 1) & "discard " & elseStripped
|
||||
ifBlock.add("\n" & sp & "else:\n" & elseFinal)
|
||||
ifBlock.add("\n" & sp & "else:\n" & elseCode)
|
||||
return ifBlock
|
||||
|
||||
of "when":
|
||||
if items.len < 3:
|
||||
raise newException(EmitterError, "when requires condition and body")
|
||||
let condCode = emitExpr(items[1], 0)
|
||||
let condCode = emitExpr(items[1], indent)
|
||||
var lines: seq[string] = @[]
|
||||
lines.add(sp & "if cljIsTruthy(" & condCode & "):")
|
||||
lines.add(sp & "if cljIsTruthy(" & condCode.strip() & "):")
|
||||
let body = items[2..^1]
|
||||
for b in body:
|
||||
lines.add(emitExpr(b, indent + 1))
|
||||
lines.add(sp & "else:")
|
||||
lines.add(indentStr(indent + 1) & "cljNil()")
|
||||
return lines.join("\n")
|
||||
|
||||
of "when-let":
|
||||
@@ -602,14 +624,42 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
inc ti
|
||||
var lines: seq[string] = @[]
|
||||
lines.add(sp & "try:")
|
||||
for b in bodyForms:
|
||||
lines.add(emitExpr(b, indent + 1))
|
||||
for i, b in bodyForms:
|
||||
let isLast = (i == bodyForms.len - 1)
|
||||
let code = emitExpr(b, indent + 1)
|
||||
if isLast:
|
||||
lines.add(code)
|
||||
else:
|
||||
let stripped = code.strip()
|
||||
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or
|
||||
stripped.startsWith("block:") or stripped.startsWith("if ") or
|
||||
stripped.startsWith("try:") or stripped.startsWith("var ") or
|
||||
stripped.startsWith("let ") or stripped.startsWith("proc ") or
|
||||
stripped.startsWith("result = ") or stripped.contains("\n") or
|
||||
stripped.contains(" = "):
|
||||
lines.add(code)
|
||||
else:
|
||||
lines.add(indentStr(indent + 1) & "discard " & stripped)
|
||||
for (exType, exVar, exBody) in catchClauses:
|
||||
lines.add(sp & "except " & exType & ":")
|
||||
lines.add(indentStr(indent + 1) & "let " & exVar & " = cljMap(@[cljKeyword(\"message\")], @[cljString(getCurrentExceptionMsg())])")
|
||||
lines.add(indentStr(indent + 1) & "discard cljAssoc(" & exVar & ", cljKeyword(\"type\"), cljString(\"" & exType & "\"))")
|
||||
for eb in exBody:
|
||||
lines.add(emitExpr(eb, indent + 1))
|
||||
for i, eb in exBody:
|
||||
let isLast = (i == exBody.len - 1)
|
||||
let code = emitExpr(eb, indent + 1)
|
||||
if isLast:
|
||||
lines.add(code)
|
||||
else:
|
||||
let stripped = code.strip()
|
||||
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or
|
||||
stripped.startsWith("block:") or stripped.startsWith("if ") or
|
||||
stripped.startsWith("try:") or stripped.startsWith("var ") or
|
||||
stripped.startsWith("let ") or stripped.startsWith("proc ") or
|
||||
stripped.startsWith("result = ") or stripped.contains("\n") or
|
||||
stripped.contains(" = "):
|
||||
lines.add(code)
|
||||
else:
|
||||
lines.add(indentStr(indent + 1) & "discard " & stripped)
|
||||
if finallyBody.len > 0:
|
||||
lines.add(sp & "finally:")
|
||||
for fb in finallyBody:
|
||||
@@ -631,12 +681,12 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
of ckList:
|
||||
var parts: seq[string] = @[]
|
||||
for item in quoted.items:
|
||||
parts.add(emitExpr(item, 0))
|
||||
parts.add(emitQuotedForm(item))
|
||||
return sp & "cljList(@[" & parts.join(", ") & "])"
|
||||
of ckVector:
|
||||
var parts: seq[string] = @[]
|
||||
for item in quoted.items:
|
||||
parts.add(emitExpr(item, 0))
|
||||
parts.add(emitQuotedForm(item))
|
||||
return sp & "cljVector(@[" & parts.join(", ") & "])"
|
||||
else:
|
||||
return emitExpr(quoted, indent)
|
||||
@@ -978,26 +1028,56 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
if rn.len > 0:
|
||||
var argParts: seq[string] = @[]
|
||||
for i in 1..<items.len:
|
||||
argParts.add(emitExpr(items[i], 0))
|
||||
argParts.add(emitExpr(items[i], indent))
|
||||
# Variadic functions take seq[CljVal]
|
||||
let variadic = op in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=",
|
||||
"println", "prn", "print", "str", "pr-str",
|
||||
"concat", "min", "max", "merge", "interleave"]
|
||||
"concat", "min", "max", "merge", "interleave",
|
||||
"zipmap"]
|
||||
var call: string
|
||||
if variadic and argParts.len > 0:
|
||||
call = rn & "(@[" & argParts.join(", ") & "])"
|
||||
else:
|
||||
call = rn & "(" & argParts.join(", ") & ")"
|
||||
if op in ["println", "prn", "print"]:
|
||||
return sp & "discard " & call
|
||||
return sp & call
|
||||
|
||||
# ---- User-defined function call ----
|
||||
var args: seq[string] = @[]
|
||||
for i in 1..<items.len:
|
||||
args.add(emitExpr(items[i], 0))
|
||||
args.add(emitExpr(items[i], indent))
|
||||
return sp & mangleName(op) & "(" & args.join(", ") & ")"
|
||||
|
||||
proc emitQuotedForm*(v: CljVal): string =
|
||||
case v.kind
|
||||
of ckSymbol:
|
||||
return "cljSymbol(\"" & v.symName & "\")"
|
||||
of ckKeyword:
|
||||
return "cljKeyword(\"" & v.kwName & "\")"
|
||||
of ckList:
|
||||
var parts: seq[string] = @[]
|
||||
for item in v.items:
|
||||
parts.add(emitQuotedForm(item))
|
||||
return "cljList(@[" & parts.join(", ") & "])"
|
||||
of ckVector:
|
||||
var parts: seq[string] = @[]
|
||||
for item in v.items:
|
||||
parts.add(emitQuotedForm(item))
|
||||
return "cljVector(@[" & parts.join(", ") & "])"
|
||||
of ckMap:
|
||||
var keyParts: seq[string] = @[]
|
||||
var valParts: seq[string] = @[]
|
||||
for i in 0..<v.mapKeys.len:
|
||||
keyParts.add(emitQuotedForm(v.mapKeys[i]))
|
||||
valParts.add(emitQuotedForm(v.mapVals[i]))
|
||||
return "cljMap(@[" & keyParts.join(", ") & "], @[" & valParts.join(", ") & "])"
|
||||
of ckSet:
|
||||
var parts: seq[string] = @[]
|
||||
for item in v.setItems:
|
||||
parts.add(emitQuotedForm(item))
|
||||
return "cljSet(@[" & parts.join(", ") & "])"
|
||||
else:
|
||||
return emitExpr(v, 0)
|
||||
|
||||
proc emitExpr*(v: CljVal, indent: int = 0): string =
|
||||
let sp = indentStr(indent)
|
||||
case v.kind
|
||||
@@ -1031,7 +1111,7 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
|
||||
of ckVector:
|
||||
var parts: seq[string] = @[]
|
||||
for item in v.items:
|
||||
parts.add(emitExpr(item, 0))
|
||||
parts.add(emitExpr(item, indent))
|
||||
return sp & "cljVector(@[" & parts.join(", ") & "])"
|
||||
of ckMap:
|
||||
if v.mapKeys.len == 0:
|
||||
@@ -1039,13 +1119,13 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
|
||||
var keyParts: seq[string] = @[]
|
||||
var valParts: seq[string] = @[]
|
||||
for i in 0..<v.mapKeys.len:
|
||||
keyParts.add(emitExpr(v.mapKeys[i], 0))
|
||||
valParts.add(emitExpr(v.mapVals[i], 0))
|
||||
keyParts.add(emitExpr(v.mapKeys[i], indent))
|
||||
valParts.add(emitExpr(v.mapVals[i], indent))
|
||||
return sp & "cljMap(@[" & keyParts.join(", ") & "], @[" & valParts.join(", ") & "])"
|
||||
of ckSet:
|
||||
var parts: seq[string] = @[]
|
||||
for item in v.setItems:
|
||||
parts.add(emitExpr(item, 0))
|
||||
parts.add(emitExpr(item, indent))
|
||||
return sp & "cljSet(@[" & parts.join(", ") & "])"
|
||||
of ckFn:
|
||||
return sp & "cljFn(proc(args: seq[CljVal]): CljVal = discard cljNil())"
|
||||
@@ -1056,12 +1136,39 @@ proc emitExpr*(v: CljVal, indent: int = 0): string =
|
||||
of ckAgent:
|
||||
return sp & "cljAgent(" & emitExpr(v.agentVal, 0) & ")"
|
||||
|
||||
proc emitBlock(items: seq[CljVal], indent: int): string =
|
||||
proc emitBlock(items: seq[CljVal], indent: int, useResult: bool = false): string =
|
||||
if items.len == 0:
|
||||
return indentStr(indent) & "discard cljNil()"
|
||||
var lines: seq[string] = @[]
|
||||
for item in items:
|
||||
lines.add(emitExpr(item, indent))
|
||||
for i, item in items:
|
||||
var code = emitExpr(item, indent)
|
||||
let stripped = code.strip()
|
||||
if i < items.len - 1:
|
||||
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or
|
||||
stripped.startsWith("var ") or stripped.startsWith("let ") or
|
||||
stripped.startsWith("proc ") or stripped.startsWith("result = "):
|
||||
discard
|
||||
else:
|
||||
# Prepend discard before the first non-empty line
|
||||
let firstNl = code.find("\n")
|
||||
if firstNl == -1:
|
||||
code = indentStr(indent) & "discard " & stripped
|
||||
else:
|
||||
let firstLine = code[0..<firstNl].strip()
|
||||
let rest = code[firstNl..^1]
|
||||
code = indentStr(indent) & "discard " & firstLine & rest
|
||||
elif useResult:
|
||||
if stripped.startsWith("discard "):
|
||||
code = indentStr(indent) & "result = " & stripped[8..^1]
|
||||
elif not stripped.startsWith("result = "):
|
||||
let firstNl = code.find("\n")
|
||||
if firstNl == -1:
|
||||
code = indentStr(indent) & "result = " & stripped
|
||||
else:
|
||||
let firstLine = code[0..<firstNl].strip()
|
||||
let rest = code[firstNl..^1]
|
||||
code = indentStr(indent) & "result = " & firstLine & rest
|
||||
lines.add(code)
|
||||
return lines.join("\n")
|
||||
|
||||
proc emitProgramInternal(forms: seq[CljVal]): string =
|
||||
@@ -1074,24 +1181,42 @@ proc emitProgramInternal(forms: seq[CljVal]): string =
|
||||
var defs: seq[string] = @[]
|
||||
var mainForms: seq[string] = @[]
|
||||
|
||||
for i, form in forms:
|
||||
let isDef = form.kind == ckList and form.items.len > 0 and
|
||||
form.items[0].kind == ckSymbol and
|
||||
form.items[0].symName in ["def", "defn", "defn-"]
|
||||
if isDef:
|
||||
proc processForm(i: int, form: CljVal, isLast: bool) =
|
||||
let headSym = form.kind == ckList and form.items.len > 0 and
|
||||
form.items[0].kind == ckSymbol
|
||||
let headName = if headSym: form.items[0].symName else: ""
|
||||
let isDef = headSym and headName in ["def", "defn", "defn-"]
|
||||
let isMacro = headSym and headName in ["defmacro"]
|
||||
let isNs = headSym and headName == "ns"
|
||||
if isNs:
|
||||
discard
|
||||
elif isDef or isMacro:
|
||||
defs.add(emitExpr(form, 0))
|
||||
else:
|
||||
let code = emitExpr(form, 2)
|
||||
let stripped = code.strip()
|
||||
if stripped.startsWith("echo ") or stripped.startsWith("discard ") or
|
||||
stripped.startsWith("block:") or stripped.startsWith("if ") or
|
||||
stripped.startsWith("try:") or stripped.startsWith("var "):
|
||||
stripped.startsWith("var "):
|
||||
mainForms.add(code)
|
||||
elif i == forms.len - 1:
|
||||
elif isLast:
|
||||
mainForms.add(indentStr(2) & "echo cljRepr(" & stripped & ")")
|
||||
else:
|
||||
mainForms.add(indentStr(2) & "discard cljRepr(" & stripped & ")")
|
||||
|
||||
proc unwrapAndProcess(i: int, form: CljVal, isLast: bool) =
|
||||
# Unwrap top-level (do ...) forms so each sub-form is processed individually
|
||||
if form.kind == ckList and form.items.len > 0 and
|
||||
form.items[0].kind == ckSymbol and form.items[0].symName == "do":
|
||||
let subForms = form.items[1..^1]
|
||||
for j, subForm in subForms:
|
||||
let subLast = isLast and (j == subForms.len - 1)
|
||||
unwrapAndProcess(j, subForm, subLast)
|
||||
else:
|
||||
processForm(i, form, isLast)
|
||||
|
||||
for i, form in forms:
|
||||
unwrapAndProcess(i, form, i == forms.len - 1)
|
||||
|
||||
var lines = headerLines
|
||||
# Add collected Nim imports
|
||||
for imp in requiredImports:
|
||||
|
||||
Reference in New Issue
Block a user