summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2012-07-01 19:35:19 +0200
committerAraq <rumpf_a@web.de>2012-07-01 19:35:19 +0200
commitfe285b354dee2c8148cd66207d8ed528c7845e5d (patch)
tree01f799a3905ede0ffe6fb5af49d063733203051b
parentee1bcb6414a50642811632a92a803840c1c67036 (diff)
downloadNim-fe285b354dee2c8148cd66207d8ed528c7845e5d.tar.gz
JS codegen enhancements; still unusable
-rwxr-xr-xcompiler/ecmasgen.nim98
-rwxr-xr-xcompiler/main.nim4
-rwxr-xr-xcompiler/nimrod.nim9
-rwxr-xr-xdoc/nimrodc.txt1
-rwxr-xr-xlib/pure/hashes.nim23
-rwxr-xr-xlib/system/ecmasys.nim101
-rw-r--r--tests/specials.nim16
-rwxr-xr-xtests/tester.nim25
-rwxr-xr-xtodo.txt5
9 files changed, 223 insertions, 59 deletions
diff --git a/compiler/ecmasgen.nim b/compiler/ecmasgen.nim
index 64ef75de5..532023136 100755
--- a/compiler/ecmasgen.nim
+++ b/compiler/ecmasgen.nim
@@ -546,11 +546,12 @@ proc genTryStmt(p: var TProc, n: PNode, r: var TCompRes) =
       if i > 1: app(epart, '}' & tnl)
     else: 
       orExpr = nil
+      useMagic(p, "isObj")
       for j in countup(0, blen - 2): 
         if (n.sons[i].sons[j].kind != nkType): 
           InternalError(n.info, "genTryStmt")
         if orExpr != nil: app(orExpr, "||")
