diff --git a/PLAN.md b/PLAN.md index 27432f4..14c1947 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,30 +1,26 @@ # Clojure/Nim — Test Suite Compliance Plan -## Current Status: 233/233 (100%) +## Current Status: 230/233 (98.7%) | Component | Pass | Total | % | |---|---|---|---| | clojure.string | 8 | 8 | 100% | -| clojure.core | 225 | 225 | 100% | -| **Total** | **233** | **233** | **100%** | +| clojure.core | 222 | 225 | 98.7% | +| **Total** | **230** | **233** | **98.7%** | -## Progress This Session (2026-05-11) +## Unit Tests: 78/80 pass (97.5%) -**Started: 210/233 (90%) → Current: 233/233 (100%) — +23 tests** +- `emit symbol` — returns `cljSymbol(...)` instead of raw name +- `emit mangled symbol` — same issue -### Key Changes Made +## 3 Remaining Compliance Failures -- **Reader**: Removed debug stderr lines causing crashes in readList -- **Reader**: Added `##Inf`, `##-Inf`, `##NaN` dispatch macro handling (was returning nil, causing arity mismatches in `are` macro) -- **Reader**: Added `#?@` splice-unwrap support in `readAll` (top-level splicing) -- **Reader**: Reader conditionals now prefer `:clj` platform branch, falling back to `:default` -- **Reader**: Fixed hex literal overflow for `-0x8000000000000000` (returns int64 min) -- **Macros**: `doseq` macro now handles `:when`, `:let`, `:while` keywords -- **Macros**: `(when false)` in doseq while-loop has body (was invalid) -- **Macros**: `expandSyntaxQuote` handles `unquote-splicing` inside collections (returns form directly for concat splicing) - -### Pre-existing Issues (not from this session) -- Emitter test: `emit symbol` returns `cljSymbol(...)` instead of raw name -- Emitter test: `emit mangled symbol` similar issue -- Emitter test: `emit cond` generates wrong Nim code +| # | Test | Error | Root Cause | +|---|---|---|---| +| 1 | `not_eq` | `undeclared 'eq_SLASH_tests'` | Cross-namespace: `eq/tests` fn from `clojure.core-test.eq` not loaded | +| 2 | `add_watch` | `undeclared 'testvar_a'` | `(def testvar-a 0)` inside deftest body — Nim requires var decl before use in closures | +| 3 | `remove_watch` | `undeclared 'watchable'` | Same — `(def watchable 0)` inside deftest body, referenced in closures | +All 3 require deeper architectural changes: +- Multi-file compilation or inline required namespaces +- Top-level def hoisting across closure boundaries diff --git a/lib/cljnim_runtime.nim b/lib/cljnim_runtime.nim index cc31763..e942b70 100644 --- a/lib/cljnim_runtime.nim +++ b/lib/cljnim_runtime.nim @@ -1338,6 +1338,10 @@ proc cljGet*(m: CljVal, key: CljVal): CljVal = else: cljNil() else: cljNil() +proc cljGet*(args: seq[CljVal]): CljVal = + if args.len < 2: return cljNil() + cljGet(args[0], args[1]) + proc cljGetDefault*(m: CljVal, key: CljVal, default: CljVal): CljVal = if m.isNil: return default case m.kind @@ -1352,6 +1356,13 @@ proc cljExInfo*(msg: CljVal, data: CljVal): CljVal = ex.exData = data raise ex +proc cljExInfo*(args: seq[CljVal]): CljVal = + var msg = if args.len > 0: args[0] else: cljString("") + var data = if args.len > 1: args[1] else: cljNil() + var ex = newException(ExInfo, cljStr(msg)) + ex.exData = data + raise ex + proc cljExData*(ex: CljVal): CljVal = if ex.isNil: return cljNil() case ex.kind @@ -1403,10 +1414,18 @@ proc cljAssoc*(m: CljVal, key: CljVal, val: CljVal): CljVal = else: raise newException(CatchableError, "assoc expects a map or vector") +proc cljAssoc*(args: seq[CljVal]): CljVal = + if args.len < 3: raise newException(CatchableError, "assoc requires 3 arguments") + cljAssoc(args[0], args[1], args[2]) + proc cljDissoc*(m: CljVal, key: CljVal): CljVal = if m.isNil or m.kind != ckMap: return cljMap(@[], @[]) CljVal(kind: ckMap, mapData: pmapDissoc(m.mapData, key, hash(key), cljEq)) +proc cljDissoc*(args: seq[CljVal]): CljVal = + if args.len < 2: return cljMap(@[], @[]) + cljDissoc(args[0], args[1]) + proc cljContains*(m: CljVal, key: CljVal): CljVal = if m.isNil: return cljBool(false) case m.kind @@ -1523,6 +1542,10 @@ proc cljTransduce*(xform: CljVal, f: CljVal, init: CljVal, coll: CljVal): CljVal # Fallback: just return init return init +proc cljTransduce*(args: seq[CljVal]): CljVal = + if args.len < 4: return cljNil() + cljTransduce(args[0], args[1], args[2], args[3]) + proc cljMapv*(f: proc(args: seq[CljVal]): CljVal, coll: CljVal): CljVal = if coll.isNil: return cljVector(@[]) case coll.kind @@ -2246,3 +2269,90 @@ proc cljZipmap*(args: seq[CljVal]): CljVal = mk.add(kitems[i]) mv.add(vitems[i]) return cljMap(mk, mv) + +# ---- Missing stubs ---- + +proc cljRandomUuid*(args: seq[CljVal]): CljVal = + randomize() + var uuid = "" + for i in 0..<32: + uuid.add("0123456789abcdef"[rand(15)]) + if i in {7, 11, 15, 19}: uuid.add('-') + cljString(uuid) + +proc cljSystemGetProperty*(args: seq[CljVal]): CljVal = + if args.len == 0: return cljNil() + let key = if args[0].kind == ckString: args[0].strVal else: "" + case key + of "line.separator": cljString("\n") + of "file.separator": cljString("/") + of "path.separator": cljString(":") + of "os.name": cljString("linux") + of "os.arch": cljString("x86_64") + of "java.version": cljString("0") + else: cljString("") + +proc cljVresetB*(args: seq[CljVal]): CljVal = + if args.len < 2: return cljNil() + if args[0].kind == ckAtom: + args[0].atomVal = args[1] + args[1] + else: + raise newException(CatchableError, "vreset! requires a volatile") + +proc cljRestartAgent*(args: seq[CljVal]): CljVal = + if args.len < 2: return cljNil() + if args[0].kind == ckAgent: + args[0].agentVal = args[1] + args[0].agentBusy = false + args[0] + else: + raise newException(CatchableError, "restart-agent requires an agent") + +proc cljRequire*(args: seq[CljVal]): CljVal = + cljNil() + +proc cljEvalStub*(args: seq[CljVal]): CljVal = + cljNil() + +proc cljResolve*(args: seq[CljVal]): CljVal = + if args.len == 0: return cljNil() + cljNil() + +proc Boolean*(args: seq[CljVal]): CljVal = + if args.len == 0: return cljBool(false) + let v = args[0] + if v.kind == ckBool: v + elif v.kind == ckString: cljBool(v.strVal.toLowerAscii() == "true") + elif v.kind == ckInt: cljBool(v.intVal != 0) + elif v.kind == ckFloat: cljBool(v.floatVal != 0.0) + else: cljBool(cljIsTruthy(v)) + +proc Boolean*(v: CljVal): CljVal = + Boolean(@[v]) + +proc Object*(args: seq[CljVal]): CljVal = + if args.len == 0: cljKeyword("Object") + else: args[0] + +proc Object*(): CljVal = + cljKeyword("Object") + +proc Integer*(args: seq[CljVal]): CljVal = + if args.len == 0: return cljInt(0) + cljToInt(args) + +proc Integer*(v: CljVal): CljVal = + cljToInt(@[v]) + +proc Long*(args: seq[CljVal]): CljVal = + if args.len == 0: return cljInt(0) + cljToInt(args) + +proc Float*(args: seq[CljVal]): CljVal = + if args.len == 0: return cljFloat(0.0) + cljToFloat(args) + +proc Double*(args: seq[CljVal]): CljVal = + if args.len == 0: return cljFloat(0.0) + cljToFloat(args) diff --git a/src/emitter.nim b/src/emitter.nim index a7af7bf..2789302 100644 --- a/src/emitter.nim +++ b/src/emitter.nim @@ -329,6 +329,16 @@ proc runtimeName(op: string): string = of "boolean": "cljToBool" of "num": "cljToInt" of "number": "cljToInt" + of "int?": "cljIsInteger" + of "Thread/sleep": "cljSleep" + of "System/getProperty": "cljSystemGetProperty" + of "random-uuid": "cljRandomUuid" + of "vreset!": "cljVresetB" + of "restart-agent": "cljRestartAgent" + of "with-out-str": "cljWithOutStr" + of "require": "cljRequire" + of "eval": "cljEvalStub" + of "resolve": "cljResolve" else: "" proc emitFnAsProc(items: seq[CljVal], indent: int): string = @@ -414,10 +424,6 @@ proc letDestructuredBindings(bindings: CljVal): CljVal = proc emitSpecialForm(items: seq[CljVal], indent: int): string = let sp = indentStr(indent) let head = items[0] - if head.kind == ckSymbol: - stderr.writeLine("DEBUG emitSpecialForm head=", head.symName) - if head.kind == ckSymbol and head.symName == "fn*": - stderr.writeLine("DEBUG emitSpecialForm fn* reached, op=", resolveNsAlias(head.symName)) if head.kind != ckSymbol: # Head is not a symbol - evaluate as function call: ((fn ...) args...) var argParts: seq[string] = @[] @@ -438,12 +444,16 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = raise newException(EmitterError, "ns name must be a symbol") var lines: seq[string] = @[] lines.add(sp & "# Namespace: " & nsName.symName) - # Process require clauses + # Process require clauses — register aliases + var newAliases: seq[(string, string)] = @[] for ri in 2.. 0 and ((clause.items[0].kind == ckSymbol and clause.items[0].symName == ":require") or (clause.items[0].kind == ckKeyword and clause.items[0].kwName == "require"))) + let isImport = (clause.kind == ckList and clause.items.len > 0 and + ((clause.items[0].kind == ckSymbol and clause.items[0].symName == ":import") or + (clause.items[0].kind == ckKeyword and clause.items[0].kwName == "import"))) if isRequire: for ci in 1..= 3 and req.items[1].kind == ckKeyword and req.items[1].kwName == "as": if req.items[2].kind == ckSymbol: alias = req.items[2].symName + # Also check for :refer + var referNames: seq[string] = @[] + var ri2 = 1 + while ri2 < req.items.len: + if req.items[ri2].kind == ckKeyword and req.items[ri2].kwName == "refer": + if ri2 + 1 < req.items.len and req.items[ri2+1].kind == ckVector: + for rn in req.items[ri2+1].items: + if rn.kind == ckSymbol: + referNames.add(rn.symName) + ri2 += 2 + elif req.items[ri2].kind == ckKeyword and req.items[ri2].kwName in ["as", "refer-macros"]: + ri2 += 2 + else: + ri2 += 1 + newAliases.add((alias, libName.symName)) + for rn in referNames: + newAliases.add((rn, rn)) # Convert namespace to file path: my.app -> my/app let filePath = libName.symName.replace(".", "/") & ".clj" lines.add(sp & "# require: " & libName.symName & " as " & alias & " from " & filePath) + if isImport: + # Skip :import clauses + discard + # Merge new aliases with existing + for (a, ns) in newAliases: + var found = false + for (ea, ens) in nsAliases: + if ea == a: + found = true + break + if not found: + nsAliases.add((a, ns)) return lines.join("\n") of "defmacro": @@ -605,9 +644,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = # Special handling: (def name (fn ...)) should emit raw proc, not cljFn wrapper let isFnDef = valForm.kind == ckList and valForm.items.len > 0 and valForm.items[0].kind == ckSymbol and valForm.items[0].symName == "fn" - stderr.writeLine("DEBUG def mangled=", mangled, " indent=", indent, " items.len=", items.len, " isFnDef=", isFnDef) if indent == 0: - stderr.writeLine("DEBUG def indent=0 branch") if isFnDef: # Emit fn as raw proc for def context let oldEmitLibMode = emitLibMode @@ -618,17 +655,15 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = let valCode = emitExpr(items[2], 0) return sp & "let " & mangled & " = " & valCode else: - stderr.writeLine("DEBUG def indent>0 branch") # For nested def: use global var so it's accessible from closures if isFnDef: let oldEmitLibMode = emitLibMode emitLibMode = false let valCode = emitFnAsProc(valForm.items, indent) emitLibMode = oldEmitLibMode - return sp & "var " & mangled & " {.global.} = " & valCode.strip() & "\n" & sp & mangled + return sp & "var " & mangled & " = " & valCode.strip() & "\n" & sp & mangled let valCode = emitExpr(items[2], indent) - stderr.writeLine("DEBUG def about to return var global for ", mangled) - return sp & "var " & mangled & " {.global.} = " & valCode.strip() & "\n" & sp & mangled + return sp & "var " & mangled & " = " & valCode.strip() & "\n" & sp & mangled of "defn": if items.len < 3: @@ -706,7 +741,6 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = return sp & "proc " & procName & "(" & paramNames.join(", ") & "): CljVal {.used.} =\n" & bodyCode of "fn": - stderr.writeLine("DEBUG emitSpecialForm fn indent=", indent) if items.len < 2: raise newException(EmitterError, "fn requires params") var paramsIdx = 1 @@ -757,7 +791,6 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = if fnName.len > 0 or argsLines.len > 0 or bodyCode.find("\n") != -1: # Wrap in IIFE to avoid indentation issues when used as an argument var wrapperLines: seq[string] = @[] - stderr.writeLine("DEBUG fn indent=", indent, " spLen=", sp.len, " indent+1Len=", indentStr(indent+1).len) wrapperLines.add(sp & "((proc (): CljVal =") var tempName: string if fnName.len > 0: @@ -861,16 +894,14 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = for bi, b in body: let bcode = emitExpr(b, indent + 1) let stripped = bcode.strip() - let isLast = (bi == body.len - 1) let isStatement = stripped.startsWith("echo ") or stripped.startsWith("discard ") or stripped.startsWith("var ") or stripped.startsWith("let ") or stripped.startsWith("proc ") or stripped.startsWith("if ") or stripped.startsWith("try:") or stripped.startsWith("while ") or stripped.contains("continue") - if isLast or isStatement: + if isStatement: lines.add(bcode) elif stripped.startsWith("block:"): - # Multi-line block as non-last form: discard the last line var blockLines = bcode.split("\n") var lastIdx = blockLines.len - 1 while lastIdx >= 0 and blockLines[lastIdx].strip() == "": @@ -887,6 +918,26 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = lines.add(indentStr(indent + 1) & "discard cljNil()") return lines.join("\n") + of "cond": + if items.len < 3 or items.len mod 2 != 1: + raise newException(EmitterError, "cond requires test/expr pairs") + # Build nested if-elif chain + var lines: seq[string] = @[] + var first = true + var ci = 1 + while ci < items.len - 1: + let testExpr = items[ci] + let thenExpr = items[ci + 1] + let condCode = emitExpr(testExpr, 0).strip() + let thenCode = emitExpr(thenExpr, indent + 1) + if first: + lines.add(sp & "if cljIsTruthy(" & condCode & "):\n" & thenCode) + first = false + else: + lines.add(sp & "elif cljIsTruthy(" & condCode & "):\n" & thenCode) + ci += 2 + return lines.join("\n") + of "loop": if items.len < 3: raise newException(EmitterError, "loop requires bindings and body") @@ -989,7 +1040,14 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = var nameIdx = 2 var bodyIdx = 3 if form.items[1].kind == ckSymbol and form.items.len >= 4: - exTypeStr = form.items[1].symName + let rawType = form.items[1].symName + # Map known Clojure exception types to Nim + if rawType in ["clojure.lang.ExceptionInfo", "ExceptionInfo"]: + exTypeStr = "ExInfo" + elif rawType in ["Throwable", "Exception", "java.lang.Exception", "java.lang.Throwable"]: + exTypeStr = "CatchableError" + else: + exTypeStr = rawType elif form.items[1].kind == ckKeyword: if form.items[1].kwName == "default": exTypeStr = "CatchableError" @@ -1035,6 +1093,8 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = lines.add(indentStr(indent + 1) & "let exPtr" & exVar & " = getCurrentException()") lines.add(indentStr(indent + 1) & "if exPtr" & exVar & " of ExInfo:") lines.add(indentStr(indent + 2) & "discard cljAssoc(" & exVar & ", cljKeyword(\"data\"), (ref ExInfo)(exPtr" & exVar & ").exData)") + pushScope() + addToScope(exVar) for i, eb in exBody: let isLast = (i == exBody.len - 1) let code = emitExpr(eb, indent + 1) @@ -1051,6 +1111,7 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = lines.add(code) else: lines.add(indentStr(indent + 1) & "discard " & stripped) + popScope() if finallyBody.len > 0: lines.add(sp & "finally:") for fb in finallyBody: @@ -1172,7 +1233,6 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = let fnArg = items[1] let collArg = emitExpr(items[2], 0) let isFn = fnArg.kind == ckList and fnArg.items.len > 0 and fnArg.items[0].kind == ckSymbol and fnArg.items[0].symName in ["fn", "fn*"] - stderr.writeLine("DEBUG filter isFn=", isFn, " symName=", (if fnArg.kind == ckList and fnArg.items.len > 0 and fnArg.items[0].kind == ckSymbol: fnArg.items[0].symName else: "N/A")) if isFn: let fnParams = fnArg.items[1] let fnBody = fnArg.items[2..^1] @@ -1298,6 +1358,14 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = return sp & "cljVar(" & emitExpr(target, 0) & ")" return sp & "cljVar(" & emitExpr(target, 0) & ")" + of "definterface": + if items.len < 2: + raise newException(EmitterError, "definterface requires a name") + let iname = items[1] + if iname.kind != ckSymbol: + raise newException(EmitterError, "definterface name must be a symbol") + return sp & "discard cljProtocol(\"" & iname.symName & "\")" + of "defprotocol": if items.len < 2: raise newException(EmitterError, "defprotocol requires a name") @@ -1337,6 +1405,19 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = raise newException(EmitterError, "deftype name must be a symbol") return sp & "let " & mangleName(tname.symName) & " = cljTypeConstructor(\"" & tname.symName & "\", cljVector(@[]))" + of "new": + # Java-style constructor: (new ClassName args...) — emit as constructor call + if items.len < 2: + return sp & "cljNil()" + let className = items[1] + if className.kind != ckSymbol: + return sp & "cljNil()" + var argParts: seq[string] = @[] + for i in 2.. 0: var argParts: seq[string] = @[] for i in 1.. 0 and items[i].items[0].kind == ckSymbol and items[i].items[0].symName in ["fn", "fn*"]: - stderr.writeLine("DEBUG runtimeCall fn arg indent=", indent) argParts.add(emitExpr(items[i], indent)) # Variadic functions take seq[CljVal] let variadic = op in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=", @@ -1676,7 +1755,16 @@ proc emitSpecialForm(items: seq[CljVal], indent: int): string = "println-str", "prn-str", "binding", "aset", "volatile!", "deliver", "doall", "dorun", "to-array", "into-array", "vector", "rand", "rand-int", - "rand-nth", "random-sample"] + "rand-nth", "random-sample", + "assoc", "dissoc", "get", "get-in", "update", "assoc-in", + "contains?", "select-keys", "keys", "vals", + "disj", "peek", "pop", + "transduce", "ex-info", + "compare", "subvec", + "require", "eval", "resolve", "random-uuid", + "vreset!", "restart-agent", "with-out-str", + "System/getProperty"] + var call: string if variadic: call = rn & "(@[" & argParts.join(", ") & "])" @@ -1751,22 +1839,30 @@ proc emitExpr*(v: CljVal, indent: int = 0): string = if rn.len > 0 and not isLocalVar(symName): # Check if the runtime function is variadic (takes seq[CljVal]) let variadic = symName in ["+", "-", "*", "/", "=", ">", "<", ">=", "<=", "not=", - "println", "prn", "print", "str", "pr-str", - "atom", "concat", "min", "max", "merge", "interleave", - "zipmap", "hash-map", "hash-set", "sorted-map", "sorted-map-by", "sorted-set", "sorted-set-by", "cons", "use-fixtures", "vswap!", "swap!", "tap>", "add-tap", "remove-tap", "add-watch", "remove-watch", "alter-var-root", - "array-map", "inf", "nan", "list", - "float", "int", "double", "long", "short", "byte", - "boolean", "num", "number", - "make-hierarchy", "derive", "underive", "ancestors", - "descendants", "parents", "isa?", "promise", "create-ns", "future", - "delay", - "aclone", "alength", "aget", "int-array", "identical?", "empty", "identity", - "conj", - "drop-last", "shuffle", "repeatedly", "fnil", "intern", - "println-str", "prn-str", "binding", "aset", - "volatile!", "deliver", "doall", "dorun", - "to-array", "into-array", "vector", "rand", "rand-int", - "rand-nth", "random-sample"] + "println", "prn", "print", "str", "pr-str", + "atom", "concat", "min", "max", "merge", "interleave", + "zipmap", "hash-map", "hash-set", "sorted-map", "sorted-map-by", "sorted-set", "sorted-set-by", "cons", "use-fixtures", "vswap!", "swap!", "tap>", "add-tap", "remove-tap", "add-watch", "remove-watch", "alter-var-root", + "array-map", "inf", "nan", "list", + "float", "int", "double", "long", "short", "byte", + "boolean", "num", "number", + "make-hierarchy", "derive", "underive", "ancestors", + "descendants", "parents", "isa?", "promise", "create-ns", "future", + "delay", + "aclone", "alength", "aget", "int-array", "identical?", "empty", "identity", + "conj", + "drop-last", "shuffle", "repeatedly", "fnil", "intern", + "println-str", "prn-str", "binding", "aset", + "volatile!", "deliver", "doall", "dorun", + "to-array", "into-array", "vector", "rand", "rand-int", + "rand-nth", "random-sample", + "assoc", "dissoc", "get", "get-in", "update", "assoc-in", + "contains?", "select-keys", "keys", "vals", + "disj", "peek", "pop", + "transduce", "ex-info", + "compare", "subvec", + "require", "eval", "resolve", "random-uuid", + "vreset!", "restart-agent", "with-out-str", + "System/getProperty"] if variadic: return sp & "cljFn(proc(args: seq[CljVal]): CljVal = " & rn & "(args))" else: @@ -1816,16 +1912,10 @@ proc emitBlock(items: seq[CljVal], indent: int, useResult: bool = false): string var lines: seq[string] = @[] for i, item in items: var code = emitExpr(item, indent) - if code.contains("testvar_a"): - stderr.writeLine("DEBUG emitBlock indent=", indent, " code=", code.repr) let stripped = code.strip() if i < items.len - 1: let firstNl = code.find("\n") let firstLine = if firstNl == -1: stripped else: code[0.. 0: let (item, itemIsLast) = worklist.pop() if item.kind == ckList and item.items.len > 0 and item.items[0].kind == ckSymbol: - stderr.writeLine("DEBUG worklist popping ", item.items[0].symName) - unwrapOneForm(item, itemIsLast) + unwrapOneForm(item, itemIsLast) var lines = headerLines # Add collected Nim imports diff --git a/src/reader.nim b/src/reader.nim index 91a9569..9a62544 100644 --- a/src/reader.nim +++ b/src/reader.nim @@ -450,10 +450,10 @@ proc readDispatch(s: string, i: var int): CljVal = else: discard k += 2 var resultVal: CljVal = nil - if cljVal != nil: - resultVal = cljVal - elif defaultVal != nil: + if defaultVal != nil: resultVal = defaultVal + elif cljVal != nil: + resultVal = cljVal else: return nil if isSplice and resultVal != nil and resultVal.kind == ckVector: