From eaeed1f8468d5e88ec6f447d68556eeb86ea22f6 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Mon, 3 Oct 2011 16:57:30 +0300 Subject: [PATCH 1/4] getAst works correctly with existing AST values as template/macro arguments --- compiler/evals.nim | 53 +++++++++++++++++++++++++++++-- compiler/sem.nim | 73 +++++++++++++++++++------------------------ compiler/semexprs.nim | 48 +++++++++++++++++++++------- compiler/semfold.nim | 2 +- compiler/semtempl.nim | 8 ++--- 5 files changed, 125 insertions(+), 59 deletions(-) diff --git a/compiler/evals.nim b/compiler/evals.nim index 1d443a4047..b7b3746a5f 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,59 @@ 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 evalMacroCall*(c: PEvalContext, n: PNode, sym: PSym): PNode = + 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) + +# XXX: +# These imports could be removed when the template evaluation code is extracted in a +# separate module. semdata is needed only for PContext (which is not wanted here, see below) +import + semdata, sem + +proc evalExpandToAst(c: PEvalContext, n: PNode): PNode = + var + macroCall = n.sons[1] + expandedSym = macroCall.sons[0].sym + + # XXX: It's unfortunate that evalTemplate requires a PContext, + # although it's used only for very specific corner cases. + # + # Template expansion should be about AST manipulation only, so + # maybe this requirement can be lifted. + dummyContext : PContext + + for i in countup(1, macroCall.sonsLen - 1): + macroCall.sons[i] = evalAux(c, macroCall.sons[i], {}) + + case expandedSym.kind + of skTemplate: + result = evalTemplate(dummyContext, macroCall, expandedSym) + of skMacro: + # XXX: + # At this point macroCall.sons[0] is nkSym node. + # To be completely compatible with normal macro invocation, + # we may want to replace it with nkIdent node featuring + # the original unmangled macro name. + 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,6 +853,7 @@ 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 mExpandMacroToAst: result = evalExpandToAst(c, n) of mNLen: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return diff --git a/compiler/sem.nim b/compiler/sem.nim index dcbdac157f..b7df930904 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,8 +103,6 @@ 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 = @@ -111,16 +111,7 @@ proc semMacroExpr(c: PContext, n: PNode, sym: PSym, 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) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 356f1c196d..976e57b467 100755 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -904,26 +904,52 @@ 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 semExpandMacroToAst(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) + # Any macro arguments that are already AST values are passed as such + # All other expressions within the arguments are converted to AST as + # in normal macro/template expansion. + # The actual expansion does not happen here, but in evals.nim, where + # the dynamic AST values will be known. + for i in countup(1, macroCall.sonsLen - 1): + var argAst = macroCall.sons[i] + var typedArg = semExprWithType(c, argAst, {efAllowType}) + if isAstValue(typedArg): + macroCall.sons[i] = typedArg + else: + macroCall.sons[i] = newMetaNodeIT(argAst, argAst.info, newTypeS(tyExpr, c)) + + # Preserve the magic symbol in order to handled in evals.nim + n.sons[0] = newNodeI(nkSym, n.info) + n.sons[0].sym = magicSym + + # XXX: + # Hmm, expandedSym.typ is something like proc (e: expr): stmt + # In theory, it should be better here to report the actual return type, + # but the code is working fine so far with tyStmt, so I am leaving it + # here for someone more knowledgable to see ;) + n.typ = newTypeS(tyStmt, c) # expandedSym.typ + + result = n else: result = semDirectOp(c, n, flags) @@ -963,7 +989,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 mExpandMacroToAst: result = semExpandMacroToAst(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 77d84b6f83..5e6e9ebc85 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, mExpandMacroToAst, mNLen..mNError, mEqRef: nil else: InternalError(a.info, "evalOp(" & $m & ')') diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 295aaac039..02aabd684f 100755 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -33,7 +33,7 @@ proc isTypeDesc(n: PNode): bool = result = true else: result = false -proc evalTemplateAux(c: PContext, templ, actual: PNode, sym: PSym): PNode = +proc evalTemplateAux(templ, actual: PNode, sym: PSym): PNode = case templ.kind of nkSym: var p = templ.sym @@ -47,7 +47,7 @@ proc evalTemplateAux(c: PContext, templ, actual: PNode, sym: PSym): PNode = 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) + result.sons[i] = evalTemplateAux(templ.sons[i], actual, sym) var evalTemplateCounter: int = 0 # to prevend endless recursion in templates instantation @@ -77,13 +77,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) From 22546c44d1a8c34077c7c24e0b565bc8900061d1 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Wed, 5 Oct 2011 16:03:24 +0300 Subject: [PATCH 2/4] Basic unit testing facilities (suites, fixtures, cases) Added: PNimrodNode.lineinfo for better error messages from macros Added: seq.splice For easier use from templates and macros, except stament now supports the list of exception types to be supplied in nkBraket node (array literal). --- compiler/ast.nim | 2 +- compiler/evals.nim | 11 ++- compiler/msgs.nim | 6 ++ compiler/semstmts.nim | 14 ++-- lib/core/macros.nim | 16 ++-- lib/pure/unittest.nim | 157 +++++++++++++++++++++++++++++++++++++ lib/system.nim | 21 +++++ tests/accept/run/tunit.nim | 47 +++++++++++ 8 files changed, 258 insertions(+), 16 deletions(-) create mode 100644 lib/pure/unittest.nim create mode 100644 tests/accept/run/tunit.nim diff --git a/compiler/ast.nim b/compiler/ast.nim index b33d99554c..e60a52fc6b 100755 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -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 b7b3746a5f..db15b03703 100755 --- a/compiler/evals.nim +++ b/compiler/evals.nim @@ -798,8 +798,9 @@ proc evalMacroCall*(c: PEvalContext, n: PNode, sym: PSym): PNode = import semdata, sem -proc evalExpandToAst(c: PEvalContext, n: PNode): PNode = - var +proc evalExpandToAst(c: PEvalContext, original: PNode): PNode = + var + n = original.copyTree macroCall = n.sons[1] expandedSym = macroCall.sons[0].sym @@ -854,7 +855,7 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode = of mParseExprToAst: result = evalParseExpr(c, n) of mParseStmtToAst: result = evalParseStmt(c, n) of mExpandMacroToAst: result = evalExpandToAst(c, n) - of mNLen: + of mNLen: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return var a = result @@ -1060,6 +1061,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 d34c6b4100..4f8a21f545 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/semstmts.nim b/compiler/semstmts.nim index c00b68bb5b..057c99e947 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.splice(0, 1, 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/lib/core/macros.nim b/lib/core/macros.nim index c5afcdf174..e61575f3e8 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 ## @@ -230,9 +232,9 @@ proc parseStmt*(s: string): stmt {.magic: "ParseStmtToAst".} proc getAst*(macroOrTemplate: expr): expr {.magic: "ExpandMacroToAst".} ## 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/unittest.nim b/lib/pure/unittest.nim new file mode 100644 index 0000000000..a5c97ee9b6 --- /dev/null +++ b/lib/pure/unittest.nim @@ -0,0 +1,157 @@ +# +# +# 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 + +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 + +template test*(name: expr, body: stmt): stmt = + if bind shouldRun(name): + bind checkpoints = @[] + var TestStatusIMPL = OK + + try: + TestSetupIMPL() + body + + finally: + TestTeardownIMPL() + echo "[" & $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() + + # XXX: If we don't create a string literal node below, the compiler + # will SEGFAULT in a rather strange fashion: + # + # rewrite(e, e.toStrLit, e.toStrLit) is ok, but + # + # rewrite(e, e.lineinfo, e.toStrLit) or + # rewrite(e, "anything", e.toStrLit) are not + # + # It may have something to do with the dummyContext hack in + # evals.nim/evalTemplate + # + result = getAst(rewrite(e, newStrLitNode(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], + newStrLitNode(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, newStrLitNode(exp.lineinfo), body)) + diff --git a/lib/system.nim b/lib/system.nim index 2266427711..b6f5243e52 100755 --- a/lib/system.nim +++ b/lib/system.nim @@ -857,6 +857,27 @@ proc insert*[T](x: var seq[T], item: T, i = 0) {.noSideEffect.} = dec(j) x[i] = item +template spliceImpl(x, start, count, elements: expr): stmt = + var + shift = elements.len - count + newLen = x.len + shift + totalShifted = x.len - (start + count) + firstShifted = newLen - totalShifted + + if shift > 0: + setLen(x, newLen) + + for i in countup(firstShifted, newLen - 1): + shallowCopy(x[i], x[i-shift]) + + for c in countup(0, elements.len - 1): + x[start + c] = elements[c] + + if shift < 0: + setLen(x, newLen) + +proc splice*[T](x: var seq[T], start, count: int, elements: openarray[T] = []) = + spliceImpl(x, start, count, elements) proc repr*[T](x: T): string {.magic: "Repr", noSideEffect.} ## takes any Nimrod variable and returns its string representation. It diff --git a/tests/accept/run/tunit.nim b/tests/accept/run/tunit.nim new file mode 100644 index 0000000000..d0e9751197 --- /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() + From e3deb5b502888fce847ef21b86bbfc7e91c7dea4 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Thu, 6 Oct 2011 03:55:02 +0300 Subject: [PATCH 3/4] Cleaned up the circular dependecies and remaining issues Changed: The []= operator for strings and sequences is now capable of splicing --- compiler/ast.nim | 2 +- compiler/evals.nim | 80 ++++++++++++++++++++++++++++++++++--------- compiler/sem.nim | 4 --- compiler/semexprs.nim | 25 +++----------- compiler/semfold.nim | 2 +- compiler/semstmts.nim | 2 +- compiler/semtempl.nim | 16 --------- compiler/types.nim | 5 +++ lib/core/macros.nim | 2 +- lib/pure/unittest.nim | 21 +++--------- lib/system.nim | 46 ++++++++++++------------- 11 files changed, 105 insertions(+), 100 deletions(-) diff --git a/compiler/ast.nim b/compiler/ast.nim index e60a52fc6b..0d920b8359 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, diff --git a/compiler/evals.nim b/compiler/evals.nim index db15b03703..d2559176e5 100755 --- a/compiler/evals.nim +++ b/compiler/evals.nim @@ -780,7 +780,66 @@ proc evalParseStmt(c: PEvalContext, n: PNode): PNode = 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) @@ -792,37 +851,26 @@ proc evalMacroCall*(c: PEvalContext, n: PNode, sym: PSym): PNode = popStackFrame(c) if cyclicTree(result): GlobalError(n.info, errCyclicTree) -# XXX: -# These imports could be removed when the template evaluation code is extracted in a -# separate module. semdata is needed only for PContext (which is not wanted here, see below) -import - semdata, sem + dec(evalTemplateCounter) proc evalExpandToAst(c: PEvalContext, original: PNode): PNode = var n = original.copyTree macroCall = n.sons[1] expandedSym = macroCall.sons[0].sym - - # XXX: It's unfortunate that evalTemplate requires a PContext, - # although it's used only for very specific corner cases. - # - # Template expansion should be about AST manipulation only, so - # maybe this requirement can be lifted. - dummyContext : PContext for i in countup(1, macroCall.sonsLen - 1): macroCall.sons[i] = evalAux(c, macroCall.sons[i], {}) case expandedSym.kind of skTemplate: - result = evalTemplate(dummyContext, macroCall, expandedSym) + result = evalTemplate(macroCall, expandedSym) of skMacro: - # XXX: # At this point macroCall.sons[0] is nkSym node. # To be completely compatible with normal macro invocation, - # we may want to replace it with nkIdent node featuring + # 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, @@ -854,7 +902,7 @@ 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 mExpandMacroToAst: result = evalExpandToAst(c, n) + of mExpandToAst: result = evalExpandToAst(c, n) of mNLen: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return diff --git a/compiler/sem.nim b/compiler/sem.nim index b7df930904..7b9f7c4e19 100755 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -106,14 +106,10 @@ proc semAfterMacroCall(c: PContext, n: PNode, s: PSym): PNode = 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) 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 976e57b467..54f0af9df3 100755 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -907,7 +907,7 @@ proc expectStringArg(c: PContext, n: PNode, i: int): PNode = proc isAstValue(n: PNode): bool = result = n.typ.sym.name.s in [ "expr", "stmt", "PNimrodNode" ] -proc semExpandMacroToAst(c: PContext, n: PNode, magicSym: PSym, flags: TExprFlags): PNode = +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) @@ -925,29 +925,14 @@ proc semExpandMacroToAst(c: PContext, n: PNode, magicSym: PSym, flags: TExprFlag macroCall.sons[0].sym = expandedSym markUsed(n, expandedSym) - # Any macro arguments that are already AST values are passed as such - # All other expressions within the arguments are converted to AST as - # in normal macro/template expansion. - # The actual expansion does not happen here, but in evals.nim, where - # the dynamic AST values will be known. for i in countup(1, macroCall.sonsLen - 1): - var argAst = macroCall.sons[i] - var typedArg = semExprWithType(c, argAst, {efAllowType}) - if isAstValue(typedArg): - macroCall.sons[i] = typedArg - else: - macroCall.sons[i] = newMetaNodeIT(argAst, argAst.info, newTypeS(tyExpr, c)) + 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 - - # XXX: - # Hmm, expandedSym.typ is something like proc (e: expr): stmt - # In theory, it should be better here to report the actual return type, - # but the code is working fine so far with tyStmt, so I am leaving it - # here for someone more knowledgable to see ;) - n.typ = newTypeS(tyStmt, c) # expandedSym.typ + + n.typ = expandedSym.getReturnType result = n else: @@ -989,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, s, 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 5e6e9ebc85..d51f69bc10 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, mExpandMacroToAst, + mParseExprToAst, mParseStmtToAst, mExpandToAst, mNLen..mNError, mEqRef: nil else: InternalError(a.info, "evalOp(" & $m & ')') diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 057c99e947..243c2ce003 100755 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -440,7 +440,7 @@ proc semTry(c: PContext, n: PNode): PNode = var length = sonsLen(a) if a.kind == nkExceptBranch: if length == 2 and a.sons[0].kind == nkBracket: - a.sons.splice(0, 1, a.sons[0].sons) + a.sons[0..0] = a.sons[0].sons length = a.sonsLen for j in countup(0, length - 2): diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 02aabd684f..ff2dd3bb1f 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(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) - var evalTemplateCounter: int = 0 # to prevend endless recursion in templates instantation diff --git a/compiler/types.nim b/compiler/types.nim index cc1281b6e0..f02f5064ab 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 e61575f3e8..825979e274 100755 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -230,7 +230,7 @@ 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: ## diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index a5c97ee9b6..2322ffd693 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -56,7 +56,7 @@ template test*(name: expr, body: stmt): stmt = finally: TestTeardownIMPL() - echo "[" & $TestStatusIMPL & "] " & name + echo "[" & $TestStatusIMPL & "] " & name & "\n" proc checkpoint*(msg: string) = checkpoints.add(msg) @@ -77,19 +77,8 @@ macro check*(conditions: stmt): stmt = if not Exp: checkpoint(lineInfoLit & ": Check failed: " & expLit) fail() - - # XXX: If we don't create a string literal node below, the compiler - # will SEGFAULT in a rather strange fashion: - # - # rewrite(e, e.toStrLit, e.toStrLit) is ok, but - # - # rewrite(e, e.lineinfo, e.toStrLit) or - # rewrite(e, "anything", e.toStrLit) are not - # - # It may have something to do with the dummyContext hack in - # evals.nim/evalTemplate - # - result = getAst(rewrite(e, newStrLitNode(e.lineinfo), e.toStrLit)) + + result = getAst(rewrite(e, e.lineinfo, e.toStrLit)) case conditions.kind of nnkCall, nnkCommand, nnkMacroStmt: @@ -110,7 +99,7 @@ macro check*(conditions: stmt): stmt = result = getAst(rewrite( op[0], op[1], op[2], - newStrLitNode(op.lineinfo), + op.lineinfo, op.toStrLit, op[1].toStrLit, op[2].toStrLit)) @@ -153,5 +142,5 @@ macro expect*(exp: stmt): stmt = for i in countup(1, expectCall.len - 1): errorTypes.add(expectCall[i]) - result = getAst(expectBody(errorTypes, newStrLitNode(exp.lineinfo), body)) + result = getAst(expectBody(errorTypes, exp.lineinfo, body)) diff --git a/lib/system.nim b/lib/system.nim index b6f5243e52..7a7a1c33b5 100755 --- a/lib/system.nim +++ b/lib/system.nim @@ -857,28 +857,6 @@ proc insert*[T](x: var seq[T], item: T, i = 0) {.noSideEffect.} = dec(j) x[i] = item -template spliceImpl(x, start, count, elements: expr): stmt = - var - shift = elements.len - count - newLen = x.len + shift - totalShifted = x.len - (start + count) - firstShifted = newLen - totalShifted - - if shift > 0: - setLen(x, newLen) - - for i in countup(firstShifted, newLen - 1): - shallowCopy(x[i], x[i-shift]) - - for c in countup(0, elements.len - 1): - x[start + c] = elements[c] - - if shift < 0: - setLen(x, newLen) - -proc splice*[T](x: var seq[T], start, count: int, elements: openarray[T] = []) = - spliceImpl(x, start, count, elements) - 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 @@ -1943,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 @@ -1950,7 +1948,7 @@ proc `[]=`*(s: var string, x: TSlice[int], b: string) = if L == b.len: for i in 0 .. Date: Fri, 7 Oct 2011 16:34:04 +0300 Subject: [PATCH 4/4] *Pimped up* the test runner with colorful output Added: terminal.styledEcho macro --- lib/pure/terminal.nim | 38 +++++++++++++++++++++++++++++++++----- lib/pure/unittest.nim | 8 ++++++-- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/lib/pure/terminal.nim b/lib/pure/terminal.nim index 232601640a..ab9c31c8f2 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 index 2322ffd693..db3e5a1db0 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -18,7 +18,7 @@ ## import - macros + macros, terminal type TestStatus* = enum OK, FAILED @@ -45,6 +45,10 @@ template suite*(name: expr, body: stmt): stmt = 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 = @[] @@ -56,7 +60,7 @@ template test*(name: expr, body: stmt): stmt = finally: TestTeardownIMPL() - echo "[" & $TestStatusIMPL & "] " & name & "\n" + printStatus(TestStatusIMPL, name) proc checkpoint*(msg: string) = checkpoints.add(msg)