summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rwxr-xr-xcompiler/ast.nim4
-rwxr-xr-xcompiler/evals.nim108
-rwxr-xr-xcompiler/msgs.nim6
-rwxr-xr-xcompiler/sem.nim77
-rwxr-xr-xcompiler/semexprs.nim33
-rwxr-xr-xcompiler/semfold.nim2
-rwxr-xr-xcompiler/semstmts.nim14
-rwxr-xr-xcompiler/semtempl.nim20
-rwxr-xr-xcompiler/types.nim5
-rwxr-xr-xlib/core/macros.nim18
-rwxr-xr-xlib/pure/terminal.nim38
-rw-r--r--lib/pure/unittest.nim150
-rwxr-xr-xlib/system.nim25
-rw-r--r--tests/accept/run/tunit.nim47
14 files changed, 446 insertions, 101 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index b33d99554..0d920b835 100755
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -327,7 +327,7 @@ type
   TMagic* = enum # symbols that require compiler magic:
     mNone, mDefined, mDefinedInScope, mLow, mHigh, mSizeOf, mIs, mOf,
     mEcho, mShallowCopy, mSlurp,
-    mAstToYaml, mParseExprToAst, mParseStmtToAst, mExpandMacroToAst,
+    mAstToYaml, mParseExprToAst, mParseStmtToAst, mExpandToAst,
     mUnaryLt, mSucc, 
     mPred, mInc, mDec, mOrd, mNew, mNewFinalize, mNewSeq, mLengthOpenArray, 
     mLengthStr, mLengthArray, mLengthSeq, mIncl, mExcl, mCard, mChr, mGCref, 
@@ -369,7 +369,7 @@ type
     mCompileOption, mCompileOptionArg,
     mNLen, mNChild, mNSetChild, mNAdd, mNAddMultiple, mNDel, mNKind, 
     mNIntVal, mNFloatVal, mNSymbol, mNIdent, mNGetType, mNStrVal, mNSetIntVal, 
-    mNSetFloatVal, mNSetSymbol, mNSetIdent, mNSetType, mNSetStrVal, 
+    mNSetFloatVal, mNSetSymbol, mNSetIdent, mNSetType, mNSetStrVal, mNLineInfo,
     mNNewNimNode, mNCopyNimNode, mNCopyNimTree, mStrToIdent, mIdentToStr, 
     mEqIdent, mEqNimrodNode, mNHint, mNWarning, mNError, mGetTypeInfo
 
diff --git a/compiler/evals.nim b/compiler/evals.nim
index 1d443a404..d2559176e 100755
--- a/compiler/evals.nim
+++ b/compiler/evals.nim
@@ -65,6 +65,7 @@ proc popStackFrame*(c: PEvalContext) {.inline.} =
   if (c.tos == nil): InternalError("popStackFrame")
   c.tos = c.tos.next
 