-        appf(orExpr, "($1.exc.m_type == $2)", 
+        appf(orExpr, "isObj($1.exc.m_type, $2)", 
              [safePoint, genTypeInfo(p, n.sons[i].sons[j].typ)])
       if i > 1: app(epart, "else ")
       appf(epart, "if ($1.exc && $2) {$n", [safePoint, orExpr])
@@ -791,7 +792,7 @@ proc genSwap(p: var TProc, n: PNode, r: var TCompRes) =
   gen(p, n.sons[2], b)
   inc(p.unique)
   var tmp = ropef("Tmp$1", [toRope(p.unique)])
-  case mapType(n.sons[1].typ)
+  case mapType(skipTypes(n.sons[1].typ, abstractVar))
   of etyBaseIndex: 
     inc(p.unique)
     var tmp2 = ropef("Tmp$1", [toRope(p.unique)])
@@ -804,24 +805,36 @@ proc genSwap(p: var TProc, n: PNode, r: var TCompRes) =
     if b.com != nil: appf(r.com, "$1;$n", [b.com])
     appf(r.com, "var $1 = $2; $2 = $3; $3 = $1", [tmp, a.res, b.res])
 
+proc getFieldPosition(f: PNode): int =
+  case f.kind
+  of nkIntLit..nkUInt64Lit: result = int(f.intVal)
+  of nkSym: result = f.sym.position
+  else: InternalError(f.info, "genFieldPosition")
+
 proc genFieldAddr(p: var TProc, n: PNode, r: var TCompRes) = 
   var a: TCompRes
   r.kind = etyBaseIndex
   var b = if n.kind == nkHiddenAddr: n.sons[0] else: n
   gen(p, b.sons[0], a)
-  if b.sons[1].kind != nkSym: InternalError(b.sons[1].info, "genFieldAddr")
-  var f = b.sons[1].sym
-  if f.loc.r == nil: f.loc.r = mangleName(f)
-  r.res = makeCString(ropeToStr(f.loc.r))
+  if skipTypes(b.sons[0].typ, abstractVarRange).kind == tyTuple:
+    r.res = makeCString("Field" & $getFieldPosition(b.sons[1]))
+  else:
+    if b.sons[1].kind != nkSym: InternalError(b.sons[1].info, "genFieldAddr")
+    var f = b.sons[1].sym
+    if f.loc.r == nil: f.loc.r = mangleName(f)
+    r.res = makeCString(ropeToStr(f.loc.r))
   r.com = mergeExpr(a)
 
 proc genFieldAccess(p: var TProc, n: PNode, r: var TCompRes) = 
   r.kind = etyNone
   gen(p, n.sons[0], r)
-  if n.sons[1].kind != nkSym: InternalError(n.sons[1].info, "genFieldAddr")
-  var f = n.sons[1].sym
-  if f.loc.r == nil: f.loc.r = mangleName(f)
-  r.res = ropef("$1.$2", [r.res, f.loc.r])
+  if skipTypes(n.sons[0].typ, abstractVarRange).kind == tyTuple:
+    r.res = ropef("$1.Field$2", [r.res, getFieldPosition(n.sons[1]).toRope])
+  else:
+    if n.sons[1].kind != nkSym: InternalError(n.sons[1].info, "genFieldAddr")
+    var f = n.sons[1].sym
+    if f.loc.r == nil: f.loc.r = mangleName(f)
+    r.res = ropef("$1.$2", [r.res, f.loc.r])
 
 proc genCheckedFieldAddr(p: var TProc, n: PNode, r: var TCompRes) = 
   genFieldAddr(p, n.sons[0], r) # XXX
@@ -884,11 +897,17 @@ proc genAddr(p: var TProc, n: PNode, r: var TCompRes) =
     genCheckedFieldAddr(p, n, r)
   of nkDotExpr: 
     genFieldAddr(p, n, r)
-  of nkBracketExpr: 
-    genArrayAddr(p, n, r)
+  of nkBracketExpr:
+    var ty = skipTypes(n.sons[0].typ, abstractVarRange)
+    if ty.kind in {tyRef, tyPtr}: ty = skipTypes(ty.sons[0], abstractVarRange)
+    case ty.kind
+    of tyArray, tyArrayConstr, tyOpenArray, tySequence, tyString, tyCString: 
+      genArrayAddr(p, n, r)
+    of tyTuple: 
+      genFieldAddr(p, n, r)
+    else: InternalError(n.info, "expr(nkBracketExpr, " & $ty.kind & ')')
   else: InternalError(n.info, "genAddr")
   
-
 proc genSym(p: var TProc, n: PNode, r: var TCompRes) = 
   var s = n.sym
   case s.kind
@@ -956,6 +975,7 @@ proc genCall(p: var TProc, n: PNode, r: var TCompRes) =
   genArgs(p, n, r)
 
 proc genEcho(p: var TProc, n: PNode, r: var TCompRes) =
+  useMagic(p, "rawEcho")
   app(r.res, "rawEcho")
   genArgs(p, n, r)
 
@@ -1146,6 +1166,25 @@ proc genRepr(p: var TProc, n: PNode, r: var TCompRes) =
     # XXX:
     internalError(n.info, "genRepr: Not implemented")
 
+proc genOf(p: var TProc, n: PNode, r: var TCompRes) =
+  var x: TCompRes
+  let t = n.sons[2].typ
+  gen(p, n.sons[1], x)
+  if tfFinal in t.flags:
+    r.res = ropef("($1.m_type == $2)", [x.res, genTypeInfo(p, t)])
+  else:
+    useMagic(p, "isObj")
+    r.res = ropef("isObj($1.m_type, $2)", [x.res, genTypeInfo(p, t)])
+  r.com = mergeExpr(r.com, x.com)
+
+proc genReset(p: var TProc, n: PNode, r: var TCompRes) =
+  var x: TCompRes
+  useMagic(p, "genericReset")
+  gen(p, n.sons[1], x)
+  r.res = ropef("$1 = genericReset($1, $2)", [x.res, 
+                genTypeInfo(p, n.sons[1].typ)])
+  r.com = mergeExpr(r.com, x.com)
+
 proc genMagic(p: var TProc, n: PNode, r: var TCompRes) =
   var 
     a: TCompRes
@@ -1216,6 +1255,8 @@ proc genMagic(p: var TProc, n: PNode, r: var TCompRes) =
   of mNLen..mNError:
     localError(n.info, errCannotGenerateCodeForX, n.sons[0].sym.name.s)
   of mNewSeq: binaryStmt(p, n, r, "", "$1 = new Array($2)")
+  of mOf: genOf(p, n, r)
+  of mReset: genReset(p, n, r)
   of mEcho: genEcho(p, n, r)
   of mSlurp, mStaticExec:
     localError(n.info, errXMustBeCompileTime, n.sons[0].sym.name.s)
@@ -1252,19 +1293,17 @@ proc genArrayConstr(p: var TProc, n: PNode, r: var TCompRes) =
     app(r.res, a.res)
   app(r.res, "]")
 
-proc genRecordConstr(p: var TProc, n: PNode, r: var TCompRes) = 
+proc genTupleConstr(p: var TProc, n: PNode, r: var TCompRes) = 
   var a: TCompRes
-  var i = 0
-  var length = sonsLen(n)
   r.res = toRope("{")
-  while i < length: 
+  for i in countup(0, sonsLen(n) - 1):
     if i > 0: app(r.res, ", ")
-    if (n.sons[i].kind != nkSym): 
-      internalError(n.sons[i].info, "genRecordConstr")
-    gen(p, n.sons[i + 1], a)
+    var it = n.sons[i]
+    if it.kind == nkExprColonExpr: it = it.sons[1]
+    gen(p, it, a)
     r.com = mergeExpr(r.com, a.com)
-    appf(r.res, "$1: $2", [mangleName(n.sons[i].sym), a.res])
-    inc(i, 2)
+    appf(r.res, "Field$1: $2", [i.toRope, a.res])
+  r.res.app("}")
 
 proc genConv(p: var TProc, n: PNode, r: var TCompRes) = 
   var dest = skipTypes(n.typ, abstractVarRange)
@@ -1414,9 +1453,6 @@ proc genStmt(p: var TProc, n: PNode, r: var TCompRes) =
       genSym(p, n.sons[namePos], r2)
   else:
     genLineDir(p, n, r)
-    if n.sons[0].kind == nkSym:
-      if n.sons[0].sym.loc.r == nil:
-        n.sons[0].sym.loc.r = toRope(n.sons[0].sym.name.s)
     gen(p, n, r)
     app(r.res, ';' & tnl)
 
@@ -1460,7 +1496,7 @@ proc gen(p: var TProc, n: PNode, r: var TCompRes) =
       genCall(p, n, r)
   of nkCurly: genSetConstr(p, n, r)
   of nkBracket: genArrayConstr(p, n, r)
-  of nkPar: genRecordConstr(p, n, r)
+  of nkPar: genTupleConstr(p, n, r)
   of nkHiddenStdConv, nkHiddenSubConv, nkConv: genConv(p, n, r)
   of nkAddr, nkHiddenAddr: genAddr(p, n, r)
   of nkDerefExpr, nkHiddenDeref: genDeref(p, n, r)
@@ -1477,8 +1513,14 @@ proc gen(p: var TProc, n: PNode, r: var TCompRes) =
   of nkStmtListExpr: genStmtListExpr(p, n, r)
   of nkEmpty: nil
   of nkLambdaKinds: 
-    # XXX not correct, as we need to put it into the proper scope!
-    gen(p, n.sons[namePos], r)
+    let s = n.sons[namePos].sym
+    discard mangleName(s)
+    r.res = s.loc.r
+    if lfNoDecl in s.loc.flags or s.magic != mNone or isGenericRoutine(s): nil
+    elif not p.g.generatedSyms.containsOrIncl(s.id):
+      var r2: TCompRes
+      genProc(p, s, r2)
+      app(r.com, mergeStmt(r2))
   of nkMetaNode: gen(p, n.sons[0], r)
   of nkType: r.res = genTypeInfo(p, n.typ)
   else: InternalError(n.info, "gen: unknown node type: " & $n.kind)
diff --git a/compiler/main.nim b/compiler/main.nim
index 0675142fa..d3a40a32d 100755
--- a/compiler/main.nim
+++ b/compiler/main.nim
@@ -131,7 +131,9 @@ when has_LLVM_Backend:
 proc CommandCompileToEcmaScript =
   incl(gGlobalOptions, optSafeCode)
   setTarget(osEcmaScript, cpuEcmaScript)
-  initDefines()
+  #initDefines()
+  DefineSymbol("nimrod") # 'nimrod' is always defined
+  DefineSymbol("ecmascript")
   semanticPasses()
   registerPass(ecmasgenPass())
   compileProject()
diff --git a/compiler/nimrod.nim b/compiler/nimrod.nim
index 6328c3ae3..1bd6698a7 100755
--- a/compiler/nimrod.nim
+++ b/compiler/nimrod.nim
@@ -98,9 +98,14 @@ proc HandleCmdLine() =
                    formatFloat(epochTime() - start, ffDecimal, 3),
                    formatSize(getTotalMem())])
       if optRun in gGlobalOptions:
-        var ex = quoteIfContainsWhite(
+        if gCmd == cmdCompileToEcmaScript:
+          var ex = quoteIfContainsWhite(
+            completeCFilePath(changeFileExt(gProjectFull, "js").prependCurDir))
+          execExternalProgram("node " & ex & ' ' & arguments)
+        else:
+          var ex = quoteIfContainsWhite(
             changeFileExt(gProjectFull, exeExt).prependCurDir)
-        execExternalProgram(ex & ' ' & arguments)
+          execExternalProgram(ex & ' ' & arguments)
 
 #GC_disableMarkAndSweep()
 
diff --git a/doc/nimrodc.txt b/doc/nimrodc.txt
index 0fda5bb88..b131acca1 100755
--- a/doc/nimrodc.txt
+++ b/doc/nimrodc.txt
@@ -145,6 +145,7 @@ Define               Effect
 ``useRealtimeGC``    Enables support of Nimrod's GC for *soft* realtime 
                      systems. See the documentation of the `gc <gc.html>`_ 
                      for further information.
+``nodejs``           The EcmaScript target is actually ``node.js``.
 ==================   =========================================================
 
 

diff --git a/lib/pure/hashes.nim b/lib/pure/hashes.nim
index 06cc6c79b..7d4613f50 100755
--- a/lib/pure/hashes.nim
+++ b/lib/pure/hashes.nim
@@ -35,7 +35,11 @@ proc `!$`*(h: THash): THash {.inline.} =
 proc hashData*(Data: Pointer, Size: int): THash = 
   ## hashes an array of bytes of size `size`
   var h: THash = 0
-  var p = cast[cstring](Data)
+  when defined(ecmascript):
+    var p: cstring
+    asm """`p` = `Data`;"""
+  else:
+    var p = cast[cstring](Data)
   var i = 0
   var s = size
   while s > 0: 
@@ -44,9 +48,24 @@ proc hashData*(Data: Pointer, Size: int): THash =
     Dec(s)
   result = !$h
 
+when defined(ecmascript):
+  var objectID = 0
+
 proc hash*(x: Pointer): THash {.inline.} = 
   ## efficient hashing of pointers
-  result = (cast[THash](x)) shr 3 # skip the alignment
+  when defined(ecmascript):
+    asm """
+      if (typeof `x` == "object") {
+        if ("_NimID" in `x`)
+          `result` = `x`["_NimID"];
+        else {
+          `result` = ++`objectID`;
+          `x`["_NimID"] = `result`;
+        }
+      }
+    """
+  else:
+    result = (cast[THash](x)) shr 3 # skip the alignment
   
 proc hash*(x: int): THash {.inline.} = 
   ## efficient hashing of integers
diff --git a/lib/system/ecmasys.nim b/lib/system/ecmasys.nim
index e9a1e92ed..8f4585931 100755
--- a/lib/system/ecmasys.nim
+++ b/lib/system/ecmasys.nim
@@ -7,7 +7,11 @@
 #    distribution, for details about the copyright.
 #
 
-proc alert*(s: cstring) {.importc, nodecl.}
+when defined(nodejs):
+  proc alert*(s: cstring) {.importc: "console.log", nodecl.}
+else:
+  proc alert*(s: cstring) {.importc, nodecl.}
+
 proc log*(s: cstring) {.importc: "console.log", nodecl.}
 
 type
@@ -100,7 +104,7 @@ proc raiseException(e: ref E_Base, ename: cstring) {.
     alert(buf)
   asm """throw `e`;"""
 
-proc reraiseException() =
+proc reraiseException() {.compilerproc, noStackFrame.} =
   if excHandler == nil:
     raise newException(ENoExceptionToReraise, "no exception to reraise")
   else:
@@ -295,27 +299,40 @@ type
     setAttribute*: proc (name, value: cstring)
     setAttributeNode*: proc (attr: ref TNode)
     
-var
-  document {.importc, nodecl.}: ref TDocument
+when defined(nodejs):
+  proc ewriteln(x: cstring) = log(x)
+  
+  proc rawEcho {.compilerproc, nostackframe.} =
+    asm """
+      var buf = "";
+      for (var i = 0; i < arguments.length; ++i) {
+        buf += `toEcmaStr`(arguments[i]);
+      }
+      console.log(buf);
+    """
 
-proc ewriteln(x: cstring) = 
-  var node = document.getElementsByTagName("body")[0]
-  if node != nil: 
-    node.appendChild(document.createTextNode(x))
+else:
+  var
+    document {.importc, nodecl.}: ref TDocument
+
+  proc ewriteln(x: cstring) = 
+    var node = document.getElementsByTagName("body")[0]
+    if node != nil: 
+      node.appendChild(document.createTextNode(x))
+      node.appendChild(document.createElement("br"))
+    else: 
+      raise newException(EInvalidValue, "<body> element does not exist yet!")
+
+  proc rawEcho {.compilerproc.} =
+    var node = document.getElementsByTagName("body")[0]
+    if node == nil: raise newException(EIO, "<body> element does not exist yet!")
+    asm """
+      for (var i = 0; i < arguments.length; ++i) {
+        var x = `toEcmaStr`(arguments[i]);
+        `node`.appendChild(document.createTextNode(x))
+      }
+    """
     node.appendChild(document.createElement("br"))
-  else: 
-    raise newException(EInvalidValue, "<body> element does not exist yet!")
-
-proc rawEcho {.compilerproc.} =
-  var node = document.getElementsByTagName("body")[0]
-  if node == nil: raise newException(EIO, "<body> element does not exist yet!")
-  asm """
-    for (var i = 0; i < arguments.length; ++i) {
-      var x = `toEcmaStr`(arguments[i]);
-      `node`.appendChild(document.createTextNode(x))
-    }
-  """
-  node.appendChild(document.createElement("br"))
 
 # Arithmetic:
 proc addInt(a, b: int): int {.noStackFrame, compilerproc.} =
@@ -474,7 +491,7 @@ proc isFatPointer(ti: PNimType): bool =
 
 proc NimCopy(x: pointer, ti: PNimType): pointer {.compilerproc.}
 
-proc NimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.exportc.} =
+proc NimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.compilerproc.} =
   case n.kind
   of nkNone: sysAssert(false, "NimCopyAux")
   of nkSlot:
@@ -525,6 +542,37 @@ proc NimCopy(x: pointer, ti: PNimType): pointer =
   else:
     result = x
 
+proc genericReset(x: Pointer, ti: PNimType): pointer {.compilerproc.} =
+  case ti.kind
+  of tyPtr, tyRef, tyVar, tyNil:
+    if not isFatPointer(ti):
+      result = nil
+    else:
+      asm """
+        `result` = [null, 0];
+      """
+  of tySet:
+    asm """
+      `result` = {};
+    """
+  of tyTuple, tyObject:
+    if ti.kind == tyObject:
+      asm "`result` = {m_type: `ti`};"
+    else:
+      asm "`result` = {};"
+  of tySequence, tyOpenArray:
+    asm """
+      `result` = [];
+    """
+  of tyArrayConstr, tyArray:
+    asm """
+      `result` = new Array(`x`.length);
+      for (var i = 0; i < `x`.length; ++i) {
+        `result`[i] = genericReset(`x`[i], `ti`.base);
+      }
+    """
+  else:
+    result = nil
 
 proc ArrayConstr(len: int, value: pointer, typ: PNimType): pointer {.
                  noStackFrame, compilerproc.} =
@@ -552,4 +600,13 @@ proc chckObj(obj, subclass: PNimType) {.compilerproc.} =
       raise newException(EInvalidObjectConversion, "invalid object conversion")
     x = x.base
 
