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
commit fae8ea0cee
14 changed files with 446 additions and 101 deletions

View file

@ -327,7 +327,7 @@ type
TMagic* = enum # symbols that require compiler magic: TMagic* = enum # symbols that require compiler magic:
mNone, mDefined, mDefinedInScope, mLow, mHigh, mSizeOf, mIs, mOf, mNone, mDefined, mDefinedInScope, mLow, mHigh, mSizeOf, mIs, mOf,
mEcho, mShallowCopy, mSlurp, mEcho, mShallowCopy, mSlurp,
mAstToYaml, mParseExprToAst, mParseStmtToAst, mExpandMacroToAst, mAstToYaml, mParseExprToAst, mParseStmtToAst, mExpandToAst,
mUnaryLt, mSucc, mUnaryLt, mSucc,
mPred, mInc, mDec, mOrd, mNew, mNewFinalize, mNewSeq, mLengthOpenArray, mPred, mInc, mDec, mOrd, mNew, mNewFinalize, mNewSeq, mLengthOpenArray,
mLengthStr, mLengthArray, mLengthSeq, mIncl, mExcl, mCard, mChr, mGCref, mLengthStr, mLengthArray, mLengthSeq, mIncl, mExcl, mCard, mChr, mGCref,
@ -369,7 +369,7 @@ type
mCompileOption, mCompileOptionArg, mCompileOption, mCompileOptionArg,
mNLen, mNChild, mNSetChild, mNAdd, mNAddMultiple, mNDel, mNKind, mNLen, mNChild, mNSetChild, mNAdd, mNAddMultiple, mNDel, mNKind,
mNIntVal, mNFloatVal, mNSymbol, mNIdent, mNGetType, mNStrVal, mNSetIntVal, mNIntVal, mNFloatVal, mNSymbol, mNIdent, mNGetType, mNStrVal, mNSetIntVal,
mNSetFloatVal, mNSetSymbol, mNSetIdent, mNSetType, mNSetStrVal, mNSetFloatVal, mNSetSymbol, mNSetIdent, mNSetType, mNSetStrVal, mNLineInfo,
mNNewNimNode, mNCopyNimNode, mNCopyNimTree, mStrToIdent, mIdentToStr, mNNewNimNode, mNCopyNimNode, mNCopyNimTree, mStrToIdent, mIdentToStr,
mEqIdent, mEqNimrodNode, mNHint, mNWarning, mNError, mGetTypeInfo mEqIdent, mEqNimrodNode, mNHint, mNWarning, mNError, mGetTypeInfo

View file

@ -65,6 +65,7 @@ proc popStackFrame*(c: PEvalContext) {.inline.} =
if (c.tos == nil): InternalError("popStackFrame") if (c.tos == nil): InternalError("popStackFrame")
c.tos = c.tos.next c.tos = c.tos.next
proc eval*(c: PEvalContext, n: PNode): PNode
proc evalAux(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode proc evalAux(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode
proc stackTraceAux(x: PStackFrame) = proc stackTraceAux(x: PStackFrame) =
@ -764,7 +765,7 @@ proc isEmpty(n: PNode): bool =
proc stringStartingLine(s: PNode): int = proc stringStartingLine(s: PNode): int =
result = s.info.line - countLines(s.strVal) 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 code = evalAux(c, n.sons[1], {})
var ast = parseString(code.getStrValue, code.info.toFilename, var ast = parseString(code.getStrValue, code.info.toFilename,
code.stringStartingLine) code.stringStartingLine)
@ -773,12 +774,108 @@ proc evalParseExpr(c: PEvalContext, n: Pnode): Pnode =
result = ast.sons[0] result = ast.sons[0]
result.typ = newType(tyExpr, c.module) 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], {}) var code = evalAux(c, n.sons[1], {})
result = parseString(code.getStrValue, code.info.toFilename, result = parseString(code.getStrValue, code.info.toFilename,
code.stringStartingLine) code.stringStartingLine)
result.typ = newType(tyStmt, c.module) 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 = proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
var m = getMagic(n) var m = getMagic(n)
case m case m
@ -805,7 +902,8 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
of mAppendSeqElem: result = evalAppendSeqElem(c, n) of mAppendSeqElem: result = evalAppendSeqElem(c, n)
of mParseExprToAst: result = evalParseExpr(c, n) of mParseExprToAst: result = evalParseExpr(c, n)
of mParseStmtToAst: result = evalParseStmt(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}) result = evalAux(c, n.sons[1], {efLValue})
if isSpecial(result): return if isSpecial(result): return
var a = result var a = result
@ -1011,6 +1109,10 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode =
if (a == b) or if (a == b) or
(b.kind in {nkNilLit, nkEmpty}) and (a.kind in {nkNilLit, nkEmpty}): (b.kind in {nkNilLit, nkEmpty}) and (a.kind in {nkNilLit, nkEmpty}):
result.intVal = 1 result.intVal = 1
of mNLineInfo:
result = evalAux(c, n.sons[1], {})
if isSpecial(result): return
result = newStrNodeT(result.info.toFileLineCol, n)
of mAstToYaml: of mAstToYaml:
var ast = evalAux(c, n.sons[1], {efLValue}) var ast = evalAux(c, n.sons[1], {efLValue})
result = newStrNode(nkStrLit, ast.treeToYaml.ropeToStr) result = newStrNode(nkStrLit, ast.treeToYaml.ropeToStr)

