Merge pull request #58 from zah/getast-unittests

getAst operational. Unit testing library based on it.
This commit is contained in:
Araq
2011-10-07 10:07:18 -07:00
14 changed files with 446 additions and 101 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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) =

View File

@@ -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)

View File

@@ -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 =

View File

@@ -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 & ')')

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 & ')')

View File

@@ -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())

View File

@@ -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")

150
lib/pure/unittest.nim Normal file
View File

@@ -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))

View File

@@ -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

View File

@@ -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()