+proc isObj(obj, subclass: PNimType): bool {.compilerproc.} =
+  # checks if obj is of type subclass:
+  var x = obj
+  if x == subclass: return true # optimized fast path
+  while x != subclass:
+    if x == nil: return false
+    x = x.base
+  return true
+
 {.pop.}
diff --git a/tests/specials.nim b/tests/specials.nim
index da2013e02..073ea0d27 100644
--- a/tests/specials.nim
+++ b/tests/specials.nim
@@ -169,6 +169,22 @@ proc compileDebuggerTests(r: var TResults, options: string) =
   compileSingleTest(r, "tools/nimgrep", options & 
                     " --debugger:on")
 
+# ------------------------- JS tests ------------------------------------------
+
+proc runJsTests(r: var TResults, options: string) =
+  template test(filename: expr): stmt =
+    runSingleTest(r, filename, options & " -d:nodejs", targetJS)
+    runSingleTest(r, filename, options & " -d:nodejs -d:release", targetJS)
+    
+  # tactiontable, texceptions, texcpt1, texcsub, tfinally, tfinally2,
+  # tfinally3
+  for t in os.walkFiles("tests/js/t*.nim"):
+    test(t)
+  test "tests/run/tactiontable"
+  test "tests/run/tmultim1"
+  test "tests/run/tmultim3"
+  test "tests/run/tmultim4"
+
 # ------------------------- register special tests here -----------------------
 proc runSpecialTests(r: var TResults, options: string) =
   runRodFiles(r, options)