View file

@ -456,6 +456,12 @@ proc ToLinenumber*(info: TLineInfo): int {.inline.} =
proc toColumn*(info: TLineInfo): int {.inline.} = proc toColumn*(info: TLineInfo): int {.inline.} =
result = info.col 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] = @[] var checkPoints: seq[TLineInfo] = @[]
proc addCheckpoint*(info: TLineInfo) = proc addCheckpoint*(info: TLineInfo) =

View file

@ -10,29 +10,15 @@
# This module implements the semantic checking pass. # This module implements the semantic checking pass.
import import
strutils, hashes, lists, options, lexer, ast, astalgo, trees, treetab, strutils, hashes, lists, options, lexer, ast, astalgo, trees, treetab,
wordrecg, ropes, msgs, os, condsyms, idents, renderer, types, platform, math, wordrecg, ropes, msgs, os, condsyms, idents, renderer, types, platform, math,
magicsys, parser, nversion, nimsets, semdata, evals, semfold, importer, magicsys, parser, nversion, semdata, nimsets, semfold, importer,
procfind, lookups, rodread, pragmas, passes, semtypinst, sigmatch, suggest, procfind, lookups, rodread, pragmas, passes, semtypinst, sigmatch, suggest,
semthreads, intsets, transf semthreads, intsets, transf, evals
proc semPass*(): TPass proc semPass*(): TPass
# implementation # 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 type
TExprFlag = enum TExprFlag = enum
efAllowType, efLValue, efWantIterator, efInTypeof efAllowType, efLValue, efWantIterator, efInTypeof
@ -50,10 +36,36 @@ proc addResult(c: PContext, t: PType, info: TLineInfo)
proc addResultNode(c: PContext, n: PNode) proc addResultNode(c: PContext, n: PNode)
proc instGenericContainer(c: PContext, n: PNode, header: PType): PType 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.} = proc ParamsTypeCheck(c: PContext, typ: PType) {.inline.} =
if not typeAllowed(typ, skConst): if not typeAllowed(typ, skConst):
GlobalError(typ.n.info, errXisNoType, typeToString(typ)) GlobalError(typ.n.info, errXisNoType, typeToString(typ))
include semtempl
proc semConstExpr(c: PContext, n: PNode): PNode = proc semConstExpr(c: PContext, n: PNode): PNode =
var e = semExprWithType(c, n) var e = semExprWithType(c, n)
if e == nil: if e == nil:
@ -76,17 +88,7 @@ proc semAndEvalConstExpr(c: PContext, n: PNode): PNode =
result = semConstExpr(c, n) result = semConstExpr(c, n)
include seminst, semcall 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 = proc semAfterMacroCall(c: PContext, n: PNode, s: PSym): PNode =
result = n result = n
case s.typ.sons[0].kind case s.typ.sons[0].kind
@ -101,28 +103,13 @@ proc semAfterMacroCall(c: PContext, n: PNode, s: PSym): PNode =
result = semExpr(c, result) result = semExpr(c, result)
result = fitNode(c, s.typ.sons[0], result) result = fitNode(c, s.typ.sons[0], result)
#GlobalError(s.info, errInvalidParamKindX, typeToString(s.typ.sons[0])) #GlobalError(s.info, errInvalidParamKindX, typeToString(s.typ.sons[0]))
include "semtempl.nim"
proc semMacroExpr(c: PContext, n: PNode, sym: PSym, proc semMacroExpr(c: PContext, n: PNode, sym: PSym,
semCheck: bool = true): PNode = semCheck: bool = true): PNode =
inc(evalTemplateCounter)
if evalTemplateCounter > 100:
GlobalError(n.info, errTemplateInstantiationTooNested)
markUsed(n, sym) markUsed(n, sym)
var p = newEvalContext(c.module, "", false) var p = newEvalContext(c.module, "", false)
var s = newStackFrame() result = evalMacroCall(p, n, sym)
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)
if semCheck: result = semAfterMacroCall(c, result, sym) if semCheck: result = semAfterMacroCall(c, result, sym)
dec(evalTemplateCounter)
proc forceBool(c: PContext, n: PNode): PNode = proc forceBool(c: PContext, n: PNode): PNode =
result = fitNode(c, getSysType(tyBool), n) 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}: if result.kind notin {nkStrLit, nkRStrLit, nkTripleStrLit}:
GlobalError(result.info, errStringLiteralExpected) 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 sonsLen(n) == 2:
if not isCallExpr(n.sons[1]): if not isCallExpr(n.sons[1]):
GlobalError(n.info, errXisNoMacroOrTemplate, n.renderTree) GlobalError(n.info, errXisNoMacroOrTemplate, n.renderTree)
var macroCall = n.sons[1] var macroCall = n.sons[1]
var s = qualifiedLookup(c, macroCall.sons[0], {checkUndeclared}) var expandedSym = qualifiedLookup(c, macroCall.sons[0], {checkUndeclared})
if s == nil: if expandedSym == nil:
GlobalError(n.info, errUndeclaredIdentifier, macroCall.sons[0].renderTree) 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 macroCall.sons[0] = newNodeI(nkSym, macroCall.info)
of skMacro: expanded = semMacroExpr(c, macroCall, s, false) macroCall.sons[0].sym = expandedSym
of skTemplate: expanded = semTemplateExpr(c, macroCall, s, false) markUsed(n, expandedSym)
else: GlobalError(n.info, errXisNoMacroOrTemplate, s.name.s)
var macroRetType = newTypeS(s.typ.sons[0].kind, c) for i in countup(1, macroCall.sonsLen - 1):
result = newMetaNodeIT(expanded, n.info, macroRetType) 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: else:
result = semDirectOp(c, n, flags) result = semDirectOp(c, n, flags)
@ -963,7 +974,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
else: else:
result = semDirectOp(c, n, flags) result = semDirectOp(c, n, flags)
of mSlurp: result = semSlurp(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) else: result = semDirectOp(c, n, flags)
proc semIfExpr(c: PContext, n: PNode): PNode = 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, of mNewString, mNewStringOfCap,
mExit, mInc, ast.mDec, mEcho, mAssert, mSwap, mAppendStrCh, mExit, mInc, ast.mDec, mEcho, mAssert, mSwap, mAppendStrCh,
mAppendStrStr, mAppendSeqElem, mSetLengthStr, mSetLengthSeq, mAppendStrStr, mAppendSeqElem, mSetLengthStr, mSetLengthSeq,
mParseExprToAst, mParseStmtToAst, mParseExprToAst, mParseStmtToAst, mExpandToAst,
mNLen..mNError, mEqRef: mNLen..mNError, mEqRef:
nil nil
else: InternalError(a.info, "evalOp(" & $m & ')') 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 var typ = n.sons[0].typ
if typ.kind != tyRef or typ.sons[0].kind != tyObject: if typ.kind != tyRef or typ.sons[0].kind != tyObject:
localError(n.info, errExprCannotBeRaised) localError(n.info, errExprCannotBeRaised)
proc semTry(c: PContext, n: PNode): PNode = proc semTry(c: PContext, n: PNode): PNode =
result = n result = n
checkMinSonsLen(n, 2) checkMinSonsLen(n, 2)
@ -438,15 +438,19 @@ proc semTry(c: PContext, n: PNode): PNode =
var a = n.sons[i] var a = n.sons[i]
checkMinSonsLen(a, 1) checkMinSonsLen(a, 1)
var length = sonsLen(a) var length = sonsLen(a)
if a.kind == nkExceptBranch: if a.kind == nkExceptBranch:
for j in countup(0, length - 2): 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) var typ = semTypeNode(c, a.sons[j], nil)
if typ.kind == tyRef: typ = typ.sons[0] if typ.kind == tyRef: typ = typ.sons[0]
if typ.kind != tyObject: if typ.kind != tyObject:
GlobalError(a.sons[j].info, errExprCannotBeRaised) GlobalError(a.sons[j].info, errExprCannotBeRaised)
a.sons[j] = newNodeI(nkType, a.sons[j].info) a.sons[j] = newNodeI(nkType, a.sons[j].info)
a.sons[j].typ = typ a.sons[j].typ = typ
if ContainsOrIncl(check, typ.id): if ContainsOrIncl(check, typ.id):
localError(a.sons[j].info, errExceptionAlreadyHandled) localError(a.sons[j].info, errExceptionAlreadyHandled)
elif a.kind != nkFinally: elif a.kind != nkFinally:
illFormedAst(n) illFormedAst(n)