+proc eval*(c: PEvalContext, n: PNode): PNode
 proc evalAux(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode
 
 proc stackTraceAux(x: PStackFrame) =
@@ -764,7 +765,7 @@ proc isEmpty(n: PNode): bool =
 proc stringStartingLine(s: PNode): int =
   result = s.info.line - countLines(s.strVal)
 
-proc evalParseExpr(c: PEvalContext, n: Pnode): Pnode =
+proc evalParseExpr(c: PEvalContext, n: PNode): PNode =
   var code = evalAux(c, n.sons[1], {})
   var ast = parseString(code.getStrValue, code.info.toFilename,
                         code.stringStartingLine)
@@ -773,12 +774,108 @@ proc evalParseExpr(c: PEvalContext, n: Pnode): Pnode =
   result = ast.sons[0]
   result.typ = newType(tyExpr, c.module)
 
-proc evalParseStmt(c: PEvalContext, n: Pnode): Pnode =
+proc evalParseStmt(c: PEvalContext, n: PNode): PNode =
   var code = evalAux(c, n.sons[1], {})
   result = parseString(code.getStrValue, code.info.toFilename,
                        code.stringStartingLine)
   result.typ = newType(tyStmt, c.module)
 
+proc evalTemplateAux*(templ, actual: PNode, sym: PSym): PNode = 
+  case templ.kind
+  of nkSym: 
+    var p = templ.sym
+    if (p.kind == skParam) and (p.owner.id == sym.id): 
+      result = copyTree(actual.sons[p.position])
+    else: 
+      result = copyNode(templ)
+  of nkNone..nkIdent, nkType..nkNilLit: # atom
+    result = copyNode(templ)
+  else: 
+    result = copyNode(templ)
+    newSons(result, sonsLen(templ))
+    for i in countup(0, sonsLen(templ) - 1): 
+      result.sons[i] = evalTemplateAux(templ.sons[i], actual, sym)
+
+proc evalTemplateArgs(n: PNode, s: PSym): PNode =
+  var 
+    f, a: int
+    arg: PNode
+    
+  f = sonsLen(s.typ)
+
+  # if the template has zero arguments, it can be called without ``()``
+  # `n` is then a nkSym or something similar
+  case n.kind
+  of nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, nkCallStrLit:
+    a = sonsLen(n)
+  else: a = 0
+  
+  if a > f: GlobalError(n.info, errWrongNumberOfArguments)
+
+  result = copyNode(n)
+  for i in countup(1, f - 1):
+    if i < a:
+      arg = n.sons[i]
+    else:
+      arg = copyTree(s.typ.n.sons[i].sym.ast)
+
+    addSon(result, arg)
+
+var evalTemplateCounter = 0
+  # to prevend endless recursion in templates instantation
+
+proc evalTemplate(n: PNode, sym: PSym): PNode = 
+  inc(evalTemplateCounter)
+  if evalTemplateCounter > 100:
+    GlobalError(n.info, errTemplateInstantiationTooNested)
+
+  # replace each param by the corresponding node:
+  var args = evalTemplateArgs(n, sym)
+  result = evalTemplateAux(sym.ast.sons[codePos], args, sym)
+
+  dec(evalTemplateCounter)
+
+proc evalMacroCall*(c: PEvalContext, n: PNode, sym: PSym): PNode =
+  inc(evalTemplateCounter)
+  if evalTemplateCounter > 100: 
+    GlobalError(n.info, errTemplateInstantiationTooNested)
+
+  var s = newStackFrame()
+  s.call = n
+  setlen(s.params, 2)
+  s.params[0] = newNodeIT(nkNilLit, n.info, sym.typ.sons[0])
+  s.params[1] = n
+  pushStackFrame(c, s)
+  discard eval(c, sym.ast.sons[codePos])
+  result = s.params[0]
+  popStackFrame(c)
+  if cyclicTree(result): GlobalError(n.info, errCyclicTree)
+
+  dec(evalTemplateCounter)
+  
+proc evalExpandToAst(c: PEvalContext, original: PNode): PNode =
+  var
+    n = original.copyTree
+    macroCall = n.sons[1]
+    expandedSym = macroCall.sons[0].sym
+
+  for i in countup(1, macroCall.sonsLen - 1):
+    macroCall.sons[i] = evalAux(c, macroCall.sons[i], {})
+
+  case expandedSym.kind
+  of skTemplate:
+    result = evalTemplate(macroCall, expandedSym)
+  of skMacro:
+    # At this point macroCall.sons[0] is nkSym node.
+    # To be completely compatible with normal macro invocation,
+    # we want to replace it with nkIdent node featuring
+    # the original unmangled macro name.
+    macroCall.sons[0] = newIdentNode(expandedSym.name, expandedSym.info)
+    result = evalMacroCall(c, macroCall, expandedSym)
+  else:
+    InternalError(macroCall.info,
+      "ExpandToAst: expanded symbol is no macro or template")
+
 proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode = 
   var m = getMagic(n)
   case m
@@ -805,7 +902,8 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
   of mAppendSeqElem: result = evalAppendSeqElem(c, n)
   of mParseExprToAst: result = evalParseExpr(c, n)
   of mParseStmtToAst: result = evalParseStmt(c, n)
-  of mNLen: 
+  of mExpandToAst: result = evalExpandToAst(c, n)
+  of mNLen:
     result = evalAux(c, n.sons[1], {efLValue})
     if isSpecial(result): return 
     var a = result
@@ -1011,6 +1109,10 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
     if (a == b) or
         (b.kind in {nkNilLit, nkEmpty}) and (a.kind in {nkNilLit, nkEmpty}): 
       result.intVal = 1
+  of mNLineInfo:
+    result = evalAux(c, n.sons[1], {})
+    if isSpecial(result): return
+    result = newStrNodeT(result.info.toFileLineCol, n)
   of mAstToYaml:
     var ast = evalAux(c, n.sons[1], {efLValue})
     result = newStrNode(nkStrLit, ast.treeToYaml.ropeToStr)
diff --git a/compiler/msgs.nim b/compiler/msgs.nim
index d34c6b410..4f8a21f54 100755
--- a/compiler/msgs.nim
+++ b/compiler/msgs.nim
@@ -456,6 +456,12 @@ proc ToLinenumber*(info: TLineInfo): int {.inline.} =
 proc toColumn*(info: TLineInfo): int {.inline.} = 
   result = info.col
 
+proc toFileLine*(info: TLineInfo): string {.inline.} =
+  result = info.toFilename & ":" & $info.line
+
+proc toFileLineCol*(info: TLineInfo): string {.inline.} =
+  result = info.toFilename & "(" & $info.line & "," & $info.col & ")"
+
 var checkPoints: seq[TLineInfo] = @[]
 
 proc addCheckpoint*(info: TLineInfo) = 
diff --git a/compiler/sem.nim b/compiler/sem.nim
index dcbdac157..7b9f7c4e1 100755
--- a/compiler/sem.nim
+++ b/compiler/sem.nim
@@ -10,29 +10,15 @@
 # This module implements the semantic checking pass.
 
 import
-  strutils, hashes, lists, options, lexer, ast, astalgo, trees, treetab, 
-  wordrecg, ropes, msgs, os, condsyms, idents, renderer, types, platform, math, 
-  magicsys, parser, nversion, nimsets, semdata, evals, semfold, importer, 
+  strutils, hashes, lists, options, lexer, ast, astalgo, trees, treetab,
+  wordrecg, ropes, msgs, os, condsyms, idents, renderer, types, platform, math,
+  magicsys, parser, nversion, semdata, nimsets, semfold, importer,
   procfind, lookups, rodread, pragmas, passes, semtypinst, sigmatch, suggest,
-  semthreads, intsets, transf
+  semthreads, intsets, transf, evals
 
 proc semPass*(): TPass
 # implementation
 
-proc isTopLevel(c: PContext): bool {.inline.} = 
-  result = c.tab.tos <= 2
-
-proc newSymS(kind: TSymKind, n: PNode, c: PContext): PSym = 
-  result = newSym(kind, considerAcc(n), getCurrOwner())
-  result.info = n.info
-  
-proc semIdentVis(c: PContext, kind: TSymKind, n: PNode,
-                 allowed: TSymFlags): PSym
-  # identifier with visability
-proc semIdentWithPragma(c: PContext, kind: TSymKind, n: PNode, 
-                        allowed: TSymFlags): PSym
-proc semStmtScope(c: PContext, n: PNode): PNode
-
 type 
   TExprFlag = enum 
     efAllowType, efLValue, efWantIterator, efInTypeof
@@ -50,10 +36,36 @@ proc addResult(c: PContext, t: PType, info: TLineInfo)
 proc addResultNode(c: PContext, n: PNode)
 proc instGenericContainer(c: PContext, n: PNode, header: PType): PType
 
+proc typeMismatch(n: PNode, formal, actual: PType) = 
+  GlobalError(n.Info, errGenerated, msgKindToString(errTypeMismatch) &
+      typeToString(actual) & ") " &
+      `%`(msgKindToString(errButExpectedX), [typeToString(formal)]))
+
+proc fitNode(c: PContext, formal: PType, arg: PNode): PNode = 
+  result = IndexTypesMatch(c, formal, arg.typ, arg)
+  if result == nil:
+    typeMismatch(arg, formal, arg.typ)
+
+proc isTopLevel(c: PContext): bool {.inline.} = 
+  result = c.tab.tos <= 2
+
+proc newSymS(kind: TSymKind, n: PNode, c: PContext): PSym = 
+  result = newSym(kind, considerAcc(n), getCurrOwner())
+  result.info = n.info
+  
+proc semIdentVis(c: PContext, kind: TSymKind, n: PNode,
+                 allowed: TSymFlags): PSym
+  # identifier with visability
+proc semIdentWithPragma(c: PContext, kind: TSymKind, n: PNode, 
+                        allowed: TSymFlags): PSym
+proc semStmtScope(c: PContext, n: PNode): PNode
+
 proc ParamsTypeCheck(c: PContext, typ: PType) {.inline.} =
   if not typeAllowed(typ, skConst):
     GlobalError(typ.n.info, errXisNoType, typeToString(typ))
 
+include semtempl
+
 proc semConstExpr(c: PContext, n: PNode): PNode = 
   var e = semExprWithType(c, n)
   if e == nil: 
@@ -76,17 +88,7 @@ proc semAndEvalConstExpr(c: PContext, n: PNode): PNode =
   result = semConstExpr(c, n)
   
 include seminst, semcall
-  
-proc typeMismatch(n: PNode, formal, actual: PType) = 
-  GlobalError(n.Info, errGenerated, msgKindToString(errTypeMismatch) &
-      typeToString(actual) & ") " &
-      `%`(msgKindToString(errButExpectedX), [typeToString(formal)]))
-
-proc fitNode(c: PContext, formal: PType, arg: PNode): PNode = 
-  result = IndexTypesMatch(c, formal, arg.typ, arg)
-  if result == nil:
-    typeMismatch(arg, formal, arg.typ)
-  
+    
 proc semAfterMacroCall(c: PContext, n: PNode, s: PSym): PNode = 
   result = n
   case s.typ.sons[0].kind
@@ -101,28 +103,13 @@ proc semAfterMacroCall(c: PContext, n: PNode, s: PSym): PNode =
     result = semExpr(c, result)
     result = fitNode(c, s.typ.sons[0], result)
     #GlobalError(s.info, errInvalidParamKindX, typeToString(s.typ.sons[0]))
-  
-include "semtempl.nim"
 
 proc semMacroExpr(c: PContext, n: PNode, sym: PSym, 
                   semCheck: bool = true): PNode = 
-  inc(evalTemplateCounter)
-  if evalTemplateCounter > 100: 
-    GlobalError(n.info, errTemplateInstantiationTooNested)
   markUsed(n, sym)
   var p = newEvalContext(c.module, "", false)
-  var s = newStackFrame()
-  s.call = n
-  setlen(s.params, 2)
-  s.params[0] = newNodeIT(nkNilLit, n.info, sym.typ.sons[0])
-  s.params[1] = n
-  pushStackFrame(p, s)
-  discard eval(p, sym.ast.sons[codePos])
-  result = s.params[0]
-  popStackFrame(p)
-  if cyclicTree(result): GlobalError(n.info, errCyclicTree)
+  result = evalMacroCall(p, n, sym)
   if semCheck: result = semAfterMacroCall(c, result, sym)
-  dec(evalTemplateCounter)
 
 proc forceBool(c: PContext, n: PNode): PNode = 
   result = fitNode(c, getSysType(tyBool), n)
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index 356f1c196..54f0af9df 100755
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -904,26 +904,37 @@ proc expectStringArg(c: PContext, n: PNode, i: int): PNode =
   if result.kind notin {nkStrLit, nkRStrLit, nkTripleStrLit}:
     GlobalError(result.info, errStringLiteralExpected)
 
-proc semExpandMacroToAst(c: PContext, n: PNode, flags: TExprFlags): PNode =
+proc isAstValue(n: PNode): bool =
+  result = n.typ.sym.name.s in [ "expr", "stmt", "PNimrodNode" ]
+
+proc semExpandToAst(c: PContext, n: PNode, magicSym: PSym, flags: TExprFlags): PNode =
   if sonsLen(n) == 2:
     if not isCallExpr(n.sons[1]):
       GlobalError(n.info, errXisNoMacroOrTemplate, n.renderTree)
 
     var macroCall = n.sons[1]
 
-    var s = qualifiedLookup(c, macroCall.sons[0], {checkUndeclared})
-    if s == nil:
+    var expandedSym = qualifiedLookup(c, macroCall.sons[0], {checkUndeclared})
+    if expandedSym == nil:
       GlobalError(n.info, errUndeclaredIdentifier, macroCall.sons[0].renderTree)
 
-    var expanded : Pnode
+    if not (expandedSym.kind in { skMacro, skTemplate }):
+      GlobalError(n.info, errXisNoMacroOrTemplate, expandedSym.name.s)
 
-    case s.kind
-    of skMacro: expanded = semMacroExpr(c, macroCall, s, false)
-    of skTemplate: expanded = semTemplateExpr(c, macroCall, s, false)
-    else: GlobalError(n.info, errXisNoMacroOrTemplate, s.name.s)
+    macroCall.sons[0] = newNodeI(nkSym, macroCall.info)
+    macroCall.sons[0].sym = expandedSym
+    markUsed(n, expandedSym)
 
-    var macroRetType = newTypeS(s.typ.sons[0].kind, c)
-    result = newMetaNodeIT(expanded, n.info, macroRetType)
+    for i in countup(1, macroCall.sonsLen - 1):
+      macroCall.sons[i] = semExprWithType(c, macroCall.sons[i], {efAllowType})
+
+    # Preserve the magic symbol in order to handled in evals.nim
+    n.sons[0] = newNodeI(nkSym, n.info)
+    n.sons[0].sym = magicSym
+   
+    n.typ = expandedSym.getReturnType
+
+    result = n
   else:
     result = semDirectOp(c, n, flags)
 
@@ -963,7 +974,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
     else:
       result = semDirectOp(c, n, flags)
   of mSlurp: result = semSlurp(c, n, flags)
-  of mExpandMacroToAst: result = semExpandMacroToAst(c, n, flags)
+  of mExpandToAst: result = semExpandToAst(c, n, s, flags)
   else: result = semDirectOp(c, n, flags)
 
 proc semIfExpr(c: PContext, n: PNode): PNode = 
diff --git a/compiler/semfold.nim b/compiler/semfold.nim
index 77d84b6f8..d51f69bc1 100755
--- a/compiler/semfold.nim
+++ b/compiler/semfold.nim
@@ -206,7 +206,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode =
   of mNewString, mNewStringOfCap, 
      mExit, mInc, ast.mDec, mEcho, mAssert, mSwap, mAppendStrCh, 
      mAppendStrStr, mAppendSeqElem, mSetLengthStr, mSetLengthSeq, 
-     mParseExprToAst, mParseStmtToAst,
+     mParseExprToAst, mParseStmtToAst, mExpandToAst,
      mNLen..mNError, mEqRef: 
     nil
   else: InternalError(a.info, "evalOp(" & $m & ')')
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index c00b68bb5..243c2ce00 100755
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -428,7 +428,7 @@ proc semRaise(c: PContext, n: PNode): PNode =
     var typ = n.sons[0].typ
     if typ.kind != tyRef or typ.sons[0].kind != tyObject: 
       localError(n.info, errExprCannotBeRaised)
-  
+
 proc semTry(c: PContext, n: PNode): PNode = 
   result = n
   checkMinSonsLen(n, 2)
@@ -438,15 +438,19 @@ proc semTry(c: PContext, n: PNode): PNode =
     var a = n.sons[i]
     checkMinSonsLen(a, 1)
     var length = sonsLen(a)
-    if a.kind == nkExceptBranch: 
-      for j in countup(0, length - 2): 
+    if a.kind == nkExceptBranch:
+      if length == 2 and a.sons[0].kind == nkBracket:
+        a.sons[0..0] = a.sons[0].sons
+        length = a.sonsLen
+
+      for j in countup(0, length - 2):
         var typ = semTypeNode(c, a.sons[j], nil)
         if typ.kind == tyRef: typ = typ.sons[0]
-        if typ.kind != tyObject: 
+        if typ.kind != tyObject:
           GlobalError(a.sons[j].info, errExprCannotBeRaised)
         a.sons[j] = newNodeI(nkType, a.sons[j].info)
         a.sons[j].typ = typ
-        if ContainsOrIncl(check, typ.id): 
+        if ContainsOrIncl(check, typ.id):
           localError(a.sons[j].info, errExceptionAlreadyHandled)
     elif a.kind != nkFinally: 
       illFormedAst(n) 
diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim
index 295aaac03..ff2dd3bb1 100755
--- a/compiler/semtempl.nim
+++ b/compiler/semtempl.nim
@@ -33,22 +33,6 @@ proc isTypeDesc(n: PNode): bool =
     result = true
   else: result = false
   
-proc evalTemplateAux(c: PContext, templ, actual: PNode, sym: PSym): PNode = 
-  case templ.kind
-  of nkSym: 
-    var p = templ.sym
-    if (p.kind == skParam) and (p.owner.id == sym.id): 
-      result = copyTree(actual.sons[p.position])
-    else: 
-      result = copyNode(templ)
-  of nkNone..nkIdent, nkType..nkNilLit: # atom
-    result = copyNode(templ)
-  else: 
-    result = copyNode(templ)
-    newSons(result, sonsLen(templ))
-    for i in countup(0, sonsLen(templ) - 1): 
-      result.sons[i] = evalTemplateAux(c, templ.sons[i], actual, sym)
-  
 var evalTemplateCounter: int = 0
   # to prevend endless recursion in templates instantation
 
@@ -77,13 +61,13 @@ proc evalTemplateArgs(c: PContext, n: PNode, s: PSym): PNode =
       arg = fitNode(c, s.typ.sons[i], semExprWithType(c, arg))
     addSon(result, arg)
 
-proc evalTemplate(c: PContext, n: PNode, sym: PSym): PNode = 
+proc evalTemplate*(c: PContext, n: PNode, sym: PSym): PNode = 
   var args: PNode
   inc(evalTemplateCounter)
   if evalTemplateCounter <= 100: 
     # replace each param by the corresponding node:
     args = evalTemplateArgs(c, n, sym)
-    result = evalTemplateAux(c, sym.ast.sons[codePos], args, sym)
+    result = evalTemplateAux(sym.ast.sons[codePos], args, sym)
     dec(evalTemplateCounter)
   else:
     GlobalError(n.info, errTemplateInstantiationTooNested)
diff --git a/compiler/types.nim b/compiler/types.nim
index cc1281b6e..f02f5064a 100755
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -925,6 +925,11 @@ proc computeSize(typ: PType): biggestInt =
   var a: biggestInt = 1
   result = computeSizeAux(typ, a)
 
+proc getReturnType*(s: PSym): PType =
+  # Obtains the return type of a iterator/proc/macro/template
+  assert s.kind in { skProc, skTemplate, skMacro, skIterator }
+  result = s.typ.n.sons[0].typ
+
 proc getSize(typ: PType): biggestInt = 
   result = computeSize(typ)
   if result < 0: InternalError("getSize(" & $typ.kind & ')')
diff --git a/lib/core/macros.nim b/lib/core/macros.nim
index c5afcdf17..825979e27 100755
--- a/lib/core/macros.nim
+++ b/lib/core/macros.nim
@@ -36,8 +36,8 @@ type
     nnkMacroDef, nnkTemplateDef, nnkIteratorDef, nnkOfBranch, 

     nnkElifBranch, nnkExceptBranch, nnkElse, nnkMacroStmt, 

     nnkAsmStmt, nnkPragma, nnkIfStmt, nnkWhenStmt, 

-    nnkForStmt, nnkWhileStmt, nnkCaseStmt, 
-    nnkVarSection, nnkLetSection, nnkConstSection, 
+    nnkForStmt, nnkWhileStmt, nnkCaseStmt, 

+    nnkVarSection, nnkLetSection, nnkConstSection, 

     nnkConstDef, nnkTypeSection, nnkTypeDef, 

     nnkYieldStmt, nnkTryStmt, nnkFinally, nnkRaiseStmt, 

     nnkReturnStmt, nnkBreakStmt, nnkContinueStmt, nnkBlockStmt, 

@@ -45,8 +45,8 @@ type
     nnkIncludeStmt, nnkCommentStmt, nnkStmtListExpr, nnkBlockExpr, 

     nnkStmtListType, nnkBlockType, nnkTypeOfExpr, nnkObjectTy, 

     nnkTupleTy, nnkRecList, nnkRecCase, nnkRecWhen, 

-    nnkRefTy, nnkPtrTy, nnkVarTy, 
-    nnkConstTy, nnkMutableTy,
+    nnkRefTy, nnkPtrTy, nnkVarTy, 

+    nnkConstTy, nnkMutableTy,

     nnkDistinctTy, 

     nnkProcTy, nnkEnumTy, nnkEnumFieldDef, nnkReturnToken

   TNimNodeKinds* = set[TNimrodNodeKind]

@@ -187,6 +187,8 @@ proc toStrLit*(n: PNimrodNode): PNimrodNode {.compileTime.} =
   ## in a string literal node

   return newStrLitNode(repr(n))

 

+proc lineinfo*(n: PNimrodNode): string {.magic: "NLineInfo".}

+

 proc toLisp*(n: PNimrodNode): string {.compileTime.} =

   ## Convert the AST `n` to a human-readable string

   ##

@@ -228,11 +230,11 @@ proc parseStmt*(s: string): stmt {.magic: "ParseStmtToAst".}
   ## Compiles the passed string to its AST representation.

   ## Expects one or more statements.

 

-proc getAst*(macroOrTemplate: expr): expr {.magic: "ExpandMacroToAst".}

+proc getAst*(macroOrTemplate: expr): expr {.magic: "ExpandToAst".}

   ## Obtains the AST nodes returned from a macro or template invocation.

-  ## Example:
-  ## 
-  ## .. code-block:: nimrod
+  ## Example:

+  ## 

+  ## .. code-block:: nimrod

   ##

   ##   macro FooMacro() = 

   ##     var ast = getAst(BarTemplate())

diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim
index 232601640..ab9c31c8f 100755
--- a/lib/pure/terminal.nim
+++ b/lib/pure/terminal.nim
@@ -14,6 +14,8 @@
 ## Changing the style is permanent even after program termination! Use the

 ## code ``system.addQuitProc(resetAttributes)`` to restore the defaults.

 

+import macros

+

 when defined(windows):

   import windows, os

 

@@ -210,24 +212,32 @@ type
 

 when not defined(windows):

   var

+    # XXX: These better be thread-local

     gFG = 0

     gBG = 0

 

-proc WriteStyled*(txt: string, style: set[TStyle] = {styleBright}) =

-  ## writes the text `txt` in a given `style`.

+proc setStyle*(style: set[TStyle]) =

+  ## sets the terminal style

   when defined(windows):

     var a = 0'i16

     if styleBright in style: a = a or int16(FOREGROUND_INTENSITY)

     if styleBlink in style: a = a or int16(BACKGROUND_INTENSITY)

     if styleReverse in style: a = a or 0x4000'i16 # COMMON_LVB_REVERSE_VIDEO

     if styleUnderscore in style: a = a or 0x8000'i16 # COMMON_LVB_UNDERSCORE

-    var old = getAttributes()

     discard SetConsoleTextAttribute(conHandle, old or a)

-    stdout.write(txt)

-    discard SetConsoleTextAttribute(conHandle, old)

   else:

     for s in items(style):

       stdout.write("\e[" & $ord(s) & 'm')

+

+proc WriteStyled*(txt: string, style: set[TStyle] = {styleBright}) =

+  ## writes the text `txt` in a given `style`.

+  when defined(windows):

+    var old = getAttributes()

+    setStyle(style)

+    stdout.write(txt)

+    discard SetConsoleTextAttribute(conHandle, old)

+  else:

+    setStyle(style)

     stdout.write(txt)

     resetAttributes()

     if gFG != 0:

@@ -298,6 +308,24 @@ proc setBackgroundColor*(bg: TBackgroundColor, bright=false) =
     if bright: inc(gBG, 60)

     stdout.write("\e[" & $gBG & 'm')

 

+# XXX: 

+# These should be private, but there is no yet 

+# facility for binding local symbols within macros

+proc styledEchoProcessArg*(s: string)               = write stdout, s

+proc styledEchoProcessArg*(style: TStyle)           = setStyle {style}

+proc styledEchoProcessArg*(style: set[TStyle])      = setStyle style

+proc styledEchoProcessArg*(color: TForegroundColor) = setForeGroundColor color

+proc styledEchoProcessArg*(color: TBackgroundColor) = setBackGroundColor color

+

+macro styledEcho*(m: stmt): stmt =

+  result = newNimNode(nnkStmtList)

+

+  for i in countup(1, m.len - 1):

+    result.add(newCall(!"styledEchoProcessArg", m[i]))

+

+  result.add(newCall(!"write", newIdentNode("stdout"), newStrLitNode("\n")))

+  result.add(newCall(!"resetAttributes"))

+

 when isMainModule:

   system.addQuitProc(resetAttributes)

   write(stdout, "never mind")

diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim
new file mode 100644
index 000000000..db3e5a1db
--- /dev/null
+++ b/lib/pure/unittest.nim
@@ -0,0 +1,150 @@
+#

+#

+#            Nimrod's Runtime Library

+#        (c) Copyright 2011 Nimrod Contributors

+#

+#    See the file "copying.txt", included in this

+#    distribution, for details about the copyright.

+#

+

+## This module implements the standard unit testing facilities such as

+## suites, fixtures and test cases as well as facilities for combinatorial 

+## and randomzied test case generation (not yet available) 

+## and object mocking (not yet available)

+##

+## It is loosely based on C++'s boost.test and Haskell's QuickTest

+##

+## Maintainer: Zahary Karadjov (zah@github)

+##

+

+import

+  macros, terminal

+

+type

+  TestStatus* = enum OK, FAILED

+  # ETestFailed* = object of ESynch

+

+var 

+  # XXX: These better be thread-local

+  AbortOnError* = false

+  checkpoints: seq[string] = @[]

+

+template TestSetupIMPL*: stmt = nil

+template TestTeardownIMPL*: stmt = nil

+

+proc shouldRun(testName: string): bool =

+  result = true

+

+template suite*(name: expr, body: stmt): stmt =

+  block:

+    template setup(setupBody: stmt): stmt =

+      template TestSetupIMPL: stmt = setupBody

+

+    template teardown(teardownBody: stmt): stmt =

+      template TestTeardownIMPL: stmt = teardownBody

+

+    body

+

+proc printStatus*(s: TestStatus, name: string) =

+  var color = (if s == OK: fgGreen else: fgRed)

+  styledEcho styleBright, color, "[", $s, "] ", fgWhite, name, "\n"

+  

+template test*(name: expr, body: stmt): stmt =

+  if bind shouldRun(name):

+    bind checkpoints = @[]

+    var TestStatusIMPL = OK

+    

+    try:

+      TestSetupIMPL()

+      body

+

+    finally:

+      TestTeardownIMPL()

+      printStatus(TestStatusIMPL, name)

+

+proc checkpoint*(msg: string) =

+  checkpoints.add(msg)

+  # TODO: add support for something like SCOPED_TRACE from Google Test

+

+template fail* =

+  for msg in items(bind checkpoints):

+    echo msg

+

+  if AbortOnError: quit(1)

+  

+  TestStatusIMPL = FAILED

+  checkpoints = @[]

+

+macro check*(conditions: stmt): stmt =

+  proc standardRewrite(e: expr): stmt =

+    template rewrite(Exp, lineInfoLit: expr, expLit: string): stmt =

+      if not Exp:

+        checkpoint(lineInfoLit & ": Check failed: " & expLit)

+        fail()

+ 

+    result = getAst(rewrite(e, e.lineinfo, e.toStrLit))

+  

+  case conditions.kind

+  of nnkCall, nnkCommand, nnkMacroStmt:

+    case conditions[1].kind

+    of nnkInfix:

+      proc rewriteBinaryOp(op: expr): stmt =

+        template rewrite(op, left, right, lineInfoLit: expr, opLit, leftLit, rightLit: string): stmt =

+          block:

+            var 

+              lhs = left

+              rhs = right

+

+            if not `op`(lhs, rhs):

+              checkpoint(lineInfoLit & ": Check failed: " & opLit)

+              checkpoint("  " & leftLit & " was " & $lhs)

+              checkpoint("  " & rightLit & " was " & $rhs)

+              fail()

+

+        result = getAst(rewrite(

+          op[0], op[1], op[2],

+          op.lineinfo,

+          op.toStrLit,

+          op[1].toStrLit,

+          op[2].toStrLit))

+        

+      result = rewriteBinaryOp(conditions[1])

+  

+    of nnkCall, nnkCommand:

+      # TODO: We can print out the call arguments in case of failure

+      result = standardRewrite(conditions[1])

+

+    of nnkStmtList:

+      result = newNimNode(nnkStmtList)

+      for i in countup(0, conditions[1].len - 1):

+        result.add(newCall(!"check", conditions[1][i]))

+

+    else:

+      result = standardRewrite(conditions[1])

+

+  else:

+    error conditions.lineinfo & ": Malformed check statement"

+

+template require*(conditions: stmt): stmt =

+  block:

+    const AbortOnError = true    

+    check conditions

+

+macro expect*(exp: stmt): stmt =

+  template expectBody(errorTypes, lineInfoLit: expr, body: stmt): stmt =

+    try:

+      body

+      checkpoint(lineInfoLit & ": Expect Failed, no exception was thrown.")

+      fail()

+    except errorTypes:

+      nil

+

+  var expectCall = exp[0]

+  var body = exp[1]

+  

+  var errorTypes = newNimNode(nnkBracket)

+  for i in countup(1, expectCall.len - 1):

+    errorTypes.add(expectCall[i])

+

+  result = getAst(expectBody(errorTypes, exp.lineinfo, body))

+

diff --git a/lib/system.nim b/lib/system.nim
index 226642771..7a7a1c33b 100755
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -857,7 +857,6 @@ proc insert*[T](x: var seq[T], item: T, i = 0) {.noSideEffect.} =
     dec(j)
   x[i] = item
 
-
 proc repr*[T](x: T): string {.magic: "Repr", noSideEffect.}
   ## takes any Nimrod variable and returns its string representation. It
   ## works even for complex data graphs with cycles. This is a great
@@ -1922,6 +1921,26 @@ proc `[]`*(s: string, x: TSlice[int]): string {.inline.} =
   ## slice operation for strings. Negative indexes are supported.
   result = s.substr(x.a-|s, x.b-|s)
 
+template spliceImpl(x, start, endp, spliced: expr): stmt =
+  var 
+    count = endp - start + 1
+    shift = spliced.len - count
+    newLen = x.len + shift
+    totalShifted = x.len - (start + count)
+    firstShifted = newLen - totalShifted
+
+  if shift > 0:
+    setLen(x, newLen)
+
+  for i in countdown(newLen - 1, firstShifted):
+    shallowCopy(x[i], x[i-shift])
+
+  for c in countup(0, spliced.len - 1):
+    x[start + c] = spliced[c]
+
+  if shift < 0:
+    setLen(x, newLen)
+
 proc `[]=`*(s: var string, x: TSlice[int], b: string) = 
   ## slice assignment for strings. Negative indexes are supported.
   var a = x.a-|s
@@ -1929,7 +1948,7 @@ proc `[]=`*(s: var string, x: TSlice[int], b: string) =
   if L == b.len:
     for i in 0 .. <L: s[i+a] = b[i]
   else:
-    raise newException(EOutOfRange, "differing lengths for slice assignment")
+    spliceImpl(s, x.a, x.b, b)
 
 proc `[]`*[Idx, T](a: array[Idx, T], x: TSlice[int]): seq[T] =
   ## slice operation for arrays. Negative indexes are NOT supported because
@@ -1983,7 +2002,7 @@ proc `[]=`*[T](s: var seq[T], x: TSlice[int], b: openArray[T]) =
   if L == b.len:
     for i in 0 .. <L: s[i+a] = b[i]
   else:
-    raise newException(EOutOfRange, "differing lengths for slice assignment")
+    spliceImpl(s, x.a, x.b, b)
 
 proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo".}
   ## get type information for `x`. Ordinary code should not use this, but
diff --git a/tests/accept/run/tunit.nim b/tests/accept/run/tunit.nim
new file mode 100644
index 000000000..d0e975119
--- /dev/null
+++ b/tests/accept/run/tunit.nim
@@ -0,0 +1,47 @@
+import

+  unittest, macros

+

+var

+    a = 1

+    b = 22

+    c = 1

+    d = 3

+

+suite "my suite":

+  setup:

+    echo "suite setup"

+    var testVar = "from setup"

+    

+  teardown:

+    echo "suite teardown"

+

+  test "first suite test":

+    testVar = "modified"

+    echo "test var: " & testVar

+    check a > b

+

+  test "second suite test":

+    echo "test var: " & testVar

+

+proc foo: bool =

+  echo "running foo"

+  return true

+

+proc err =

+  raise newException(EArithmetic, "some exception")

+

+test "final test":

+  echo "inside suite-less test"

+

+  check:

+    a == c

+    foo()

+    d > 10

+

+test "arithmetic failure":

+  expect(EArithmetic):

+    err()

+

+  expect(EArithmetic, ESystem):

+    discard foo()

+