From 7e44015491d4002be3c80cb7d6797e4c63651fbe Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Sat, 29 Sep 2012 16:49:04 +0300 Subject: implemented return type inference Other fixes: * bind once is now the default for type classes as documented in the manual * fixes an issue in template overloading (erroneous ambiguity when different typedesc params were used) --- compiler/ast.nim | 2 ++ compiler/semexprs.nim | 16 ++++++++++++++-- compiler/seminst.nim | 11 ++++++++--- compiler/semtypes.nim | 19 +++++++++---------- compiler/sigmatch.nim | 7 ++++++- compiler/types.nim | 4 ++-- 6 files changed, 41 insertions(+), 18 deletions(-) (limited to 'compiler') diff --git a/compiler/ast.nim b/compiler/ast.nim index 814784029..c6e4d8318 100755 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -350,6 +350,8 @@ type # pass of semProcTypeNode performed after instantiation. # this won't be needed if we don't perform this redundant # second pass (stay tuned). + tfRetType # marks return types in proc (used to detect type classes + # used as return types for return type inference) tfAll, # type class requires all constraints to be met (default) tfAny, # type class requires any constraint to be met tfCapturesEnv, # whether proc really captures some environment diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 7c147e778..4608d38ef 100755 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -1048,8 +1048,20 @@ proc semAsgn(c: PContext, n: PNode): PNode = localError(a.info, errXCannotBeAssignedTo, renderTree(a, {renderNoComments})) else: - n.sons[1] = semExprWithType(c, n.sons[1]) - n.sons[1] = fitNode(c, le, n.sons[1]) + var + rhs = semExprWithType(c, n.sons[1]) + lhs = n.sons[0] + if lhs.kind == nkSym and lhs.sym.kind == skResult and + lhs.sym.typ.kind == tyGenericParam: + if matchTypeClass(lhs.typ, rhs.typ): + InternalAssert c.p.resultSym != nil + lhs.typ = rhs.typ + c.p.resultSym.typ = rhs.typ + c.p.owner.typ.sons[0] = rhs.typ + else: + typeMismatch(n, lhs.typ, rhs.typ) + + n.sons[1] = fitNode(c, le, rhs) fixAbstractType(c, n) asgnToResultVar(c, n, n.sons[0], n.sons[1]) result = n diff --git a/compiler/seminst.nim b/compiler/seminst.nim index 61210c0f8..8e164531a 100755 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -25,8 +25,13 @@ proc instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable, s.flags = s.flags + {sfUsed, sfFromGeneric} var t = PType(IdTableGet(pt, q.typ)) if t == nil: - LocalError(a.info, errCannotInstantiateX, s.name.s) - t = errorType(c) + if tfRetType in q.typ.flags: + # keep the generic type and allow the return type to be bound + # later by semAsgn in return type inference scenario + t = q.typ + else: + LocalError(a.info, errCannotInstantiateX, s.name.s) + t = errorType(c) elif t.kind == tyGenericParam: InternalError(a.info, "instantiateGenericParamList: " & q.name.s) elif t.kind == tyGenericInvokation: @@ -163,7 +168,6 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, result.typ = newTypeS(tyProc, c) rawAddSon(result.typ, nil) result.typ.callConv = fn.typ.callConv - ParamsTypeCheck(c, result.typ) var oldPrc = GenericCacheGet(c, entry) if oldPrc == nil: c.generics.generics.add(entry) @@ -174,6 +178,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, if fn.kind != skTemplate: instantiateBody(c, n, result) sideEffectsCheck(c, result) + ParamsTypeCheck(c, result.typ) else: result = oldPrc popInfoContext() diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 5362d6d4a..eeb48a647 100755 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -584,22 +584,20 @@ proc paramTypeClass(c: PContext, paramType: PType, procKind: TSymKind): result.typ = newTypeS(tyTypeDesc, c) result.typ.sons = paramType.sons of tyDistinct: - # type T1 = distinct expr - # type S1 = distinct Sortable - # proc x(a, b: T1, c, d: S1) - # This forces bindOnce behavior for the type class, equivalent to - # proc x[T, S](a, b: T, c, d: S) result = paramTypeClass(c, paramType.lastSon, procKind) - result.id = paramType.sym.name + # disable the bindOnce behavior for the type class + result.id = nil + return of tyGenericBody: # type Foo[T] = object # proc x(a: Foo, b: Foo) result.typ = newTypeS(tyTypeClass, c) result.typ.addSonSkipIntLit(paramType) - result.id = paramType.sym.name # bindOnce by default of tyTypeClass: result.typ = copyType(paramType, getCurrOwner(), false) else: nil + # bindOnce by default + if paramType.sym != nil: result.id = paramType.sym.name proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, paramType: PType, paramName: string, @@ -619,7 +617,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, let s = SymtabGet(c.tab, paramTypId) # tests/run/tinterf triggers this: if s != nil: result = s.typ - else: + else: LocalError(info, errCannotInstantiateX, paramName) result = errorType(c) else: @@ -684,8 +682,8 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, if skipTypes(typ, {tyGenericInst}).kind == tyEmpty: continue for j in countup(0, length-3): var arg = newSymG(skParam, a.sons[j], c) - var finalType = liftParamType(c, kind, genericParams, typ, arg.name.s, - arg.info).skipIntLit + var finalType = liftParamType(c, kind, genericParams, typ, + arg.name.s, arg.info).skipIntLit arg.typ = finalType arg.position = counter inc(counter) @@ -703,6 +701,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, if skipTypes(r, {tyGenericInst}).kind != tyEmpty: if r.sym == nil or sfAnon notin r.sym.flags: r = liftParamType(c, kind, genericParams, r, "result", n.sons[0].info) + r.flags.incl tfRetType result.sons[0] = skipIntLit(r) res.typ = result.sons[0] diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 7e482b3d2..82a8c9399 100755 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -282,7 +282,12 @@ proc matchTypeClass(c: var TCandidate, typeClass, t: PType): TTypeRelation = # if the loop finished without returning, either all constraints matched # or none of them matched. result = if tfAny in typeClass.flags: isNone else: isGeneric - + +proc matchTypeClass*(typeClass, typ: PType): bool = + var c: TCandidate + InitCandidate(c, typeClass) + result = matchTypeClass(c, typeClass, typ) == isGeneric + proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = proc inconsistentVarTypes(f, a: PType): bool {.inline.} = result = f.kind != a.kind and (f.kind == tyVar or a.kind == tyVar) diff --git a/compiler/types.nim b/compiler/types.nim index d8879f1b4..b650b49b8 100755 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -626,9 +626,9 @@ proc SameTypeOrNil*(a, b: PType, flags: TTypeCmpFlags = {}): bool = var c = initSameTypeClosure() c.flags = flags result = SameTypeAux(a, b, c) - + proc equalParam(a, b: PSym): TParamsEquality = - if SameTypeOrNil(a.typ, b.typ): + if SameTypeOrNil(a.typ, b.typ, {TypeDescExactMatch}): if a.ast == b.ast: result = paramsEqual elif a.ast != nil and b.ast != nil: -- cgit 1.4.1-2-gfad0 From 698785ef5e560d533c6aec6b1f0e9125fb7afe2a Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Sat, 29 Sep 2012 17:37:55 +0300 Subject: bugfix: allow tuple constructors in generic code --- compiler/semgnrc.nim | 3 +++ tests/run/trettypeinference.nim | 4 ++++ 2 files changed, 7 insertions(+) (limited to 'compiler') diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index 2751aa1e1..dde799ab3 100755 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -306,6 +306,9 @@ proc semGenericStmt(c: PContext, n: PNode, n.sons[bodyPos] = semGenericStmtScope(c, body, flags, toBind) closeScope(c.tab) of nkPragma, nkPragmaExpr: nil + of nkExprColonExpr: + checkMinSonsLen(n, 2) + result.sons[1] = semGenericStmt(c, n.sons[1], flags, toBind) else: for i in countup(0, sonsLen(n) - 1): result.sons[i] = semGenericStmt(c, n.sons[i], flags, toBind) diff --git a/tests/run/trettypeinference.nim b/tests/run/trettypeinference.nim index eea5b597d..41b4aa5ef 100644 --- a/tests/run/trettypeinference.nim +++ b/tests/run/trettypeinference.nim @@ -6,12 +6,16 @@ discard """ import typetraits proc plus(a, b): auto = a + b +proc makePair(a, b): auto = (first: a, second: b) proc `+`(a, b: string): seq[string] = @[a, b] var i = plus(10, 20) var s = plus("A", "B") +var p = makePair("key", 100) +static: assert p[0].type is string + echo i.type.name echo s.type.name -- cgit 1.4.1-2-gfad0 From f8184e349027dc0273a3552526af18dc03df0f79 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Sat, 29 Sep 2012 18:39:18 +0300 Subject: always print stack traces on errors in debug builds of nimrod I've been using this for a while and it's really more convenient than hunting the message in msgs.nim and grepping the error code in the whole project --- compiler/msgs.nim | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'compiler') diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 0ad79f597..c793c1c68 100755 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -575,19 +575,22 @@ proc inCheckpoint*(current: TLineInfo): TCheckPointResult = type TErrorHandling = enum doNothing, doAbort, doRaise -proc handleError(msg: TMsgKind, eh: TErrorHandling, s: string) = - if msg == errInternal: - assert(false) # we want a stack trace here +proc handleError(msg: TMsgKind, eh: TErrorHandling, s: string) = + template maybeTrace = + if defined(debug) or gVerbosity >= 3: + writeStackTrace() + + if msg == errInternal: + writeStackTrace() # we always want a stack trace here if msg >= fatalMin and msg <= fatalMax: - if gVerbosity >= 3: assert(false) + maybeTrace() quit(1) if msg >= errMin and msg <= errMax: - if gVerbosity >= 3: assert(false) + maybeTrace() inc(gErrorCounter) options.gExitcode = 1'i8 if gErrorCounter >= gErrorMax or eh == doAbort: - if gVerbosity >= 3: assert(false) - quit(1) # one error stops the compiler + quit(1) # one error stops the compiler elif eh == doRaise: raiseRecoverableError(s) -- cgit 1.4.1-2-gfad0 From 92f70b08f9c4d6108f61f37c29c1b001c6173a19 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Sat, 29 Sep 2012 18:43:41 +0300 Subject: table constructors now mimic more closely the syntax of case... of... see the safePrintF example in the manual as a motivation --- compiler/semexprs.nim | 19 ++++++++++++++----- doc/manual.txt | 4 ++-- web/news.txt | 31 +++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 7 deletions(-) (limited to 'compiler') diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 4608d38ef..17e6e8048 100755 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -1364,19 +1364,28 @@ proc semSetConstr(c: PContext, n: PNode): PNode = m = fitNode(c, typ, n.sons[i]) addSon(result, m) -proc semTableConstr(c: PContext, n: PNode): PNode = - # we simply transform ``{key: value, key2: value}`` to - # ``[(key, value), (key2, value2)]`` +proc semTableConstr(c: PContext, n: PNode): PNode = + # we simply transform ``{key: value, key2, key3: value}`` to + # ``[(key, value), (key2, value2), (key3, value2)]`` result = newNodeI(nkBracket, n.info) + var lastKey = 0 for i in 0..n.len-1: var x = n.sons[i] if x.kind == nkExprColonExpr and sonsLen(x) == 2: + for j in countup(lastKey, i-1): + var pair = newNodeI(nkPar, x.info) + pair.add(n.sons[j]) + pair.add(x[1]) + result.add(pair) + var pair = newNodeI(nkPar, x.info) pair.add(x[0]) pair.add(x[1]) result.add(pair) - else: - illFormedAst(x) + + lastKey = i+1 + + if lastKey != n.len: illFormedAst(n) result = semExpr(c, result) type diff --git a/doc/manual.txt b/doc/manual.txt index 4d9375f30..b5f5d9d63 100755 --- a/doc/manual.txt +++ b/doc/manual.txt @@ -2244,10 +2244,10 @@ Table constructor A `table constructor`:idx: is syntactic sugar for an array constructor: .. code-block:: nimrod - {"key1": "value1", "key2": "value2"} + {"key1": "value1", "key2", "key3": "value2"} # is the same as: - [("key1", "value1"), ("key2", "value2")] + [("key1", "value1"), ("key2", "value2"), ("key3", "value")] The empty table can be written ``{:}`` (in contrast to the empty set diff --git a/web/news.txt b/web/news.txt index fb133b6fe..d063c3974 100755 --- a/web/news.txt +++ b/web/news.txt @@ -2,6 +2,37 @@ News ==== +2012-XX-XX Version 0.9.XX released +================================== + +Version 0.8.XX has been released! Get it `here `_. + +Bugfixes +-------- + + + +Library Additions +----------------- + + + +Changes affecting backwards compatibility +----------------------------------------- + + + +Compiler Additions +------------------ + + + +Language Additions +------------------ + +- table constructors now mimic more closely the syntax of case... of... + + 2012-09-23 Version 0.9.0 released ================================= -- cgit 1.4.1-2-gfad0 From 770d4a997eab25a04cdfd83b325491a2e63bea08 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Mon, 1 Oct 2012 23:48:37 +0300 Subject: implemented case expressions --- compiler/ast.nim | 5 +++++ compiler/parser.nim | 8 +++---- compiler/semexprs.nim | 55 ++++++++++++++++++++++++++++++++++++++++++++- doc/manual.txt | 37 +++++++++++++++++++++++------- lib/system.nim | 2 +- tests/reject/tcaseexpr1.nim | 30 +++++++++++++++++++++++++ tests/run/tcasestm.nim | 24 +++++++++++--------- web/news.txt | 2 ++ 8 files changed, 138 insertions(+), 25 deletions(-) create mode 100644 tests/reject/tcaseexpr1.nim (limited to 'compiler') diff --git a/compiler/ast.nim b/compiler/ast.nim index c6e4d8318..aa94644aa 100755 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -781,6 +781,11 @@ proc add*(father, son: PNode) = proc `[]`*(n: PNode, i: int): PNode {.inline.} = result = n.sons[i] +# son access operators with support for negative indices +template `{}`*(n: PNode, i: int): expr = n[i -| n] +template `{}=`*(n: PNode, i: int, s: PNode): stmt = + n.sons[i -| n] = s + var emptyNode* = newNode(nkEmpty) # There is a single empty node that is shared! Do not overwrite it! diff --git a/compiler/parser.nim b/compiler/parser.nim index cdbe42c7e..7612980c5 100755 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -743,7 +743,7 @@ proc isExprStart(p: TParser): bool = case p.tok.tokType of tkSymbol, tkAccent, tkOpr, tkNot, tkNil, tkCast, tkIf, tkProc, tkBind, tkParLe, tkBracketLe, tkCurlyLe, tkIntLit..tkCharLit, tkVar, tkRef, tkPtr, - tkTuple, tkType, tkWhen: + tkTuple, tkType, tkWhen, tkCase: result = true else: result = false @@ -763,9 +763,9 @@ proc parseExpr(p: var TParser): PNode = case p.tok.tokType: of tkIf: result = parseIfExpr(p, nkIfExpr) of tkWhen: result = parseIfExpr(p, nkWhenExpr) + of tkCase: result = parseCase(p) else: result = lowestExpr(p) # XXX needs proper support: - #of tkCase: result = parseCase(p) #of tkTry: result = parseTry(p) proc primary(p: var TParser, skipSuffix = false): PNode = @@ -1044,9 +1044,9 @@ proc parseCase(p: var TParser): PNode = if b.kind == nkElse: break if wasIndented: - eat(p, tkDed) + if p.tok.tokType != tkEof: eat(p, tkDed) popInd(p.lex) - + proc parseTry(p: var TParser): PNode = result = newNodeP(nkTryStmt, p) getTok(p) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 17e6e8048..a658f7f44 100755 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -1528,6 +1528,57 @@ proc semMacroStmt(c: PContext, n: PNode, flags: TExprFlags, renderTree(a, {renderNoComments})) result = errorNode(c, n) +proc semCaseExpr(c: PContext, caseStmt: PNode): PNode = + # The case expression is simply rewritten to a StmtListExpr: + # var res {.noInit, genSym.}: type(values) + # + # case E + # of X: res = value1 + # of Y: res = value2 + # + # res + var + info = caseStmt.info + resVar = newSym(skVar, getIdent":res", getCurrOwner(), info) + resNode = newSymNode(resVar, info) + resType: PType + + resVar.flags = { sfGenSym, sfNoInit } + + for i in countup(1, caseStmt.len - 1): + var cs = caseStmt[i] + case cs.kind + of nkOfBranch, nkElifBranch, nkElse: + # the value is always the last son regardless of the branch kind + cs.checkMinSonsLen 1 + var value = cs{-1} + if value.kind == nkStmtList: value.kind = nkStmtListExpr + + value = semExprWithType(c, value) + if resType == nil: + resType = value.typ + elif not sameType(resType, value.typ): + # XXX: semeType is a bit too harsh. + # work on finding a common base type. + # this will be useful for arrays/seq too: + # [ref DerivedA, ref DerivedB, ref Base] + typeMismatch(cs, resType, value.typ) + + cs{-1} = newNode(nkAsgn, cs.info, @[resNode, value]) + else: + IllFormedAst(caseStmt) + + result = newNode(nkStmtListExpr, info, @[ + newNode(nkVarSection, info, @[ + newNode(nkIdentDefs, info, @[ + resNode, + symNodeFromType(c, resType, info), + emptyNode])]), + caseStmt, + resNode]) + + result = semStmtListExpr(c, result) + proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = result = n if gCmd == cmdIdeTools: suggestExpr(c, n) @@ -1707,7 +1758,9 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = of nkTryStmt: result = semTry(c, n) of nkBreakStmt, nkContinueStmt: result = semBreakOrContinue(c, n) of nkForStmt, nkParForStmt: result = semFor(c, n) - of nkCaseStmt: result = semCase(c, n) + of nkCaseStmt: + if efWantStmt in flags: result = semCase(c, n) + else: result = semCaseExpr(c, n) of nkReturnStmt: result = semReturn(c, n) of nkAsmStmt: result = semAsm(c, n) of nkYieldStmt: result = semYield(c, n) diff --git a/doc/manual.txt b/doc/manual.txt index b5f5d9d63..a85c1e753 100755 --- a/doc/manual.txt +++ b/doc/manual.txt @@ -2237,6 +2237,28 @@ An if expression always results in a value, so the ``else`` part is required. ``Elif`` parts are also allowed (but unlikely to be good style). +When expression +~~~~~~~~~~~~~~~ + +Just like an `if expression`, but corresponding to the when statement. + +Case expression +~~~~~~~~~~~~~~~ + +The `case expression` is again very similar to the case statement: + +.. code-block:: nimrod + var favoriteFood = case animal + of "dog": "bones" + of "cat": "mice" + elif animal.endsWith"whale": "plankton" + else: + echo "I'm not sure what to serve, but everybody loves ice cream" + "ice cream" + +As seen in the above example, the case expression can also introduce side +effects. When multiple statements are given for a branch, Nimrod will use +the last expression as the result value, much like in an `expr` template. Table constructor ~~~~~~~~~~~~~~~~~ @@ -3464,15 +3486,14 @@ a type-safe wrapper for the unsafe `printf` function form C: macro safePrintF(formatString: string{lit}, args: vararg[expr]): expr = var i = 0 for c in formatChars(formatString): - const FormatChars = { - 'c': char, - 'd', 'i', 'x', 'X': int, - 'f', 'e', 'E', 'g', 'G': float, - 's': string, - 'p': pointer, - } + var expectedType = case c + of 'c': char + of 'd', 'i', 'x', 'X': int + of 'f', 'e', 'E', 'g', 'G': float + of 's': string + of 'p': pointer + else: EOutOfRange - var expectedType = find(FormatChars, c, EOutOfRange) var actualType = args[i].getType inc i diff --git a/lib/system.nim b/lib/system.nim index 4cfa6ba84..99f1e621a 100755 --- a/lib/system.nim +++ b/lib/system.nim @@ -2116,7 +2116,7 @@ proc `/`*(x, y: int): float {.inline, noSideEffect.} = ## integer division that results in a float. result = toFloat(x) / toFloat(y) -template `-|`(b, s: expr): expr = +template `-|`*(b, s: expr): expr = (if b >= 0: b else: s.len + b) proc `[]`*(s: string, x: TSlice[int]): string {.inline.} = diff --git a/tests/reject/tcaseexpr1.nim b/tests/reject/tcaseexpr1.nim new file mode 100644 index 000000000..0c2e7c452 --- /dev/null +++ b/tests/reject/tcaseexpr1.nim @@ -0,0 +1,30 @@ +discard """ + file: "tcaseexpr1.nim" + + line: 23 + errormsg: "not all cases are covered" + + line: 29 + errormsg: "type mismatch: got (string) but expected 'int'" +""" + +type + E = enum A, B, C + +proc foo(x): auto = + return case x + of 1..9: "digit" + else: "number" + +var r = foo(10) + +var x = C + +var t1 = case x: + of A: "a" + of B: "b" + +var t2 = case x: + of A: 10 + of B, C: "23" + diff --git a/tests/run/tcasestm.nim b/tests/run/tcasestm.nim index cb63e0c51..003ec6e50 100755 --- a/tests/run/tcasestm.nim +++ b/tests/run/tcasestm.nim @@ -1,6 +1,6 @@ discard """ file: "tcasestm.nim" - output: "ayyy" + output: "ayyydd" """ # Test the case statement @@ -22,16 +22,18 @@ of "aa", "bb": write(stdout, "Du bist nicht mein Meister") of "cc", "hash", "when": nil of "will", "it", "finally", "be", "generated": nil -case i -of 1..5, 8, 9: nil -of 6, 7: nil -elif x == "Ha": - nil -elif x == "yyy": - write(stdout, x) -else: - nil - +var z = case i + of 1..5, 8, 9: "aa" + of 6, 7: "bb" + elif x == "Ha": + "cc" + elif x == "yyy": + write(stdout, x) + "dd" + else: + "zz" + +echo z #OUT ayyy diff --git a/web/news.txt b/web/news.txt index d063c3974..a3ba80072 100755 --- a/web/news.txt +++ b/web/news.txt @@ -30,7 +30,9 @@ Compiler Additions Language Additions ------------------ +- ``case expressions`` are now supported. - table constructors now mimic more closely the syntax of case... of... +- Nimrod can now infer the return type of a proc from its body 2012-09-23 Version 0.9.0 released -- cgit 1.4.1-2-gfad0 From 9c8bc3a244b4bbcdc56fdd255bb4e1a7ed30f781 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Tue, 2 Oct 2012 16:45:34 +0300 Subject: the `is` operator now works with type classes and type variables bugfixes: the DLL tests were failing on Mac OS X, due to an incorrect DynlibFormat --- compiler/evals.nim | 26 +++++++++++++++++++++++++- compiler/semexprs.nim | 29 ++++++++++++++++++++--------- compiler/semfold.nim | 11 ----------- compiler/semgnrc.nim | 20 ++++++++++---------- compiler/semtypes.nim | 33 ++++++++++++++++++--------------- compiler/semtypinst.nim | 3 ++- compiler/sigmatch.nim | 37 +++++-------------------------------- compiler/types.nim | 33 ++++++++++++++++++++++++++++++++- lib/pure/os.nim | 2 +- tests/compile/tisop.nim | 42 ++++++++++++++++++++++++++++++++++++++++++ tests/compile/ttypeclasses.nim | 4 ++-- tests/reject/tcaseexpr1.nim | 6 +++--- tests/reject/tenummix.nim | 2 +- 13 files changed, 161 insertions(+), 87 deletions(-) create mode 100644 tests/compile/tisop.nim (limited to 'compiler') diff --git a/compiler/evals.nim b/compiler/evals.nim index 9c73a6b78..0886b1ef5 100755 --- a/compiler/evals.nim +++ b/compiler/evals.nim @@ -521,7 +521,7 @@ proc evalSym(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = else: result = nil if result == nil or {sfImportc, sfForward} * s.flags != {}: result = raiseCannotEval(c, n.info) - + proc evalIncDec(c: PEvalContext, n: PNode, sign: biggestInt): PNode = result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return @@ -875,6 +875,27 @@ proc evalTypeTrait*(n: PNode, context: PSym): PNode = else: internalAssert false +proc evalIsOp*(n: PNode): PNode = + InternalAssert n.sonsLen == 3 and + n[1].kind == nkSym and n[1].sym.kind == skType and + n[2].kind in {nkStrLit..nkTripleStrLit, nkType} + + let t1 = n[1].sym.typ + + if n[2].kind in {nkStrLit..nkTripleStrLit}: + case n[2].strVal.normalize + of "closure": + let t = skipTypes(t1, abstractRange) + result = newIntNode(nkIntLit, ord(t.kind == tyProc and + t.callConv == ccClosure)) + else: + let t2 = n[2].typ + var match = if t2.kind == tyTypeClass: matchTypeClass(t2, t1) + else: sameType(t1, t2) + result = newIntNode(nkIntLit, ord(match)) + + result.typ = n.typ + proc expectString(n: PNode) = if n.kind notin {nkStrLit, nkRStrLit, nkTripleStrLit}: GlobalError(n.info, errStringLiteralExpected) @@ -968,6 +989,9 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode = of mTypeTrait: n.sons[1] = evalAux(c, n.sons[1], {}) result = evalTypeTrait(n, c.module) + of mIs: + n.sons[1] = evalAux(c, n.sons[1], {}) + result = evalIsOp(n) of mSlurp: result = evalSlurp(evalAux(c, n.sons[1], {}), c.module) of mStaticExec: let cmd = evalAux(c, n.sons[1], {}) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index a658f7f44..51ede1614 100755 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -281,18 +281,29 @@ proc semOf(c: PContext, n: PNode): PNode = n.typ = getSysType(tyBool) result = n -proc semIs(c: PContext, n: PNode): PNode = - if sonsLen(n) == 3: - n.typ = getSysType(tyBool) - let a = semTypeNode(c, n[1], nil) - n.sons[1] = newNodeIT(nkType, n[1].info, a) - if n[2].kind notin {nkStrLit..nkTripleStrLit}: - let b = semTypeNode(c, n[2], nil) - n.sons[2] = newNodeIT(nkType, n[2].info, b) - else: +proc semIs(c: PContext, n: PNode): PNode = + if sonsLen(n) != 3: LocalError(n.info, errXExpectsTwoArguments, "is") + result = n + n.typ = getSysType(tyBool) + + n.sons[1] = semExprWithType(c, n[1]) + if n[1].typ.kind != tyTypeDesc: + LocalError(n[0].info, errTypeExpected) + if n[2].kind notin {nkStrLit..nkTripleStrLit}: + let t2 = semTypeNode(c, n[2], nil) + n.sons[2] = newNodeIT(nkType, n[2].info, t2) + + if n[1].typ.sonsLen == 0: + # this is a typedesc variable, leave for evals + return + else: + let t1 = n[1].typ.sons[0] + # BUGFIX: don't evaluate this too early: ``T is void`` + if not containsGenericType(t1): result = evalIsOp(n) + proc semOpAux(c: PContext, n: PNode, tailToExclude = 1) = for i in countup(1, sonsLen(n) - tailToExclude): var a = n.sons[i] diff --git a/compiler/semfold.nim b/compiler/semfold.nim index c2958ca5e..d0805d921 100755 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -610,17 +610,6 @@ proc getConstExpr(m: PSym, n: PNode): PNode = result = newIntNodeT(sonsLen(a), n) else: result = magicCall(m, n) - of mIs: - # BUGFIX: don't evaluate this too early: ``T is void`` - if not containsGenericType(n[1].typ): - if n[2].kind in {nkStrLit..nkTripleStrLit}: - case n[2].strVal.normalize - of "closure": - let t = skipTypes(n[1].typ, abstractRange) - result = newIntNodeT(ord(t.kind == tyProc and - t.callConv == ccClosure), n) - elif not containsGenericType(n[2].typ): - result = newIntNodeT(ord(sameType(n[1].typ, n[2].typ)), n) of mAstToStr: result = newStrNodeT(renderTree(n[1], {renderNoComments}), n) of mConStrStr: diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index dde799ab3..bffa8ad8e 100755 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -261,16 +261,16 @@ proc semGenericStmt(c: PContext, n: PNode, else: a.sons[2] = semGenericStmt(c, a.sons[2], flags+{withinTypeDesc}, toBind) of nkEnumTy: - checkMinSonsLen(n, 1) - if n.sons[0].kind != nkEmpty: - n.sons[0] = semGenericStmt(c, n.sons[0], flags+{withinTypeDesc}, toBind) - for i in countup(1, sonsLen(n) - 1): - var a: PNode - case n.sons[i].kind - of nkEnumFieldDef: a = n.sons[i].sons[0] - of nkIdent: a = n.sons[i] - else: illFormedAst(n) - addDeclAt(c, newSymS(skUnknown, getIdentNode(a.sons[i]), c), c.tab.tos-1) + if n.sonsLen > 0: + if n.sons[0].kind != nkEmpty: + n.sons[0] = semGenericStmt(c, n.sons[0], flags+{withinTypeDesc}, toBind) + for i in countup(1, sonsLen(n) - 1): + var a: PNode + case n.sons[i].kind + of nkEnumFieldDef: a = n.sons[i].sons[0] + of nkIdent: a = n.sons[i] + else: illFormedAst(n) + addDeclAt(c, newSymS(skUnknown, getIdentNode(a.sons[i]), c), c.tab.tos-1) of nkObjectTy, nkTupleTy: nil of nkFormalParams: diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index eeb48a647..c41727b10 100755 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -799,22 +799,25 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = LocalError(n.info, errTypeExpected) result = newOrPrevType(tyError, prev, c) of nkCallKinds: - let op = n.sons[0].ident - if op.id in {ord(wAnd), ord(wOr)} or op.s == "|": - var - t1 = semTypeNode(c, n.sons[1], nil) - t2 = semTypeNode(c, n.sons[2], nil) - if t1 == nil: - LocalError(n.sons[1].info, errTypeExpected) - result = newOrPrevType(tyError, prev, c) - elif t2 == nil: - LocalError(n.sons[2].info, errTypeExpected) - result = newOrPrevType(tyError, prev, c) + if n[0].kind == nkIdent: + let op = n.sons[0].ident + if op.id in {ord(wAnd), ord(wOr)} or op.s == "|": + var + t1 = semTypeNode(c, n.sons[1], nil) + t2 = semTypeNode(c, n.sons[2], nil) + if t1 == nil: + LocalError(n.sons[1].info, errTypeExpected) + result = newOrPrevType(tyError, prev, c) + elif t2 == nil: + LocalError(n.sons[2].info, errTypeExpected) + result = newOrPrevType(tyError, prev, c) + else: + result = newTypeS(tyTypeClass, c) + result.addSonSkipIntLit(t1) + result.addSonSkipIntLit(t2) + result.flags.incl(if op.id == ord(wAnd): tfAll else: tfAny) else: - result = newTypeS(tyTypeClass, c) - result.addSonSkipIntLit(t1) - result.addSonSkipIntLit(t2) - result.flags.incl(if op.id == ord(wAnd): tfAll else: tfAny) + result = semTypeExpr(c, n) else: result = semTypeExpr(c, n) of nkCurlyExpr: diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim index d75594dff..70453c6db 100755 --- a/compiler/semtypinst.nim +++ b/compiler/semtypinst.nim @@ -59,6 +59,7 @@ type proc ReplaceTypeVarsT*(cl: var TReplTypeVars, t: PType): PType proc ReplaceTypeVarsS(cl: var TReplTypeVars, s: PSym): PSym +proc ReplaceTypeVarsN(cl: var TReplTypeVars, n: PNode): PNode proc prepareNode(cl: var TReplTypeVars, n: PNode): PNode = result = copyNode(n) @@ -66,7 +67,7 @@ proc prepareNode(cl: var TReplTypeVars, n: PNode): PNode = for i in 0 .. safeLen(n)-1: # XXX HACK: ``f(a, b)``, avoid to instantiate `f` if i == 0: result.add(n[i]) - else: result.add(prepareNode(cl, n[i])) + else: result.add(ReplaceTypeVarsN(cl, n[i])) proc ReplaceTypeVarsN(cl: var TReplTypeVars, n: PNode): PNode = if n == nil: return diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 82a8c9399..8f1ca5f36 100755 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -11,7 +11,7 @@ ## the call to overloaded procs, generic procs and operators. import - intsets, ast, astalgo, semdata, types, msgs, renderer, lookups, semtypinst, + intsets, ast, astalgo, semdata, types, msgs, renderer, lookups, semtypinst, magicsys, condsyms, idents, lexer, options type @@ -257,37 +257,6 @@ proc tupleRel(c: var TCandidate, f, a: PType): TTypeRelation = var y = a.n.sons[i].sym if x.name.id != y.name.id: return isNone -proc matchTypeClass(c: var TCandidate, typeClass, t: PType): TTypeRelation = - for i in countup(0, typeClass.sonsLen - 1): - let req = typeClass.sons[i] - var match = req.kind == skipTypes(t, {tyRange, tyGenericInst}).kind - - if not match: - case req.kind - of tyGenericBody: - if t.kind == tyGenericInst and t.sons[0] == req: - match = true - put(c.bindings, typeClass, t) - of tyTypeClass: - match = matchTypeClass(c, req, t) == isGeneric - else: nil - elif t.kind in {tyObject}: - match = sameType(t, req) - - if tfAny in typeClass.flags: - if match: return isGeneric - else: - if not match: return isNone - - # if the loop finished without returning, either all constraints matched - # or none of them matched. - result = if tfAny in typeClass.flags: isNone else: isGeneric - -proc matchTypeClass*(typeClass, typ: PType): bool = - var c: TCandidate - InitCandidate(c, typeClass) - result = matchTypeClass(c, typeClass, typ) == isGeneric - proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = proc inconsistentVarTypes(f, a: PType): bool {.inline.} = result = f.kind != a.kind and (f.kind == tyVar or a.kind == tyVar) @@ -330,6 +299,10 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = result = isNone else: nil +proc matchTypeClass(c: var TCandidate, f, a: PType): TTypeRelation = + result = if matchTypeClass(c.bindings, f, a): isGeneric + else: isNone + proc typeRel(c: var TCandidate, f, a: PType): TTypeRelation = # is a subtype of f? result = isNone diff --git a/compiler/types.nim b/compiler/types.nim index b650b49b8..e02d93233 100755 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -904,7 +904,38 @@ proc matchType*(a: PType, pattern: openArray[tuple[k:TTypeKind, i:int]], if i >= a.sonslen or a.sons[i] == nil: return false a = a.sons[i] result = a.kind == last - + +proc matchTypeClass*(bindings: var TIdTable, typeClass, t: PType): bool = + for i in countup(0, typeClass.sonsLen - 1): + let req = typeClass.sons[i] + var match = req.kind == skipTypes(t, {tyRange, tyGenericInst}).kind + + if not match: + case req.kind + of tyGenericBody: + if t.kind == tyGenericInst and t.sons[0] == req: + match = true + IdTablePut(bindings, typeClass, t) + of tyTypeClass: + match = matchTypeClass(bindings, req, t) + else: nil + elif t.kind in {tyObject}: + match = sameType(t, req) + + if tfAny in typeClass.flags: + if match: return true + else: + if not match: return false + + # if the loop finished without returning, either all constraints matched + # or none of them matched. + result = if tfAny in typeClass.flags: false else: true + +proc matchTypeClass*(typeClass, typ: PType): bool = + var bindings: TIdTable + initIdTable(bindings) + result = matchTypeClass(bindings, typeClass, typ) + proc typeAllowedAux(marker: var TIntSet, typ: PType, kind: TSymKind): bool = assert(kind in {skVar, skLet, skConst, skParam, skResult}) # if we have already checked the type, return true, because we stop the diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 5a298db5b..68634e4ca 100755 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -149,7 +149,7 @@ else: # UNIX-like operating system FileSystemCaseSensitive* = true ExeExt* = "" ScriptExt* = "" - DynlibFormat* = "lib$1.so" + DynlibFormat* = when defined(macosx): "lib$1.dylib" else: "lib$1.so" when defined(macosx) or defined(bsd): var diff --git a/tests/compile/tisop.nim b/tests/compile/tisop.nim new file mode 100644 index 000000000..509cc4e95 --- /dev/null +++ b/tests/compile/tisop.nim @@ -0,0 +1,42 @@ +import typetraits + +type + TRecord = (tuple) or (object) + + TFoo[T, U] = object + x: int + + when T is string: + y: float + else: + y: string + + when U is TRecord: + z: float + + E = enum A, B, C + +macro m(t: typedesc): typedesc = + if t is enum: + result = string + else: + result = int + +var f: TFoo[int, int] +static: assert(f.y.type.name == "string") + +when compiles(f.z): + {.error: "Foo should not have a `z` field".} + +proc p(a, b) = + when a.type is int: + static: assert false + + var f: TFoo[m(a.type), b.type] + static: + assert f.x.type.name == "int" + assert f.y.type.name == "float" + assert f.z.type.name == "float" + +p(A, f) + diff --git a/tests/compile/ttypeclasses.nim b/tests/compile/ttypeclasses.nim index e22ede1dc..5e23e8489 100644 --- a/tests/compile/ttypeclasses.nim +++ b/tests/compile/ttypeclasses.nim @@ -2,8 +2,8 @@ type TFoo[T] = object val: T - T1 = distinct expr - T2 = distinct expr + T1 = expr + T2 = expr proc takesExpr(x, y) = echo x, y diff --git a/tests/reject/tcaseexpr1.nim b/tests/reject/tcaseexpr1.nim index 0c2e7c452..e5e08e25e 100644 --- a/tests/reject/tcaseexpr1.nim +++ b/tests/reject/tcaseexpr1.nim @@ -1,11 +1,11 @@ discard """ file: "tcaseexpr1.nim" - line: 23 - errormsg: "not all cases are covered" - line: 29 errormsg: "type mismatch: got (string) but expected 'int'" + + line: 23 + errormsg: "not all cases are covered" """ type diff --git a/tests/reject/tenummix.nim b/tests/reject/tenummix.nim index 55c0c05a9..77b7f44d2 100644 --- a/tests/reject/tenummix.nim +++ b/tests/reject/tenummix.nim @@ -1,6 +1,6 @@ discard """ file: "system.nim" - line: 643 + line: 649 errormsg: "type mismatch" """ -- cgit 1.4.1-2-gfad0 From 2e5265bef54ef260213a2ad6ec417f48571c2824 Mon Sep 17 00:00:00 2001 From: Zahary Karadjov Date: Wed, 3 Oct 2012 01:50:26 +0300 Subject: experimental support for querying the type of expressions within macros normalised the line endings of macros.nim (minor edits otherwise) --- compiler/evals.nim | 9 +- compiler/sem.nim | 11 + compiler/semexprs.nim | 19 +- lib/core/macros.nim | 666 +++++++++++++++++++++--------------------- tests/compile/tmacrotypes.nim | 12 + 5 files changed, 371 insertions(+), 346 deletions(-) create mode 100644 tests/compile/tmacrotypes.nim (limited to 'compiler') diff --git a/compiler/evals.nim b/compiler/evals.nim index 0886b1ef5..226415815 100755 --- a/compiler/evals.nim +++ b/compiler/evals.nim @@ -41,6 +41,7 @@ type callsite: PNode # for 'callsite' magic mode*: TEvalMode globals*: TIdNodeTable # state of global vars + getType*: proc(n: PNode): PNode PEvalContext* = ref TEvalContext @@ -1091,7 +1092,10 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode = result = evalAux(c, n.sons[1], {}) if isSpecial(result): return if result.kind != nkIdent: stackTrace(c, n, errFieldXNotFound, "ident") - of mNGetType: result = evalAux(c, n.sons[1], {}) + of mNGetType: + var ast = evalAux(c, n.sons[1], {}) + InternalAssert c.getType != nil + result = c.getType(ast) of mNStrVal: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return @@ -1152,7 +1156,8 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode = var a = result result = evalAux(c, n.sons[2], {efLValue}) if isSpecial(result): return - a.typ = result.typ # XXX: exception handling? + InternalAssert result.kind == nkSym and result.sym.kind == skType + a.typ = result.sym.typ result = emptyNode of mNSetStrVal: result = evalAux(c, n.sons[1], {efLValue}) diff --git a/compiler/sem.nim b/compiler/sem.nim index 397581f63..0a6159dfa 100755 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -43,6 +43,7 @@ proc addParams(c: PContext, n: PNode, kind: TSymKind) proc addResult(c: PContext, t: PType, info: TLineInfo, owner: TSymKind) proc addResultNode(c: PContext, n: PNode) proc instGenericContainer(c: PContext, n: PNode, header: PType): PType +proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode proc typeMismatch(n: PNode, formal, actual: PType) = if formal.kind != tyError and actual.kind != tyError: @@ -156,8 +157,18 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, markUsed(n, sym) if sym == c.p.owner: GlobalError(n.info, errRecursiveDependencyX, sym.name.s) + if c.evalContext == nil: c.evalContext = newEvalContext(c.module, "", emStatic) + c.evalContext.getType = proc (n: PNode): PNode = + var e = tryExpr(c, n) + if e == nil: + result = symNodeFromType(c, errorType(c), n.info) + elif e.typ == nil: + result = newSymNode(getSysSym"void") + else: + result = symNodeFromType(c, e.typ, n.info) + result = evalMacroCall(c.evalContext, n, nOrig, sym) if semCheck: result = semAfterMacroCall(c, result, sym) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 51ede1614..e9dc5a8e9 100755 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -1237,12 +1237,7 @@ proc semExpandToAst(c: PContext, n: PNode, magicSym: PSym, else: result = semDirectOp(c, n, flags) -proc semCompiles(c: PContext, n: PNode, flags: TExprFlags): PNode = - # we replace this node by a 'true' or 'false' node: - if sonsLen(n) != 2: return semDirectOp(c, n, flags) - result = newIntNode(nkIntLit, 0) - result.info = n.info - result.typ = getSysType(tyBool) +proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = # watch out, hacks ahead: let oldErrorCount = msgs.gErrorCounter let oldErrorMax = msgs.gErrorMax @@ -1264,8 +1259,8 @@ proc semCompiles(c: PContext, n: PNode, flags: TExprFlags): PNode = let oldProcCon = c.p c.generics = newGenericsCache() try: - discard semExpr(c, n.sons[1]) - result.intVal = ord(msgs.gErrorCounter == oldErrorCount) + result = semExpr(c, n, flags) + if msgs.gErrorCounter != oldErrorCount: result = nil except ERecoverableError: nil # undo symbol table changes (as far as it's possible): @@ -1282,6 +1277,14 @@ proc semCompiles(c: PContext, n: PNode, flags: TExprFlags): PNode = msgs.gErrorCounter = oldErrorCount msgs.gErrorMax = oldErrorMax +proc semCompiles(c: PContext, n: PNode, flags: TExprFlags): PNode = + # we replace this node by a 'true' or 'false' node: + if sonsLen(n) != 2: return semDirectOp(c, n, flags) + + result = newIntNode(nkIntLit, ord(tryExpr(c, n, flags) != nil)) + result.info = n.info + result.typ = getSysType(tyBool) + proc semShallowCopy(c: PContext, n: PNode, flags: TExprFlags): PNode = if sonsLen(n) == 3: # XXX ugh this is really a hack: shallowCopy() can be overloaded only diff --git a/lib/core/macros.nim b/lib/core/macros.nim index b43105f44..8fdaf7a6c 100755 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -1,194 +1,188 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - - -## This module contains the interface to the compiler's abstract syntax -## tree (`AST`:idx:). Macros operate on this tree. - -## .. include:: ../doc/astspec.txt - -type - TNimrodNodeKind* = enum - nnkNone, nnkEmpty, nnkIdent, nnkSym, - nnkType, nnkCharLit, nnkIntLit, nnkInt8Lit, - nnkInt16Lit, nnkInt32Lit, nnkInt64Lit, nnkUIntLit, nnkUInt8Lit, - nnkUInt16Lit, nnkUInt32Lit, nnkUInt64Lit, nnkFloatLit, - nnkFloat32Lit, nnkFloat64Lit, nnkFloat128Lit, nnkStrLit, nnkRStrLit, - nnkTripleStrLit, nnkNilLit, nnkMetaNode, nnkDotCall, - nnkCommand, nnkCall, nnkCallStrLit, nnkExprEqExpr, - nnkExprColonExpr, nnkIdentDefs, nnkVarTuple, nnkInfix, - nnkPrefix, nnkPostfix, nnkPar, nnkCurly, nnkCurlyExpr, - nnkBracket, nnkBracketExpr, nnkPragmaExpr, nnkRange, - nnkDotExpr, nnkCheckedFieldExpr, nnkDerefExpr, nnkIfExpr, - nnkElifExpr, nnkElseExpr, nnkLambda, nnkDo, nnkAccQuoted, +# +# +# Nimrod's Runtime Library +# (c) Copyright 2012 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + + +## This module contains the interface to the compiler's abstract syntax +## tree (`AST`:idx:). Macros operate on this tree. + +## .. include:: ../doc/astspec.txt + +type + TNimrodNodeKind* = enum + nnkNone, nnkEmpty, nnkIdent, nnkSym, + nnkType, nnkCharLit, nnkIntLit, nnkInt8Lit, + nnkInt16Lit, nnkInt32Lit, nnkInt64Lit, nnkUIntLit, nnkUInt8Lit, + nnkUInt16Lit, nnkUInt32Lit, nnkUInt64Lit, nnkFloatLit, + nnkFloat32Lit, nnkFloat64Lit, nnkFloat128Lit, nnkStrLit, nnkRStrLit, + nnkTripleStrLit, nnkNilLit, nnkMetaNode, nnkDotCall, + nnkCommand, nnkCall, nnkCallStrLit, nnkExprEqExpr, + nnkExprColonExpr, nnkIdentDefs, nnkVarTuple, nnkInfix, + nnkPrefix, nnkPostfix, nnkPar, nnkCurly, nnkCurlyExpr, + nnkBracket, nnkBracketExpr, nnkPragmaExpr, nnkRange, + nnkDotExpr, nnkCheckedFieldExpr, nnkDerefExpr, nnkIfExpr, + nnkElifExpr, nnkElseExpr, nnkLambda, nnkDo, nnkAccQuoted, nnkTableConstr, nnkBind, nnkClosedSymChoice, nnkOpenSymChoice, - nnkHiddenStdConv, - nnkHiddenSubConv, nnkHiddenCallConv, nnkConv, nnkCast, nnkStaticExpr, - nnkAddr, nnkHiddenAddr, nnkHiddenDeref, nnkObjDownConv, - nnkObjUpConv, nnkChckRangeF, nnkChckRange64, nnkChckRange, - nnkStringToCString, nnkCStringToString, nnkAsgn, - nnkFastAsgn, nnkGenericParams, nnkFormalParams, nnkOfInherit, - nnkModule, nnkProcDef, nnkMethodDef, nnkConverterDef, - nnkMacroDef, nnkTemplateDef, nnkIteratorDef, nnkOfBranch, - nnkElifBranch, nnkExceptBranch, nnkElse, nnkMacroStmt, - nnkAsmStmt, nnkPragma, nnkPragmaBlock, nnkIfStmt, nnkWhenStmt, - nnkForStmt, nnkParForStmt, nnkWhileStmt, nnkCaseStmt, - nnkTypeSection, nnkVarSection, nnkLetSection, nnkConstSection, - nnkConstDef, nnkTypeDef, - nnkYieldStmt, nnkTryStmt, nnkFinally, nnkRaiseStmt, - nnkReturnStmt, nnkBreakStmt, nnkContinueStmt, nnkBlockStmt, nnkStaticStmt, - nnkDiscardStmt, nnkStmtList, nnkImportStmt, nnkFromStmt, - nnkIncludeStmt, nnkBindStmt, nnkMixinStmt, - nnkCommentStmt, nnkStmtListExpr, nnkBlockExpr, - nnkStmtListType, nnkBlockType, nnkTypeOfExpr, nnkObjectTy, - nnkTupleTy, nnkRecList, nnkRecCase, nnkRecWhen, - nnkRefTy, nnkPtrTy, nnkVarTy, - nnkConstTy, nnkMutableTy, - nnkDistinctTy, - nnkProcTy, nnkEnumTy, - nnkEnumFieldDef, + nnkHiddenStdConv, + nnkHiddenSubConv, nnkHiddenCallConv, nnkConv, nnkCast, nnkStaticExpr, + nnkAddr, nnkHiddenAddr, nnkHiddenDeref, nnkObjDownConv, + nnkObjUpConv, nnkChckRangeF, nnkChckRange64, nnkChckRange, + nnkStringToCString, nnkCStringToString, nnkAsgn, + nnkFastAsgn, nnkGenericParams, nnkFormalParams, nnkOfInherit, + nnkModule, nnkProcDef, nnkMethodDef, nnkConverterDef, + nnkMacroDef, nnkTemplateDef, nnkIteratorDef, nnkOfBranch, + nnkElifBranch, nnkExceptBranch, nnkElse, nnkMacroStmt, + nnkAsmStmt, nnkPragma, nnkPragmaBlock, nnkIfStmt, nnkWhenStmt, + nnkForStmt, nnkParForStmt, nnkWhileStmt, nnkCaseStmt, + nnkTypeSection, nnkVarSection, nnkLetSection, nnkConstSection, + nnkConstDef, nnkTypeDef, + nnkYieldStmt, nnkTryStmt, nnkFinally, nnkRaiseStmt, + nnkReturnStmt, nnkBreakStmt, nnkContinueStmt, nnkBlockStmt, nnkStaticStmt, + nnkDiscardStmt, nnkStmtList, nnkImportStmt, nnkFromStmt, + nnkIncludeStmt, nnkBindStmt, nnkMixinStmt, + nnkCommentStmt, nnkStmtListExpr, nnkBlockExpr, + nnkStmtListType, nnkBlockType, nnkTypeOfExpr, nnkObjectTy, + nnkTupleTy, nnkRecList, nnkRecCase, nnkRecWhen, + nnkRefTy, nnkPtrTy, nnkVarTy, + nnkConstTy, nnkMutableTy, + nnkDistinctTy, + nnkProcTy, nnkEnumTy, + nnkEnumFieldDef, nnkArglist, nnkPattern - nnkReturnToken - TNimNodeKinds* = set[TNimrodNodeKind] - TNimrodTypeKind* = enum - ntyNone, ntyBool, ntyChar, ntyEmpty, - ntyArrayConstr, ntyNil, ntyExpr, ntyStmt, - ntyTypeDesc, ntyGenericInvokation, ntyGenericBody, ntyGenericInst, - ntyGenericParam, ntyDistinct, ntyEnum, ntyOrdinal, - ntyArray, ntyObject, ntyTuple, ntySet, - ntyRange, ntyPtr, ntyRef, ntyVar, - ntySequence, ntyProc, ntyPointer, ntyOpenArray, - ntyString, ntyCString, ntyForward, ntyInt, - ntyInt8, ntyInt16, ntyInt32, ntyInt64, - ntyFloat, ntyFloat32, ntyFloat64, ntyFloat128 - TNimTypeKinds* = set[TNimrodTypeKind] - TNimrodSymKind* = enum - nskUnknown, nskConditional, nskDynLib, nskParam, - nskGenericParam, nskTemp, nskType, nskConst, - nskVar, nskProc, nskMethod, nskIterator, - nskConverter, nskMacro, nskTemplate, nskField, - nskEnumField, nskForVar, nskModule, nskLabel, - nskStub - TNimSymKinds* = set[TNimrodSymKind] - -type - TNimrodIdent* = object of TObject - ## represents a Nimrod identifier in the AST - - TNimrodSymbol {.final.} = object # hidden - TNimrodType {.final.} = object # hidden - - PNimrodType* {.compilerproc.} = ref TNimrodType - ## represents a Nimrod type in the compiler; currently this is not very - ## useful as there is no API to deal with Nimrod types. - - PNimrodSymbol* {.compilerproc.} = ref TNimrodSymbol - ## represents a Nimrod *symbol* in the compiler; a *symbol* is a looked-up - ## *ident*. - -const - nnkLiterals* = {nnkCharLit..nnkNilLit} - nnkCallKinds* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand, - nnkCallStrLit} - -proc `[]`*(n: PNimrodNode, i: int): PNimrodNode {.magic: "NChild".} - ## get `n`'s `i`'th child. - -proc `[]=`*(n: PNimrodNode, i: int, child: PNimrodNode) {.magic: "NSetChild".} - ## set `n`'s `i`'th child to `child`. - -proc `!`*(s: string): TNimrodIdent {.magic: "StrToIdent".} - ## constructs an identifier from the string `s` - -proc `$`*(i: TNimrodIdent): string {.magic: "IdentToStr".} - ## converts a Nimrod identifier to a string - -proc `$`*(s: PNimrodSymbol): string {.magic: "IdentToStr".} - ## converts a Nimrod symbol to a string - -proc `==`*(a, b: TNimrodIdent): bool {.magic: "EqIdent", noSideEffect.} - ## compares two Nimrod identifiers - -proc `==`*(a, b: PNimrodNode): bool {.magic: "EqNimrodNode", noSideEffect.} - ## compares two Nimrod nodes - -proc len*(n: PNimrodNode): int {.magic: "NLen".} - ## returns the number of children of `n`. - -proc add*(father, child: PNimrodNode) {.magic: "NAdd".} - ## adds the `child` to the `father` node - -proc add*(father: PNimrodNode, children: varargs[PNimrodNode]) {. - magic: "NAddMultiple".} - ## adds each child of `children` to the `father` node - -proc del*(father: PNimrodNode, idx = 0, n = 1) {.magic: "NDel".} - ## deletes `n` children of `father` starting at index `idx`. - -proc kind*(n: PNimrodNode): TNimrodNodeKind {.magic: "NKind".} - ## returns the `kind` of the node `n`. - -proc intVal*(n: PNimrodNode): biggestInt {.magic: "NIntVal".} -proc floatVal*(n: PNimrodNode): biggestFloat {.magic: "NFloatVal".} -proc symbol*(n: PNimrodNode): PNimrodSymbol {.magic: "NSymbol".} -proc ident*(n: PNimrodNode): TNimrodIdent {.magic: "NIdent".} -proc typ*(n: PNimrodNode): PNimrodType {.magic: "NGetType".} -proc strVal*(n: PNimrodNode): string {.magic: "NStrVal".} - -proc `intVal=`*(n: PNimrodNode, val: biggestInt) {.magic: "NSetIntVal".} -proc `floatVal=`*(n: PNimrodNode, val: biggestFloat) {.magic: "NSetFloatVal".} -proc `symbol=`*(n: PNimrodNode, val: PNimrodSymbol) {.magic: "NSetSymbol".} -proc `ident=`*(n: PNimrodNode, val: TNimrodIdent) {.magic: "NSetIdent".} -proc `typ=`*(n: PNimrodNode, typ: PNimrodType) {.magic: "NSetType".} -proc `strVal=`*(n: PNimrodNode, val: string) {.magic: "NSetStrVal".} - -proc newNimNode*(kind: TNimrodNodeKind, - n: PNimrodNode=nil): PNimrodNode {.magic: "NNewNimNode".} - -proc copyNimNode*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimNode".} -proc copyNimTree*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimTree".} - -proc error*(msg: string) {.magic: "NError".} - ## writes an error message at compile time - -proc warning*(msg: string) {.magic: "NWarning".} - ## writes a warning message at compile time - -proc hint*(msg: string) {.magic: "NHint".} - ## writes a hint message at compile time - -proc newStrLitNode*(s: string): PNimrodNode {.compileTime.} = - ## creates a string literal node from `s` - result = newNimNode(nnkStrLit) - result.strVal = s - -proc newIntLitNode*(i: biggestInt): PNimrodNode {.compileTime.} = - ## creates a int literal node from `i` - result = newNimNode(nnkIntLit) - result.intVal = i - -proc newFloatLitNode*(f: biggestFloat): PNimrodNode {.compileTime.} = - ## creates a float literal node from `f` - result = newNimNode(nnkFloatLit) - result.floatVal = f - -proc newIdentNode*(i: TNimrodIdent): PNimrodNode {.compileTime.} = - ## creates an identifier node from `i` - result = newNimNode(nnkIdent) - result.ident = i - -proc newIdentNode*(i: string): PNimrodNode {.compileTime.} = - ## creates an identifier node from `i` - result = newNimNode(nnkIdent) - result.ident = !i + nnkReturnToken + TNimNodeKinds* = set[TNimrodNodeKind] + TNimrodTypeKind* = enum + ntyNone, ntyBool, ntyChar, ntyEmpty, + ntyArrayConstr, ntyNil, ntyExpr, ntyStmt, + ntyTypeDesc, ntyGenericInvokation, ntyGenericBody, ntyGenericInst, + ntyGenericParam, ntyDistinct, ntyEnum, ntyOrdinal, + ntyArray, ntyObject, ntyTuple, ntySet, + ntyRange, ntyPtr, ntyRef, ntyVar, + ntySequence, ntyProc, ntyPointer, ntyOpenArray, + ntyString, ntyCString, ntyForward, ntyInt, + ntyInt8, ntyInt16, ntyInt32, ntyInt64, + ntyFloat, ntyFloat32, ntyFloat64, ntyFloat128 + TNimTypeKinds* = set[TNimrodTypeKind] + TNimrodSymKind* = enum + nskUnknown, nskConditional, nskDynLib, nskParam, + nskGenericParam, nskTemp, nskType, nskConst, + nskVar, nskProc, nskMethod, nskIterator, + nskConverter, nskMacro, nskTemplate, nskField, + nskEnumField, nskForVar, nskModule, nskLabel, + nskStub + TNimSymKinds* = set[TNimrodSymKind] + +type + TNimrodIdent* = object of TObject + ## represents a Nimrod identifier in the AST + + TNimrodSymbol {.final.} = object # hidden + PNimrodSymbol* {.compilerproc.} = ref TNimrodSymbol + ## represents a Nimrod *symbol* in the compiler; a *symbol* is a looked-up + ## *ident*. + +const + nnkLiterals* = {nnkCharLit..nnkNilLit} + nnkCallKinds* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand, + nnkCallStrLit} + +proc `[]`*(n: PNimrodNode, i: int): PNimrodNode {.magic: "NChild".} + ## get `n`'s `i`'th child. + +proc `[]=`*(n: PNimrodNode, i: int, child: PNimrodNode) {.magic: "NSetChild".} + ## set `n`'s `i`'th child to `child`. + +proc `!`*(s: string): TNimrodIdent {.magic: "StrToIdent".} + ## constructs an identifier from the string `s` + +proc `$`*(i: TNimrodIdent): string {.magic: "IdentToStr".} + ## converts a Nimrod identifier to a string + +proc `$`*(s: PNimrodSymbol): string {.magic: "IdentToStr".} + ## converts a Nimrod symbol to a string + +proc `==`*(a, b: TNimrodIdent): bool {.magic: "EqIdent", noSideEffect.} + ## compares two Nimrod identifiers + +proc `==`*(a, b: PNimrodNode): bool {.magic: "EqNimrodNode", noSideEffect.} + ## compares two Nimrod nodes + +proc len*(n: PNimrodNode): int {.magic: "NLen".} + ## returns the number of children of `n`. + +proc add*(father, child: PNimrodNode) {.magic: "NAdd".} + ## adds the `child` to the `father` node + +proc add*(father: PNimrodNode, children: varargs[PNimrodNode]) {. + magic: "NAddMultiple".} + ## adds each child of `children` to the `father` node + +proc del*(father: PNimrodNode, idx = 0, n = 1) {.magic: "NDel".} + ## deletes `n` children of `father` starting at index `idx`. + +proc kind*(n: PNimrodNode): TNimrodNodeKind {.magic: "NKind".} + ## returns the `kind` of the node `n`. + +proc intVal*(n: PNimrodNode): biggestInt {.magic: "NIntVal".} +proc floatVal*(n: PNimrodNode): biggestFloat {.magic: "NFloatVal".} +proc symbol*(n: PNimrodNode): PNimrodSymbol {.magic: "NSymbol".} +proc ident*(n: PNimrodNode): TNimrodIdent {.magic: "NIdent".} +proc typ*(n: PNimrodNode): typedesc {.magic: "NGetType".} +proc strVal*(n: PNimrodNode): string {.magic: "NStrVal".} + +proc `intVal=`*(n: PNimrodNode, val: biggestInt) {.magic: "NSetIntVal".} +proc `floatVal=`*(n: PNimrodNode, val: biggestFloat) {.magic: "NSetFloatVal".} +proc `symbol=`*(n: PNimrodNode, val: PNimrodSymbol) {.magic: "NSetSymbol".} +proc `ident=`*(n: PNimrodNode, val: TNimrodIdent) {.magic: "NSetIdent".} +proc `typ=`*(n: PNimrodNode, typ: typedesc) {.magic: "NSetType".} +proc `strVal=`*(n: PNimrodNode, val: string) {.magic: "NSetStrVal".} + +proc newNimNode*(kind: TNimrodNodeKind, + n: PNimrodNode=nil): PNimrodNode {.magic: "NNewNimNode".} + +proc copyNimNode*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimNode".} +proc copyNimTree*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimTree".} + +proc error*(msg: string) {.magic: "NError".} + ## writes an error message at compile time + +proc warning*(msg: string) {.magic: "NWarning".} + ## writes a warning message at compile time + +proc hint*(msg: string) {.magic: "NHint".} + ## writes a hint message at compile time + +proc newStrLitNode*(s: string): PNimrodNode {.compileTime.} = + ## creates a string literal node from `s` + result = newNimNode(nnkStrLit) + result.strVal = s + +proc newIntLitNode*(i: biggestInt): PNimrodNode {.compileTime.} = + ## creates a int literal node from `i` + result = newNimNode(nnkIntLit) + result.intVal = i + +proc newFloatLitNode*(f: biggestFloat): PNimrodNode {.compileTime.} = + ## creates a float literal node from `f` + result = newNimNode(nnkFloatLit) + result.floatVal = f + +proc newIdentNode*(i: TNimrodIdent): PNimrodNode {.compileTime.} = + ## creates an identifier node from `i` + result = newNimNode(nnkIdent) + result.ident = i + +proc newIdentNode*(i: string): PNimrodNode {.compileTime.} = + ## creates an identifier node from `i` + result = newNimNode(nnkIdent) + result.ident = !i type TBindSymRule* = enum ## specifies how ``bindSym`` behaves @@ -212,157 +206,157 @@ proc bindSym*(ident: string, rule: TBindSymRule = brClosed): PNimrodNode {. ## returned even if the symbol is not ambiguous. proc callsite*(): PNimrodNode {.magic: "NCallSite".} - ## returns the AST if the invokation expression that invoked this macro. - -proc toStrLit*(n: PNimrodNode): PNimrodNode {.compileTime.} = - ## converts the AST `n` to the concrete Nimrod code and wraps that - ## in a string literal node - return newStrLitNode(repr(n)) - -proc lineinfo*(n: PNimrodNode): string {.magic: "NLineInfo".} - ## returns the position the node appears in the original source file - ## in the form filename(line, col) - -proc parseExpr*(s: string): PNimrodNode {.magic: "ParseExprToAst".} - ## Compiles the passed string to its AST representation. - ## Expects a single expression. - -proc parseStmt*(s: string): PNimrodNode {.magic: "ParseStmtToAst".} - ## Compiles the passed string to its AST representation. - ## Expects one or more statements. - -proc getAst*(macroOrTemplate: expr): PNimrodNode {.magic: "ExpandToAst".} - ## Obtains the AST nodes returned from a macro or template invocation. - ## Example: - ## - ## .. code-block:: nimrod - ## - ## macro FooMacro() = - ## var ast = getAst(BarTemplate()) - -template emit*(s: expr): stmt = - ## accepts a single string argument and treats it as nimrod code - ## that should be inserted verbatim in the program - ## Example: - ## - ## emit("echo " & '"' & "hello world".toUpper & '"') + ## returns the AST if the invokation expression that invoked this macro. + +proc toStrLit*(n: PNimrodNode): PNimrodNode {.compileTime.} = + ## converts the AST `n` to the concrete Nimrod code and wraps that + ## in a string literal node + return newStrLitNode(repr(n)) + +proc lineinfo*(n: PNimrodNode): string {.magic: "NLineInfo".} + ## returns the position the node appears in the original source file + ## in the form filename(line, col) + +proc parseExpr*(s: string): PNimrodNode {.magic: "ParseExprToAst".} + ## Compiles the passed string to its AST representation. + ## Expects a single expression. + +proc parseStmt*(s: string): PNimrodNode {.magic: "ParseStmtToAst".} + ## Compiles the passed string to its AST representation. + ## Expects one or more statements. + +proc getAst*(macroOrTemplate: expr): PNimrodNode {.magic: "ExpandToAst".} + ## Obtains the AST nodes returned from a macro or template invocation. + ## Example: + ## + ## .. code-block:: nimrod + ## + ## macro FooMacro() = + ## var ast = getAst(BarTemplate()) + +template emit*(s: expr): stmt = + ## accepts a single string argument and treats it as nimrod code + ## that should be inserted verbatim in the program + ## Example: + ## + ## emit("echo " & '"' & "hello world".toUpper & '"') ## block: const evaluated = s eval: result = evaluated.parseStmt - -proc expectKind*(n: PNimrodNode, k: TNimrodNodeKind) {.compileTime.} = - ## checks that `n` is of kind `k`. If this is not the case, - ## compilation aborts with an error message. This is useful for writing - ## macros that check the AST that is passed to them. - if n.kind != k: error("macro expects a node of kind: " & repr(k)) - -proc expectMinLen*(n: PNimrodNode, min: int) {.compileTime.} = - ## checks that `n` has at least `min` children. If this is not the case, - ## compilation aborts with an error message. This is useful for writing - ## macros that check its number of arguments. - if n.len < min: error("macro expects a node with " & $min & " children") - -proc expectLen*(n: PNimrodNode, len: int) {.compileTime.} = - ## checks that `n` has exactly `len` children. If this is not the case, - ## compilation aborts with an error message. This is useful for writing - ## macros that check its number of arguments. - if n.len != len: error("macro expects a node with " & $len & " children") - -proc newCall*(theProc: PNimrodNode, - args: varargs[PNimrodNode]): PNimrodNode {.compileTime.} = - ## produces a new call node. `theProc` is the proc that is called with - ## the arguments ``args[0..]``. - result = newNimNode(nnkCall) - result.add(theProc) - result.add(args) - -proc newCall*(theProc: TNimrodIdent, - args: varargs[PNimrodNode]): PNimrodNode {.compileTime.} = - ## produces a new call node. `theProc` is the proc that is called with - ## the arguments ``args[0..]``. - result = newNimNode(nnkCall) - result.add(newIdentNode(theProc)) - result.add(args) - -proc newCall*(theProc: string, - args: varargs[PNimrodNode]): PNimrodNode {.compileTime.} = - ## produces a new call node. `theProc` is the proc that is called with - ## the arguments ``args[0..]``. - result = newNimNode(nnkCall) - result.add(newIdentNode(theProc)) - result.add(args) - -proc nestList*(theProc: TNimrodIdent, - x: PNimrodNode): PNimrodNode {.compileTime.} = - ## nests the list `x` into a tree of call expressions: - ## ``[a, b, c]`` is transformed into ``theProc(a, theProc(c, d))``. - var L = x.len - result = newCall(theProc, x[L-2], x[L-1]) - var a = result - for i in countdown(L-3, 0): - a = newCall(theProc, x[i], copyNimTree(a)) - -proc treeRepr*(n: PNimrodNode): string {.compileTime.} = - ## Convert the AST `n` to a human-readable tree-like string. - ## - ## See also `repr` and `lispRepr`. - proc traverse(res: var string, level: int, n: PNimrodNode) = - for i in 0..level-1: res.add " " - res.add(($n.kind).substr(3)) - - case n.kind - of nnkEmpty: nil # same as nil node in this representation - of nnkNilLit: res.add(" nil") - of nnkCharLit..nnkInt64Lit: res.add(" " & $n.intVal) - of nnkFloatLit..nnkFloat64Lit: res.add(" " & $n.floatVal) - of nnkStrLit..nnkTripleStrLit: res.add(" " & $n.strVal) - of nnkIdent: res.add(" !\"" & $n.ident & '"') + +proc expectKind*(n: PNimrodNode, k: TNimrodNodeKind) {.compileTime.} = + ## checks that `n` is of kind `k`. If this is not the case, + ## compilation aborts with an error message. This is useful for writing + ## macros that check the AST that is passed to them. + if n.kind != k: error("macro expects a node of kind: " & repr(k)) + +proc expectMinLen*(n: PNimrodNode, min: int) {.compileTime.} = + ## checks that `n` has at least `min` children. If this is not the case, + ## compilation aborts with an error message. This is useful for writing + ## macros that check its number of arguments. + if n.len < min: error("macro expects a node with " & $min & " children") + +proc expectLen*(n: PNimrodNode, len: int) {.compileTime.} = + ## checks that `n` has exactly `len` children. If this is not the case, + ## compilation aborts with an error message. This is useful for writing + ## macros that check its number of arguments. + if n.len != len: error("macro expects a node with " & $len & " children") + +proc newCall*(theProc: PNimrodNode, + args: varargs[PNimrodNode]): PNimrodNode {.compileTime.} = + ## produces a new call node. `theProc` is the proc that is called with + ## the arguments ``args[0..]``. + result = newNimNode(nnkCall) + result.add(theProc) + result.add(args) + +proc newCall*(theProc: TNimrodIdent, + args: varargs[PNimrodNode]): PNimrodNode {.compileTime.} = + ## produces a new call node. `theProc` is the proc that is called with + ## the arguments ``args[0..]``. + result = newNimNode(nnkCall) + result.add(newIdentNode(theProc)) + result.add(args) + +proc newCall*(theProc: string, + args: varargs[PNimrodNode]): PNimrodNode {.compileTime.} = + ## produces a new call node. `theProc` is the proc that is called with + ## the arguments ``args[0..]``. + result = newNimNode(nnkCall) + result.add(newIdentNode(theProc)) + result.add(args) + +proc nestList*(theProc: TNimrodIdent, + x: PNimrodNode): PNimrodNode {.compileTime.} = + ## nests the list `x` into a tree of call expressions: + ## ``[a, b, c]`` is transformed into ``theProc(a, theProc(c, d))``. + var L = x.len + result = newCall(theProc, x[L-2], x[L-1]) + var a = result + for i in countdown(L-3, 0): + a = newCall(theProc, x[i], copyNimTree(a)) + +proc treeRepr*(n: PNimrodNode): string {.compileTime.} = + ## Convert the AST `n` to a human-readable tree-like string. + ## + ## See also `repr` and `lispRepr`. + proc traverse(res: var string, level: int, n: PNimrodNode) = + for i in 0..level-1: res.add " " + res.add(($n.kind).substr(3)) + + case n.kind + of nnkEmpty: nil # same as nil node in this representation + of nnkNilLit: res.add(" nil") + of nnkCharLit..nnkInt64Lit: res.add(" " & $n.intVal) + of nnkFloatLit..nnkFloat64Lit: res.add(" " & $n.floatVal) + of nnkStrLit..nnkTripleStrLit: res.add(" " & $n.strVal) + of nnkIdent: res.add(" !\"" & $n.ident & '"') of nnkSym: res.add(" \"" & $n.symbol & '"') - of nnkNone: assert false - else: - for j in 0..n.len-1: - res.add "\n" - traverse(res, level + 1, n[j]) - - result = "" - traverse(result, 0, n) - -proc lispRepr*(n: PNimrodNode): string {.compileTime.} = - ## Convert the AST `n` to a human-readable lisp-like string, - ## - ## See also `repr` and `treeRepr`. - - result = ($n.kind).substr(3) - add(result, "(") - - case n.kind - of nnkEmpty: nil # same as nil node in this representation - of nnkNilLit: add(result, "nil") - of nnkCharLit..nnkInt64Lit: add(result, $n.intVal) - of nnkFloatLit..nnkFloat64Lit: add(result, $n.floatVal) - of nnkStrLit..nnkTripleStrLit: add(result, $n.strVal) - of nnkIdent: add(result, "!\"" & $n.ident & '"') - of nnkSym, nnkNone: assert false - else: - add(result, lispRepr(n[0])) - for j in 1..n.len-1: - add(result, ", ") - add(result, lispRepr(n[j])) - - add(result, ")") - -macro dumpTree*(s: stmt): stmt = echo s.treeRepr - ## Accepts a block of nimrod code and prints the parsed abstract syntax - ## tree using the `toTree` function. Printing is done *at compile time*. - ## - ## You can use this as a tool to explore the Nimrod's abstract syntax - ## tree and to discover what kind of nodes must be created to represent - ## a certain expression/statement. - -macro dumpLisp*(s: stmt): stmt = echo s.lispRepr - ## Accepts a block of nimrod code and prints the parsed abstract syntax - ## tree using the `toLisp` function. Printing is done *at compile time*. - ## - ## See `dumpTree`. - + of nnkNone: assert false + else: + for j in 0..n.len-1: + res.add "\n" + traverse(res, level + 1, n[j]) + + result = "" + traverse(result, 0, n) + +proc lispRepr*(n: PNimrodNode): string {.compileTime.} = + ## Convert the AST `n` to a human-readable lisp-like string, + ## + ## See also `repr` and `treeRepr`. + + result = ($n.kind).substr(3) + add(result, "(") + + case n.kind + of nnkEmpty: nil # same as nil node in this representation + of nnkNilLit: add(result, "nil") + of nnkCharLit..nnkInt64Lit: add(result, $n.intVal) + of nnkFloatLit..nnkFloat64Lit: add(result, $n.floatVal) + of nnkStrLit..nnkTripleStrLit: add(result, $n.strVal) + of nnkIdent: add(result, "!\"" & $n.ident & '"') + of nnkSym, nnkNone: assert false + else: + add(result, lispRepr(n[0])) + for j in 1..n.len-1: + add(result, ", ") + add(result, lispRepr(n[j])) + + add(result, ")") + +macro dumpTree*(s: stmt): stmt = echo s.treeRepr + ## Accepts a block of nimrod code and prints the parsed abstract syntax + ## tree using the `toTree` function. Printing is done *at compile time*. + ## + ## You can use this as a tool to explore the Nimrod's abstract syntax + ## tree and to discover what kind of nodes must be created to represent + ## a certain expression/statement. + +macro dumpLisp*(s: stmt): stmt = echo s.lispRepr + ## Accepts a block of nimrod code and prints the parsed abstract syntax + ## tree using the `toLisp` function. Printing is done *at compile time*. + ## + ## See `dumpTree`. + diff --git a/tests/compile/tmacrotypes.nim b/tests/compile/tmacrotypes.nim new file mode 100644 index 000000000..7697dba27 --- /dev/null +++ b/tests/compile/tmacrotypes.nim @@ -0,0 +1,12 @@ +import macros, typetraits + +macro checkType(ex, expected: expr): stmt {.immediate.} = + var t = ex.typ + assert t.name == expected.strVal + +proc voidProc = echo "hello" +proc intProc(a, b): int = 10 + +checkType(voidProc(), "void") +checkType(intProc(10, 20.0), "int") +checkType(noproc(10, 20.0), "Error Type") -- cgit 1.4.1-2-gfad0