Initial commit
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
# Clojure/Nim Compiler
|
||||
import os, strutils, osproc
|
||||
import reader, emitter, types
|
||||
|
||||
proc compileFile*(inputPath: string, outputPath: string) =
|
||||
let source = readFile(inputPath)
|
||||
let forms = reader.readAll(source)
|
||||
|
||||
if forms.len == 0:
|
||||
stderr.writeLine("Error: No forms found in " & inputPath)
|
||||
quit(1)
|
||||
|
||||
let nimCode = emitter.emitProgram(forms)
|
||||
writeFile(outputPath, nimCode)
|
||||
echo "Generated: ", outputPath
|
||||
|
||||
proc runFile*(inputPath: string) =
|
||||
let tmpDir = getTempDir()
|
||||
let baseName = inputPath.splitFile.name
|
||||
let nimPath = tmpDir / baseName & "_generated.nim"
|
||||
let binPath = tmpDir / baseName & "_generated"
|
||||
|
||||
compileFile(inputPath, nimPath)
|
||||
|
||||
# Compile generated Nim code
|
||||
let compileCmd = "nim c -o:" & binPath & " " & nimPath
|
||||
echo "Compiling: ", compileCmd
|
||||
let compileResult = execCmd(compileCmd)
|
||||
if compileResult != 0:
|
||||
stderr.writeLine("Compilation failed")
|
||||
quit(1)
|
||||
|
||||
# Run
|
||||
echo "Running: ", binPath
|
||||
let runResult = execCmd(binPath)
|
||||
if runResult != 0:
|
||||
stderr.writeLine("Execution failed")
|
||||
quit(1)
|
||||
|
||||
proc main() =
|
||||
let args = commandLineParams()
|
||||
|
||||
if args.len == 0:
|
||||
echo "Clojure/Nim Compiler"
|
||||
echo "Usage:"
|
||||
echo " cljnim compile <file.clj> [output.nim] Compile to Nim"
|
||||
echo " cljnim run <file.clj> Compile and run"
|
||||
echo " cljnim read <file.clj> Parse and print AST"
|
||||
quit(0)
|
||||
|
||||
let cmd = args[0]
|
||||
|
||||
case cmd
|
||||
of "compile":
|
||||
if args.len < 2:
|
||||
stderr.writeLine("Error: Missing input file")
|
||||
quit(1)
|
||||
let inputPath = args[1]
|
||||
let outputPath = if args.len >= 3: args[2] else: inputPath.changeFileExt("nim")
|
||||
compileFile(inputPath, outputPath)
|
||||
|
||||
of "run":
|
||||
if args.len < 2:
|
||||
stderr.writeLine("Error: Missing input file")
|
||||
quit(1)
|
||||
runFile(args[1])
|
||||
|
||||
of "read":
|
||||
if args.len < 2:
|
||||
stderr.writeLine("Error: Missing input file")
|
||||
quit(1)
|
||||
let source = readFile(args[1])
|
||||
let forms = reader.readAll(source)
|
||||
for form in forms:
|
||||
echo $form
|
||||
|
||||
else:
|
||||
stderr.writeLine("Unknown command: " & cmd)
|
||||
quit(1)
|
||||
|
||||
when isMainModule:
|
||||
main()
|
||||
+272
@@ -0,0 +1,272 @@
|
||||
# Clojure → Nim Emitter
|
||||
import strutils, sequtils
|
||||
import types
|
||||
|
||||
type
|
||||
EmitterError* = object of CatchableError
|
||||
|
||||
proc mangleName*(name: string): string =
|
||||
## Convert Clojure symbol to valid Nim identifier
|
||||
result = ""
|
||||
for c in name:
|
||||
case c
|
||||
of '-': result.add('_')
|
||||
of '?': result.add("_Q")
|
||||
of '!': result.add("_B")
|
||||
of '*': result.add("_STAR")
|
||||
of '+': result.add("_PLUS")
|
||||
of '/': result.add("_DIV")
|
||||
of '=': result.add("_EQ")
|
||||
of '>': result.add("_GT")
|
||||
of '<': result.add("_LT")
|
||||
of '.': result.add('_')
|
||||
of '\'': result.add("_QUOTE")
|
||||
else: result.add(c)
|
||||
|
||||
proc emitExpr*(v: CljVal, indent: int = 0): string
|
||||
|
||||
proc emitBlock(items: seq[CljVal], indent: int): string =
|
||||
if items.len == 0:
|
||||
return "discard"
|
||||
var lines: seq[string] = @[]
|
||||
for i, item in items:
|
||||
lines.add(emitExpr(item, indent))
|
||||
return lines.join("\n")
|
||||
|
||||
proc emitSpecialForm(items: seq[CljVal], indent: int): string =
|
||||
let spaces = " ".repeat(indent)
|
||||
let head = items[0]
|
||||
if head.kind != ckSymbol:
|
||||
raise newException(EmitterError, "List head must be a symbol, got " & $head.kind)
|
||||
let op = head.symName
|
||||
|
||||
case op
|
||||
of "+", "-", "*", "/", "=", ">", "<":
|
||||
if items.len < 3:
|
||||
raise newException(EmitterError, op & " requires at least 2 arguments")
|
||||
var parts: seq[string] = @[]
|
||||
let nimOp = case op
|
||||
of "=": "=="
|
||||
else: op
|
||||
for i in 1..<items.len:
|
||||
parts.add(emitExpr(items[i], indent))
|
||||
return spaces & parts.join(" " & nimOp & " ")
|
||||
|
||||
of "println":
|
||||
if items.len < 2:
|
||||
raise newException(EmitterError, "println requires at least 1 argument")
|
||||
var parts: seq[string] = @[]
|
||||
for i in 1..<items.len:
|
||||
parts.add(emitExpr(items[i], indent))
|
||||
return spaces & "echo " & parts.join(", ")
|
||||
|
||||
of "def":
|
||||
if items.len != 3:
|
||||
raise newException(EmitterError, "def requires exactly 2 arguments")
|
||||
let name = items[1]
|
||||
if name.kind != ckSymbol:
|
||||
raise newException(EmitterError, "def name must be a symbol")
|
||||
return spaces & "let " & mangleName(name.symName) & " = " & emitExpr(items[2], indent)
|
||||
|
||||
of "defn":
|
||||
if items.len < 4:
|
||||
raise newException(EmitterError, "defn requires name, params, and body")
|
||||
let name = items[1]
|
||||
if name.kind != ckSymbol:
|
||||
raise newException(EmitterError, "defn name must be a symbol")
|
||||
let params = items[2]
|
||||
if params.kind != ckVector:
|
||||
raise newException(EmitterError, "defn params must be a vector")
|
||||
var paramNames: seq[string] = @[]
|
||||
for p in params.items:
|
||||
if p.kind != ckSymbol:
|
||||
raise newException(EmitterError, "defn params must be symbols")
|
||||
paramNames.add(mangleName(p.symName) & ": auto")
|
||||
let body = items[3..^1]
|
||||
var bodyCode = ""
|
||||
if body.len == 1:
|
||||
bodyCode = emitExpr(body[0], indent + 1)
|
||||
else:
|
||||
bodyCode = emitBlock(body, indent + 1)
|
||||
let procName = mangleName(name.symName)
|
||||
var typedParams: seq[string] = @[]
|
||||
for p in params.items:
|
||||
typedParams.add(mangleName(p.symName) & ": int")
|
||||
if typedParams.len > 0:
|
||||
return spaces & "proc " & procName & "(" & typedParams.join(", ") & "): int =\n" & bodyCode
|
||||
else:
|
||||
return spaces & "proc " & procName & "(): int =\n" & bodyCode
|
||||
|
||||
of "fn":
|
||||
if items.len < 3:
|
||||
raise newException(EmitterError, "fn requires params and body")
|
||||
let params = items[1]
|
||||
if params.kind != ckVector:
|
||||
raise newException(EmitterError, "fn params must be a vector")
|
||||
var paramNames: seq[string] = @[]
|
||||
for p in params.items:
|
||||
if p.kind != ckSymbol:
|
||||
raise newException(EmitterError, "fn params must be symbols")
|
||||
paramNames.add(mangleName(p.symName) & ": auto")
|
||||
let body = items[2..^1]
|
||||
var bodyCode = ""
|
||||
if body.len == 1:
|
||||
bodyCode = emitExpr(body[0], indent + 1)
|
||||
else:
|
||||
bodyCode = emitBlock(body, indent + 1)
|
||||
var typedParams: seq[string] = @[]
|
||||
for p in params.items:
|
||||
typedParams.add(mangleName(p.symName) & ": int")
|
||||
if typedParams.len > 0:
|
||||
return spaces & "proc(" & typedParams.join(", ") & "): int =\n" & bodyCode
|
||||
else:
|
||||
return spaces & "proc(): int =\n" & bodyCode
|
||||
|
||||
of "let":
|
||||
if items.len < 3:
|
||||
raise newException(EmitterError, "let requires bindings and body")
|
||||
let bindings = items[1]
|
||||
if bindings.kind != ckVector:
|
||||
raise newException(EmitterError, "let bindings must be a vector")
|
||||
if bindings.items.len mod 2 != 0:
|
||||
raise newException(EmitterError, "let bindings must have even number of elements")
|
||||
var lines: seq[string] = @[]
|
||||
lines.add("block:")
|
||||
var bindLines: seq[string] = @[]
|
||||
var i = 0
|
||||
while i < bindings.items.len:
|
||||
let name = bindings.items[i]
|
||||
let val = bindings.items[i+1]
|
||||
if name.kind != ckSymbol:
|
||||
raise newException(EmitterError, "let binding name must be a symbol")
|
||||
bindLines.add(mangleName(name.symName) & " = " & emitExpr(val, 0).strip())
|
||||
i += 2
|
||||
lines.add(" ".repeat(indent + 1) & "let")
|
||||
for bl in bindLines:
|
||||
lines.add(" ".repeat(indent + 2) & bl)
|
||||
let body = items[2..^1]
|
||||
for j, b in body:
|
||||
let bcode = emitExpr(b, indent + 1).strip()
|
||||
if j == body.len - 1:
|
||||
lines.add(" ".repeat(indent + 1) & bcode)
|
||||
else:
|
||||
if bcode.startsWith("echo ") or bcode.startsWith("discard "):
|
||||
lines.add(" ".repeat(indent + 1) & bcode)
|
||||
else:
|
||||
lines.add(" ".repeat(indent + 1) & "discard " & bcode)
|
||||
return spaces & lines.join("\n")
|
||||
|
||||
of "if":
|
||||
if items.len < 3 or items.len > 4:
|
||||
raise newException(EmitterError, "if requires condition, then, and optional else")
|
||||
let condCode = emitExpr(items[1], indent)
|
||||
let thenCode = emitExpr(items[2], indent + 1)
|
||||
var result = spaces & "if " & condCode & ":\n" & " ".repeat(indent + 1) & thenCode
|
||||
if items.len == 4:
|
||||
let elseCode = emitExpr(items[3], indent + 1)
|
||||
result.add("\n" & spaces & "else:\n" & " ".repeat(indent + 1) & elseCode)
|
||||
return result
|
||||
|
||||
of "do":
|
||||
if items.len < 2:
|
||||
return spaces & "discard"
|
||||
return emitBlock(items[1..^1], indent)
|
||||
|
||||
of "quote":
|
||||
if items.len != 2:
|
||||
raise newException(EmitterError, "quote requires exactly 1 argument")
|
||||
let quoted = items[1]
|
||||
case quoted.kind
|
||||
of ckSymbol:
|
||||
return spaces & "cljSymbol(\"" & quoted.symName & "\")"
|
||||
of ckList:
|
||||
var parts: seq[string] = @[]
|
||||
for item in quoted.items:
|
||||
parts.add(emitExpr(item, indent))
|
||||
return spaces & "cljList(@[" & parts.join(", ") & "])"
|
||||
of ckVector:
|
||||
var parts: seq[string] = @[]
|
||||
for item in quoted.items:
|
||||
parts.add(emitExpr(item, indent))
|
||||
return spaces & "cljVector(@[" & parts.join(", ") & "])"
|
||||
else:
|
||||
return emitExpr(quoted, indent)
|
||||
|
||||
else:
|
||||
# Function call
|
||||
var args: seq[string] = @[]
|
||||
for i in 1..<items.len:
|
||||
args.add(emitExpr(items[i], indent))
|
||||
return spaces & mangleName(op) & "(" & args.join(", ") & ")"
|
||||
|
||||
proc emitExpr*(v: CljVal, indent: int = 0): string =
|
||||
let spaces = " ".repeat(indent)
|
||||
case v.kind
|
||||
of ckNil:
|
||||
return spaces & "nil"
|
||||
of ckBool:
|
||||
return spaces & $v.boolVal
|
||||
of ckInt:
|
||||
return spaces & $v.intVal
|
||||
of ckFloat:
|
||||
return spaces & $v.floatVal
|
||||
of ckString:
|
||||
return spaces & "\"" & v.strVal & "\""
|
||||
of ckKeyword:
|
||||
return spaces & "cljKeyword(\"" & v.kwName & "\")"
|
||||
of ckSymbol:
|
||||
return spaces & mangleName(v.symName)
|
||||
of ckList:
|
||||
if v.items.len == 0:
|
||||
return spaces & "cljList(@[])"
|
||||
return emitSpecialForm(v.items, indent)
|
||||
of ckVector:
|
||||
var parts: seq[string] = @[]
|
||||
for item in v.items:
|
||||
parts.add(emitExpr(item, indent))
|
||||
return spaces & "@[" & parts.join(", ") & "]"
|
||||
of ckMap:
|
||||
raise newException(EmitterError, "Maps not yet supported in emitter")
|
||||
|
||||
proc emitProgram*(forms: seq[CljVal]): string =
|
||||
var headerLines: seq[string] = @[
|
||||
"# Generated by Clojure/Nim",
|
||||
"import strutils, sequtils",
|
||||
"",
|
||||
"# Runtime helpers",
|
||||
"proc cljSymbol(name: string): auto = name",
|
||||
"proc cljKeyword(name: string): auto = \":\" & name",
|
||||
"proc cljList(items: seq[auto]): auto = items",
|
||||
"proc cljVector(items: seq[auto]): auto = items",
|
||||
"",
|
||||
]
|
||||
|
||||
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"]
|
||||
if isDef:
|
||||
defs.add(emitExpr(form, 0))
|
||||
else:
|
||||
let code = emitExpr(form, 2).strip()
|
||||
if code.startsWith("echo ") or code.startsWith("discard ") or code.startsWith("block:"):
|
||||
mainForms.add(code)
|
||||
elif i == forms.len - 1:
|
||||
# Last form - echo its result
|
||||
mainForms.add("echo " & code)
|
||||
else:
|
||||
mainForms.add("discard " & code)
|
||||
|
||||
var lines = headerLines
|
||||
lines.add(defs)
|
||||
|
||||
if mainForms.len > 0:
|
||||
lines.add("")
|
||||
lines.add("when isMainModule:")
|
||||
for form in mainForms:
|
||||
lines.add(" " & form)
|
||||
|
||||
return lines.join("\n") & "\n"
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
# Clojure Reader (EDN subset)
|
||||
import strutils, sequtils
|
||||
import types
|
||||
|
||||
type
|
||||
ReaderError* = object of CatchableError
|
||||
|
||||
proc skipWhitespaceAndComments(s: string, i: var int) =
|
||||
while i < s.len:
|
||||
let c = s[i]
|
||||
if c in Whitespace:
|
||||
inc i
|
||||
elif c == ';':
|
||||
while i < s.len and s[i] != '\n':
|
||||
inc i
|
||||
else:
|
||||
break
|
||||
|
||||
proc isSymChar(c: char): bool =
|
||||
c in Letters or c in Digits or c in {'+', '-', '*', '/', '_', '?', '!', '=', '<', '>', '.', '\'', '#', '%', '&'}
|
||||
|
||||
proc readStringTok(s: string, i: var int): string =
|
||||
inc i # skip opening quote
|
||||
var resultStr = ""
|
||||
while i < s.len and s[i] != '"':
|
||||
if s[i] == '\\' and i + 1 < s.len:
|
||||
inc i
|
||||
case s[i]
|
||||
of 'n': resultStr.add('\n')
|
||||
of 't': resultStr.add('\t')
|
||||
of 'r': resultStr.add('\r')
|
||||
of '\\': resultStr.add('\\')
|
||||
of '"': resultStr.add('"')
|
||||
else: resultStr.add(s[i])
|
||||
else:
|
||||
resultStr.add(s[i])
|
||||
inc i
|
||||
if i >= s.len:
|
||||
raise newException(ReaderError, "Unterminated string")
|
||||
inc i # skip closing quote
|
||||
return resultStr
|
||||
|
||||
proc readNumberOrSym(s: string, i: var int): string =
|
||||
var start = i
|
||||
# handle negative sign or standalone operators
|
||||
if s[i] == '-' or s[i] == '+':
|
||||
if i + 1 < s.len and s[i+1] in Digits:
|
||||
inc i
|
||||
while i < s.len and s[i] in Digits:
|
||||
inc i
|
||||
if i < s.len and s[i] == '.':
|
||||
inc i
|
||||
while i < s.len and s[i] in Digits:
|
||||
inc i
|
||||
return s[start..<i]
|
||||
else:
|
||||
# it's a symbol like - or +
|
||||
inc i
|
||||
while i < s.len and isSymChar(s[i]):
|
||||
inc i
|
||||
return s[start..<i]
|
||||
elif s[i] in Digits:
|
||||
while i < s.len and s[i] in Digits:
|
||||
inc i
|
||||
if i < s.len and s[i] == '.':
|
||||
inc i
|
||||
while i < s.len and s[i] in Digits:
|
||||
inc i
|
||||
return s[start..<i]
|
||||
else:
|
||||
while i < s.len and isSymChar(s[i]):
|
||||
inc i
|
||||
return s[start..<i]
|
||||
|
||||
proc readAtom(s: string, i: var int): CljVal =
|
||||
let tok = readNumberOrSym(s, i)
|
||||
if tok.len == 0:
|
||||
raise newException(ReaderError, "Empty token at position " & $i)
|
||||
|
||||
# nil, true, false
|
||||
if tok == "nil": return cljNil()
|
||||
if tok == "true": return cljBool(true)
|
||||
if tok == "false": return cljBool(false)
|
||||
|
||||
# keyword
|
||||
if tok[0] == ':':
|
||||
return cljKeyword(tok[1..^1])
|
||||
|
||||
# number
|
||||
var isFloat = false
|
||||
var isNumber = true
|
||||
var startIdx = 0
|
||||
if tok[0] == '-' or tok[0] == '+':
|
||||
if tok.len == 1:
|
||||
isNumber = false
|
||||
else:
|
||||
startIdx = 1
|
||||
for j in startIdx..<tok.len:
|
||||
if tok[j] == '.':
|
||||
if isFloat:
|
||||
isNumber = false
|
||||
break
|
||||
isFloat = true
|
||||
elif tok[j] notin Digits:
|
||||
isNumber = false
|
||||
break
|
||||
|
||||
if isNumber and tok.len > startIdx:
|
||||
if isFloat:
|
||||
return cljFloat(parseFloat(tok))
|
||||
else:
|
||||
return cljInt(parseInt(tok).int64)
|
||||
|
||||
# symbol
|
||||
return cljSymbol(tok)
|
||||
|
||||
proc readForm(s: string, i: var int): CljVal
|
||||
|
||||
proc readList(s: string, i: var int): CljVal =
|
||||
inc i # skip '('
|
||||
var items: seq[CljVal] = @[]
|
||||
while true:
|
||||
skipWhitespaceAndComments(s, i)
|
||||
if i >= s.len:
|
||||
raise newException(ReaderError, "Unterminated list")
|
||||
if s[i] == ')':
|
||||
inc i
|
||||
break
|
||||
items.add(readForm(s, i))
|
||||
return cljList(items)
|
||||
|
||||
proc readVector(s: string, i: var int): CljVal =
|
||||
inc i # skip '['
|
||||
var items: seq[CljVal] = @[]
|
||||
while true:
|
||||
skipWhitespaceAndComments(s, i)
|
||||
if i >= s.len:
|
||||
raise newException(ReaderError, "Unterminated vector")
|
||||
if s[i] == ']':
|
||||
inc i
|
||||
break
|
||||
items.add(readForm(s, i))
|
||||
return cljVector(items)
|
||||
|
||||
proc readForm(s: string, i: var int): CljVal =
|
||||
skipWhitespaceAndComments(s, i)
|
||||
if i >= s.len:
|
||||
raise newException(ReaderError, "Unexpected end of input")
|
||||
|
||||
let c = s[i]
|
||||
case c
|
||||
of '(':
|
||||
return readList(s, i)
|
||||
of '[':
|
||||
return readVector(s, i)
|
||||
of '"':
|
||||
return cljString(readStringTok(s, i))
|
||||
of '\'':
|
||||
inc i # skip quote
|
||||
let form = readForm(s, i)
|
||||
return cljList(@[cljSymbol("quote"), form])
|
||||
else:
|
||||
return readAtom(s, i)
|
||||
|
||||
proc readOne*(s: string, i: var int): CljVal =
|
||||
skipWhitespaceAndComments(s, i)
|
||||
if i >= s.len:
|
||||
return nil
|
||||
return readForm(s, i)
|
||||
|
||||
proc read*(s: string): CljVal =
|
||||
var i = 0
|
||||
let result = readForm(s, i)
|
||||
skipWhitespaceAndComments(s, i)
|
||||
if i < s.len:
|
||||
raise newException(ReaderError, "Extra input after form: " & s[i..^1])
|
||||
return result
|
||||
|
||||
proc readAll*(s: string): seq[CljVal] =
|
||||
var i = 0
|
||||
var forms: seq[CljVal] = @[]
|
||||
while true:
|
||||
skipWhitespaceAndComments(s, i)
|
||||
if i >= s.len:
|
||||
break
|
||||
forms.add(readForm(s, i))
|
||||
return forms
|
||||
@@ -0,0 +1,49 @@
|
||||
# Clojure Value Types (Nim Runtime)
|
||||
import sequtils, strutils
|
||||
|
||||
type
|
||||
CljKind* = enum
|
||||
ckNil, ckBool, ckInt, ckFloat, ckString, ckKeyword, ckSymbol,
|
||||
ckList, ckVector, ckMap
|
||||
|
||||
CljVal* = ref object
|
||||
case kind*: CljKind
|
||||
of ckNil: discard
|
||||
of ckBool: boolVal*: bool
|
||||
of ckInt: intVal*: int64
|
||||
of ckFloat: floatVal*: float64
|
||||
of ckString: strVal*: string
|
||||
of ckKeyword: kwName*: string
|
||||
of ckSymbol: symName*: string
|
||||
of ckList, ckVector: items*: seq[CljVal]
|
||||
of ckMap: pairs*: seq[(CljVal, CljVal)]
|
||||
|
||||
# Constructors
|
||||
proc cljNil*(): CljVal = CljVal(kind: ckNil)
|
||||
proc cljBool*(v: bool): CljVal = CljVal(kind: ckBool, boolVal: v)
|
||||
proc cljInt*(v: int64): CljVal = CljVal(kind: ckInt, intVal: v)
|
||||
proc cljFloat*(v: float64): CljVal = CljVal(kind: ckFloat, floatVal: v)
|
||||
proc cljString*(v: string): CljVal = CljVal(kind: ckString, strVal: v)
|
||||
proc cljKeyword*(v: string): CljVal = CljVal(kind: ckKeyword, kwName: v)
|
||||
proc cljSymbol*(v: string): CljVal = CljVal(kind: ckSymbol, symName: v)
|
||||
proc cljList*(items: seq[CljVal]): CljVal = CljVal(kind: ckList, items: items)
|
||||
proc cljVector*(items: seq[CljVal]): CljVal = CljVal(kind: ckVector, items: items)
|
||||
proc cljMap*(pairs: seq[(CljVal, CljVal)]): CljVal = CljVal(kind: ckMap, pairs: pairs)
|
||||
|
||||
# String representation (for debugging)
|
||||
proc `$`*(v: CljVal): string =
|
||||
case v.kind
|
||||
of ckNil: "nil"
|
||||
of ckBool: $v.boolVal
|
||||
of ckInt: $v.intVal
|
||||
of ckFloat: $v.floatVal
|
||||
of ckString: "\"" & v.strVal & "\""
|
||||
of ckKeyword: ":" & v.kwName
|
||||
of ckSymbol: v.symName
|
||||
of ckList: "(" & v.items.mapIt($it).join(" ") & ")"
|
||||
of ckVector: "[" & v.items.mapIt($it).join(" ") & "]"
|
||||
of ckMap:
|
||||
var parts: seq[string] = @[]
|
||||
for (k, v2) in v.pairs:
|
||||
parts.add($k & " " & $v2)
|
||||
"{" & parts.join(", ") & "}"
|
||||
Reference in New Issue
Block a user