diff --git a/tests/tester.nim b/tests/tester.nim
index a26b7c178..be0765874 100755
--- a/tests/tester.nim
+++ b/tests/tester.nim
@@ -261,7 +261,10 @@ proc compileSingleTest(r: var TResults, test, options: string) =
   r.addResult(t, given.msg, if given.err: reFailure else: reSuccess)
   if not given.err: inc(r.passed)
 
-proc runSingleTest(r: var TResults, test, options: string) =
+type
+  TTarget = enum targetC, targetJS
+
+proc runSingleTest(r: var TResults, test, options: string, target: TTarget) =
   var test = test.addFileExt(".nim")
   var t = extractFilename(test)
   echo t
@@ -275,9 +278,16 @@ proc runSingleTest(r: var TResults, test, options: string) =
     if given.err:
       r.addResult(t, "", given.msg, reFailure)
     else:
-      var exeFile = changeFileExt(test, ExeExt)
+      var exeFile: string
+      if target == targetC:
+        exeFile = changeFileExt(test, ExeExt)
+      else:
+        let (dir, file, ext) = splitFile(test)
+        exeFile = dir / "nimcache" / file & ".js"
+      
       if existsFile(exeFile):
-        var (buf, exitCode) = execCmdEx(exeFile)
+        var (buf, exitCode) = execCmdEx(
+          (if target==targetJS: "node " else: "") & exeFile)
         if exitCode != expected.ExitCode:
           r.addResult(t, "exitcode: " & $expected.ExitCode,
                          "exitcode: " & $exitCode, reFailure)
@@ -291,6 +301,9 @@ proc runSingleTest(r: var TResults, test, options: string) =
       else:
         r.addResult(t, expected.outp, "executable not found", reFailure)
 
+proc runSingleTest(r: var TResults, test, options: string) =
+  runSingleTest(r, test, options, targetC)
+  
 proc run(r: var TResults, dir, options: string) =
   for test in os.walkFiles(dir / "t*.nim"): runSingleTest(r, test, options)
 
@@ -350,6 +363,12 @@ proc main() =
     run(runRes, "tests/run", p.cmdLineRest.string)
     runSpecialTests(runRes, p.cmdLineRest.string)
     writeResults(runJson, runRes)
+  of "js":
+    var runRes = initResults()
+    if existsFile(runJSon):
+      runRes = readResults(runJson)
+    runJsTests(runRes, p.cmdLineRest.string)
+    writeResults(runJson, runRes)
   of "merge":
     var rejectRes = readResults(rejectJson)
     var compileRes = readResults(compileJson)
diff --git a/todo.txt b/todo.txt
index 13eecc584..ebca214b7 100755
--- a/todo.txt
+++ b/todo.txt
@@ -42,6 +42,10 @@ Bugs
 version 0.9.XX
 ==============
 
+- JS gen:
+  - document it
+  - test & fix bugs
+
 - document nimdoc properly finally
 - implement a warning message for shadowed 'result' variable
 - implement the high level optimizer
@@ -51,7 +55,6 @@ version 0.9.XX
 - implicit ref/ptr->var conversion; the compiler may store an object
   implicitly on the heap for write barrier efficiency; better: 
   proc specialization in the code gen
-- 'of' operator for JS backend
 - tlastmod returns wrong results on BSD (Linux, MacOS X: works)
 - nested tuple unpacking; tuple unpacking in non-var-context
 - 'nimrod def': does not always work?