View file

@ -33,22 +33,6 @@ proc isTypeDesc(n: PNode): bool =
result = true result = true
else: result = false 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 var evalTemplateCounter: int = 0
# to prevend endless recursion in templates instantation # 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)) arg = fitNode(c, s.typ.sons[i], semExprWithType(c, arg))
addSon(result, 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 var args: PNode
inc(evalTemplateCounter) inc(evalTemplateCounter)
if evalTemplateCounter <= 100: if evalTemplateCounter <= 100:
# replace each param by the corresponding node: # replace each param by the corresponding node:
args = evalTemplateArgs(c, n, sym) args = evalTemplateArgs(c, n, sym)
result = evalTemplateAux(c, sym.ast.sons[codePos], args, sym) result = evalTemplateAux(sym.ast.sons[codePos], args, sym)
dec(evalTemplateCounter) dec(evalTemplateCounter)
else: else:
GlobalError(n.info, errTemplateInstantiationTooNested) GlobalError(n.info, errTemplateInstantiationTooNested)

View file

@ -925,6 +925,11 @@ proc computeSize(typ: PType): biggestInt =
var a: biggestInt = 1 var a: biggestInt = 1
result = computeSizeAux(typ, a) 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 = proc getSize(typ: PType): biggestInt =
result = computeSize(typ) result = computeSize(typ)
if result < 0: InternalError("getSize(" & $typ.kind & ')') if result < 0: InternalError("getSize(" & $typ.kind & ')')

View file

@ -36,8 +36,8 @@ type
nnkMacroDef, nnkTemplateDef, nnkIteratorDef, nnkOfBranch, nnkMacroDef, nnkTemplateDef, nnkIteratorDef, nnkOfBranch,
nnkElifBranch, nnkExceptBranch, nnkElse, nnkMacroStmt, nnkElifBranch, nnkExceptBranch, nnkElse, nnkMacroStmt,
nnkAsmStmt, nnkPragma, nnkIfStmt, nnkWhenStmt, nnkAsmStmt, nnkPragma, nnkIfStmt, nnkWhenStmt,
nnkForStmt, nnkWhileStmt, nnkCaseStmt, nnkForStmt, nnkWhileStmt, nnkCaseStmt,
nnkVarSection, nnkLetSection, nnkConstSection, nnkVarSection, nnkLetSection, nnkConstSection,
nnkConstDef, nnkTypeSection, nnkTypeDef, nnkConstDef, nnkTypeSection, nnkTypeDef,
nnkYieldStmt, nnkTryStmt, nnkFinally, nnkRaiseStmt, nnkYieldStmt, nnkTryStmt, nnkFinally, nnkRaiseStmt,
nnkReturnStmt, nnkBreakStmt, nnkContinueStmt, nnkBlockStmt, nnkReturnStmt, nnkBreakStmt, nnkContinueStmt, nnkBlockStmt,
@ -45,8 +45,8 @@ type
nnkIncludeStmt, nnkCommentStmt, nnkStmtListExpr, nnkBlockExpr, nnkIncludeStmt, nnkCommentStmt, nnkStmtListExpr, nnkBlockExpr,
nnkStmtListType, nnkBlockType, nnkTypeOfExpr, nnkObjectTy, nnkStmtListType, nnkBlockType, nnkTypeOfExpr, nnkObjectTy,
nnkTupleTy, nnkRecList, nnkRecCase, nnkRecWhen, nnkTupleTy, nnkRecList, nnkRecCase, nnkRecWhen,
nnkRefTy, nnkPtrTy, nnkVarTy, nnkRefTy, nnkPtrTy, nnkVarTy,
nnkConstTy, nnkMutableTy, nnkConstTy, nnkMutableTy,
nnkDistinctTy, nnkDistinctTy,
nnkProcTy, nnkEnumTy, nnkEnumFieldDef, nnkReturnToken nnkProcTy, nnkEnumTy, nnkEnumFieldDef, nnkReturnToken
TNimNodeKinds* = set[TNimrodNodeKind] TNimNodeKinds* = set[TNimrodNodeKind]
@ -187,6 +187,8 @@ proc toStrLit*(n: PNimrodNode): PNimrodNode {.compileTime.} =
## in a string literal node ## in a string literal node
return newStrLitNode(repr(n)) return newStrLitNode(repr(n))
proc lineinfo*(n: PNimrodNode): string {.magic: "NLineInfo".}
proc toLisp*(n: PNimrodNode): string {.compileTime.} = proc toLisp*(n: PNimrodNode): string {.compileTime.} =
## Convert the AST `n` to a human-readable string ## 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. ## Compiles the passed string to its AST representation.
## Expects one or more statements. ## 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. ## Obtains the AST nodes returned from a macro or template invocation.
## Example: ## Example:
## ##
## .. code-block:: nimrod ## .. code-block:: nimrod
## ##
## macro FooMacro() = ## macro FooMacro() =
## var ast = getAst(BarTemplate()) ## var ast = getAst(BarTemplate())

View file

@ -14,6 +14,8 @@
## Changing the style is permanent even after program termination! Use the ## Changing the style is permanent even after program termination! Use the
## code ``system.addQuitProc(resetAttributes)`` to restore the defaults. ## code ``system.addQuitProc(resetAttributes)`` to restore the defaults.
import macros
when defined(windows): when defined(windows):
import windows, os import windows, os
@ -210,24 +212,32 @@ type
when not defined(windows): when not defined(windows):
var var
# XXX: These better be thread-local
gFG = 0 gFG = 0
gBG = 0 gBG = 0
proc WriteStyled*(txt: string, style: set[TStyle] = {styleBright}) = proc setStyle*(style: set[TStyle]) =
## writes the text `txt` in a given `style`. ## sets the terminal style
when defined(windows): when defined(windows):
var a = 0'i16 var a = 0'i16
if styleBright in style: a = a or int16(FOREGROUND_INTENSITY) if styleBright in style: a = a or int16(FOREGROUND_INTENSITY)
if styleBlink in style: a = a or int16(BACKGROUND_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 styleReverse in style: a = a or 0x4000'i16 # COMMON_LVB_REVERSE_VIDEO
if styleUnderscore in style: a = a or 0x8000'i16 # COMMON_LVB_UNDERSCORE if styleUnderscore in style: a = a or 0x8000'i16 # COMMON_LVB_UNDERSCORE
var old = getAttributes()
discard SetConsoleTextAttribute(conHandle, old or a) discard SetConsoleTextAttribute(conHandle, old or a)
stdout.write(txt)
discard SetConsoleTextAttribute(conHandle, old)
else: else:
for s in items(style): for s in items(style):
stdout.write("\e[" & $ord(s) & 'm') 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) stdout.write(txt)
resetAttributes() resetAttributes()
if gFG != 0: if gFG != 0:
@ -298,6 +308,24 @@ proc setBackgroundColor*(bg: TBackgroundColor, bright=false) =
if bright: inc(gBG, 60) if bright: inc(gBG, 60)
stdout.write("\e[" & $gBG & 'm') 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: when isMainModule:
system.addQuitProc(resetAttributes) system.addQuitProc(resetAttributes)
write(stdout, "never mind") 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) dec(j)
x[i] = item x[i] = item
proc repr*[T](x: T): string {.magic: "Repr", noSideEffect.} proc repr*[T](x: T): string {.magic: "Repr", noSideEffect.}
## takes any Nimrod variable and returns its string representation. It ## takes any Nimrod variable and returns its string representation. It
## works even for complex data graphs with cycles. This is a great ## 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. ## slice operation for strings. Negative indexes are supported.
result = s.substr(x.a-|s, x.b-|s) 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) = proc `[]=`*(s: var string, x: TSlice[int], b: string) =
## slice assignment for strings. Negative indexes are supported. ## slice assignment for strings. Negative indexes are supported.
var a = x.a-|s var a = x.a-|s
@ -1929,7 +1948,7 @@ proc `[]=`*(s: var string, x: TSlice[int], b: string) =
if L == b.len: if L == b.len:
for i in 0 .. <L: s[i+a] = b[i] for i in 0 .. <L: s[i+a] = b[i]
else: 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] = proc `[]`*[Idx, T](a: array[Idx, T], x: TSlice[int]): seq[T] =
## slice operation for arrays. Negative indexes are NOT supported because ## 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: if L == b.len:
for i in 0 .. <L: s[i+a] = b[i] for i in 0 .. <L: s[i+a] = b[i]
else: else:
raise newException(EOutOfRange, "differing lengths for slice assignment") spliceImpl(s, x.a, x.b, b)
proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo".} proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo".}
## get type information for `x`. Ordinary code should not use this, but ## 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()