diff options
-rwxr-xr-x | compiler/ast.nim | 43 | ||||
-rwxr-xr-x | compiler/evals.nim | 36 | ||||
-rwxr-xr-x | compiler/parser.nim | 16 | ||||
-rwxr-xr-x | compiler/semexprs.nim | 45 | ||||
-rwxr-xr-x | compiler/semfold.nim | 1 | ||||
-rwxr-xr-x | compiler/semstmts.nim | 2 | ||||
-rwxr-xr-x | lib/core/macros.nim | 49 | ||||
-rwxr-xr-x | lib/pure/parseutils.nim | 102 | ||||
-rw-r--r-- | lib/pure/uuid.nim | 30 | ||||
-rwxr-xr-x | lib/system.nim | 9 | ||||
-rwxr-xr-x | tests/accept/compile/tdumpast.nim | 39 | ||||
-rw-r--r-- | tests/accept/run/tstringinterp.nim | 71 | ||||
-rw-r--r-- | tests/accept/run/tusingstatement.nim | 102 |
13 files changed, 492 insertions, 53 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index bb9803830..85ec2c9b3 100755 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -57,10 +57,10 @@ type nkStrLit, # a string literal "" nkRStrLit, # a raw string literal r"" nkTripleStrLit, # a triple string literal """ - nkMetaNode, # difficult to explain; represents itself - # (used for macros) nkNilLit, # the nil literal # end of atoms + nkMetaNode, # difficult to explain; represents itself + # (used for macros) nkDotCall, # used to temporarily flag a nkCall node; # this is used # for transforming ``s.len`` to ``len(s)`` @@ -313,6 +313,7 @@ type TMagic* = enum # symbols that require compiler magic: mNone, mDefined, mDefinedInScope, mLow, mHigh, mSizeOf, mIs, mOf, mEcho, mShallowCopy, mSlurp, + mAstToYaml, mParseExprToAst, mParseStmtToAst, mExpandMacroToAst, mUnaryLt, mSucc, mPred, mInc, mDec, mOrd, mNew, mNewFinalize, mNewSeq, mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq, mIncl, mExcl, mCard, mChr, mGCref, @@ -610,6 +611,23 @@ proc copyTree*(src: PNode): PNode proc discardSons*(father: PNode) +proc len*(n: PNode): int {.inline.} = + if isNil(n.sons): result = 0 + else: result = len(n.sons) + +proc safeLen*(n: PNode): int {.inline.} = + ## works even for leaves. + if n.kind in {nkNone..nkNilLit} or isNil(n.sons): result = 0 + else: result = len(n.sons) + +proc add*(father, son: PNode) = + assert son != nil + if isNil(father.sons): father.sons = @[] + add(father.sons, son) + +proc `[]`*(n: PNode, i: int): PNode {.inline.} = + result = n.sons[i] + var emptyNode* = newNode(nkEmpty) # There is a single empty node that is shared! Do not overwrite it! @@ -754,6 +772,10 @@ proc newNodeIT(kind: TNodeKind, info: TLineInfo, typ: PType): PNode = result.info = info result.typ = typ +proc newMetaNodeIT*(tree: PNode, info: TLineInfo, typ: PType): PNode = + result = newNodeIT(nkMetaNode, info, typ) + result.add(tree) + proc NewType(kind: TTypeKind, owner: PSym): PType = new(result) result.kind = kind @@ -866,23 +888,6 @@ proc sonsLen(n: PNode): int = if isNil(n.sons): result = 0 else: result = len(n.sons) -proc len*(n: PNode): int {.inline.} = - if isNil(n.sons): result = 0 - else: result = len(n.sons) - -proc safeLen*(n: PNode): int {.inline.} = - ## works even for leaves. - if n.kind in {nkNone..nkNilLit} or isNil(n.sons): result = 0 - else: result = len(n.sons) - -proc add*(father, son: PNode) = - assert son != nil - if isNil(father.sons): father.sons = @[] - add(father.sons, son) - -proc `[]`*(n: PNode, i: int): PNode {.inline.} = - result = n.sons[i] - proc newSons(father: PNode, length: int) = if isNil(father.sons): father.sons = @[] setlen(father.sons, len(father.sons) + length) diff --git a/compiler/evals.nim b/compiler/evals.nim index 55157ea45..65c64f4d3 100755 --- a/compiler/evals.nim +++ b/compiler/evals.nim @@ -15,7 +15,8 @@ import strutils, magicsys, lists, options, ast, astalgo, trees, treetab, nimsets, - msgs, os, condsyms, idents, renderer, types, passes, semfold, transf + msgs, os, condsyms, idents, renderer, types, passes, semfold, transf, + parser, ropes type PStackFrame* = ref TStackFrame @@ -751,6 +752,31 @@ proc evalRepr(c: PEvalContext, n: PNode): PNode = proc isEmpty(n: PNode): bool = result = (n != nil) and (n.kind == nkEmpty) +# The lexer marks multi-line strings as residing at the line where they are closed +# This function returns the line where the string begins +# Maybe the lexer should mark both the beginning and the end of expressions, then +# this function could be removed +proc stringStartingLine(s: PNode): int = + var totalLines = 0 + for ln in splitLines(s.strVal): inc totalLines + + result = s.info.line - totalLines + +proc evalParseExpr(c: PEvalContext, n: Pnode): Pnode = + var code = evalAux(c, n.sons[1], {}) + var ast = parseString(code.getStrValue, code.info.toFilename, code.stringStartingLine) + + if sonsLen(ast) != 1: + GlobalError(code.info, errExprExpected, "multiple statements") + + result = ast.sons[0] + result.typ = newType(tyExpr, c.module) + +proc evalParseStmt(c: PEvalContext, n: Pnode): Pnode = + var code = evalAux(c, n.sons[1], {}) + result = parseString(code.getStrValue, code.info.toFilename, code.stringStartingLine) + result.typ = newType(tyStmt, c.module) + proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode = var m = getMagic(n) case m @@ -775,6 +801,8 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode = of mAppendStrCh: result = evalAppendStrCh(c, n) of mAppendStrStr: result = evalAppendStrStr(c, n) of mAppendSeqElem: result = evalAppendSeqElem(c, n) + of mParseExprToAst: result = evalParseExpr(c, n) + of mParseStmtToAst: result = evalParseStmt(c, n) of mNLen: result = evalAux(c, n.sons[1], {efLValue}) if isSpecial(result): return @@ -981,6 +1009,9 @@ proc evalMagicOrCall(c: PEvalContext, n: PNode): PNode = if (a == b) or (b.kind in {nkNilLit, nkEmpty}) and (a.kind in {nkNilLit, nkEmpty}): result.intVal = 1 + of mAstToYaml: + var ast = evalAux(c, n.sons[1], {efLValue}) + result = newStrNode(nkStrLit, ast.treeToYaml.ropeToStr) of mNHint: result = evalAux(c, n.sons[1], {}) if isSpecial(result): return @@ -1034,6 +1065,9 @@ proc evalAux(c: PEvalContext, n: PNode, flags: TEvalFlags): PNode = dec(gNestedEvals) if gNestedEvals <= 0: stackTrace(c, n, errTooManyIterations) case n.kind # atoms: + of nkMetaNode: + result = copyTree(n.sons[0]) + result.typ = n.typ of nkEmpty: result = n of nkSym: result = evalSym(c, n, flags) of nkType..nkNilLit: result = copyNode(n) # end of atoms diff --git a/compiler/parser.nim b/compiler/parser.nim index 003bd9219..df21c9916 100755 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -30,6 +30,11 @@ proc closeParser*(p: var TParser) proc parseTopLevelStmt*(p: var TParser): PNode # implements an iterator. Returns the next top-level statement or # emtyNode if end of stream. + +proc parseString*(s: string, filename: string = "", line: int = 0): PNode + # filename and line could be set optionally, when the string originates + # from a certain source file. This way, the compiler could generate + # correct error messages referring to the original source. # helpers for the other parsers proc getPrecedence*(tok: TToken): int @@ -1369,3 +1374,14 @@ proc parseTopLevelStmt(p: var TParser): PNode = result = complexOrSimpleStmt(p) if result.kind == nkEmpty: parMessage(p, errExprExpected, p.tok) break + +proc parseString(s: string, filename: string = "", line: int = 0): PNode = + var stream = LLStreamOpen(s) + stream.lineOffset = line + + var parser : TParser + OpenParser(parser, filename, stream) + + result = parser.parseAll + CloseParser(parser) + diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index f86a4f60d..6dbbba7b8 100755 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -8,6 +8,7 @@ # # this module does the semantic checking for expressions + const ConstAbstractTypes = {tyNil, tyChar, tyInt..tyInt64, tyFloat..tyFloat128, tyArrayConstr, tyTuple, tySet} @@ -384,6 +385,10 @@ proc isAssignable(c: PContext, n: PNode): TAssignableResult = else: nil +proc isCallExpr(n: PNode): bool = + result = n.kind in {nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, + nkCallStrLit} + proc newHiddenAddrTaken(c: PContext, n: PNode): PNode = if n.kind == nkHiddenDeref: checkSonsLen(n, 1) @@ -742,7 +747,7 @@ proc semSubscript(c: PContext, n: PNode, flags: TExprFlags): PNode = checkSonsLen(n, 2) n.sons[0] = makeDeref(n.sons[0]) # [] operator for tuples requires constant expression: - n.sons[1] = semConstExpr(c, n.sons[1]) + n.sons[1] = semAndEvalConstExpr(c, n.sons[1]) if skipTypes(n.sons[1].typ, {tyGenericInst, tyRange, tyOrdinal}).kind in {tyInt..tyInt64}: var idx = getOrdValue(n.sons[1]) @@ -883,11 +888,38 @@ proc setMs(n: PNode, s: PSym): PNode = n.sons[0] = newSymNode(s) n.sons[0].info = n.info +proc expectStringArg(c: PContext, n: PNode, i: int): PNode = + result = c.semAndEvalConstExpr(n.sons[i+1]) + + if result.kind notin {nkStrLit, nkRStrLit, nkTripleStrLit}: + GlobalError(result.info, errStringLiteralExpected) + +proc semExpandMacroToAst(c: PContext, n: PNode, flags: TExprFlags): PNode = + if sonsLen(n) == 2: + if not isCallExpr(n.sons[1]): + GlobalError(n.info, errXisNoMacroOrTemplate, n.renderTree) + + var macroCall = n.sons[1] + + var s = qualifiedLookup(c, macroCall.sons[0], {checkUndeclared}) + if s == nil: + GlobalError(n.info, errUndeclaredIdentifier, macroCall.sons[0].renderTree) + + var expanded : Pnode + + case s.kind + of skMacro: expanded = semMacroExpr(c, macroCall, s, false) + of skTemplate: expanded = semTemplateExpr(c, macroCall, s, false) + else: GlobalError(n.info, errXisNoMacroOrTemplate, s.name.s) + + var macroRetType = newTypeS(s.typ.sons[0].kind, c) + result = newMetaNodeIT(expanded, n.info, macroRetType) + else: + result = semDirectOp(c, n, flags) + proc semSlurp(c: PContext, n: PNode, flags: TExprFlags): PNode = if sonsLen(n) == 2: - var a = c.semConstExpr(c, n.sons[1]) - if a.kind notin {nkStrLit, nkRStrLit, nkTripleStrLit}: - GlobalError(a.info, errStringLiteralExpected) + var a = expectStringArg(c, n, 0) try: var content = readFile(a.strVal) result = newStrNode(nkStrLit, content) @@ -921,6 +953,7 @@ proc semMagic(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = else: result = semDirectOp(c, n, flags) of mSlurp: result = semSlurp(c, n, flags) + of mExpandMacroToAst: result = semExpandMacroToAst(c, n, flags) else: result = semDirectOp(c, n, flags) proc semIfExpr(c: PContext, n: PNode): PNode = @@ -1078,10 +1111,6 @@ proc semBlockExpr(c: PContext, n: PNode): PNode = closeScope(c.tab) Dec(c.p.nestedBlockCounter) -proc isCallExpr(n: PNode): bool = - result = n.kind in {nkCall, nkInfix, nkPrefix, nkPostfix, nkCommand, - nkCallStrLit} - proc semMacroStmt(c: PContext, n: PNode, semCheck = true): PNode = checkMinSonsLen(n, 2) var a: PNode diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 61e63a69f..570656a39 100755 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -208,6 +208,7 @@ proc evalOp(m: TMagic, n, a, b, c: PNode): PNode = of mNewString, mNewStringOfCap, mExit, mInc, ast.mDec, mEcho, mAssert, mSwap, mAppendStrCh, mAppendStrStr, mAppendSeqElem, mSetLengthStr, mSetLengthSeq, + mParseExprToAst, mParseStmtToAst, mNLen..mNError, mEqRef: nil else: InternalError(a.info, "evalOp(" & $m & ')') diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index ce870a3ad..decc8a2d7 100755 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -19,7 +19,7 @@ proc semWhen(c: PContext, n: PNode): PNode = case it.kind of nkElifBranch: checkSonsLen(it, 2) - var e = semConstBoolExpr(c, it.sons[0]) + var e = semAndEvalConstExpr(c, it.sons[0]) if (e.kind != nkIntLit): InternalError(n.info, "semWhen") if (e.intVal != 0) and (result == nil): result = semStmt(c, it.sons[1]) # do not open a new scope! diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 3b11e774c..bebcab677 100755 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -19,7 +19,7 @@ type nnkType, nnkCharLit, nnkIntLit, nnkInt8Lit, nnkInt16Lit, nnkInt32Lit, nnkInt64Lit, nnkFloatLit, nnkFloat32Lit, nnkFloat64Lit, nnkStrLit, nnkRStrLit, - nnkTripleStrLit, nnkMetaNode, nnkNilLit, nnkDotCall, + nnkTripleStrLit, nnkNilLit, nnkMetaNode, nnkDotCall, nnkCommand, nnkCall, nnkCallStrLit, nnkExprEqExpr, nnkExprColonExpr, nnkIdentDefs, nnkVarTuple, nnkInfix, nnkPrefix, nnkPostfix, nnkPar, nnkCurly, @@ -184,6 +184,53 @@ proc toStrLit*(n: PNimrodNode): PNimrodNode {.compileTime.} = ## in a string literal node return newStrLitNode(repr(n)) +proc prettyPrint*(n: PNimrodNode): string {.compileTime.} = + ## Convert the AST `n` to a human-readable string + ## + ## 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 + if n == nil: return "nil" + + result = $n.kind + 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, prettyPrint(n[0])) + for j in 1..n.len-1: + add(result, ", ") + add(result, prettyPrint(n[j])) + + add(result, ")") + +proc toYaml*(n: PNimrodNode): string {.magic: "AstToYaml".} + ## Converts the AST `n` to an YAML string + ## + ## Provides more detailed, potentially harder to digest information + ## than `prettyPrint` + +proc parseExpr*(s: string) : expr {.magic: "ParseExprToAst".} + ## Compiles the passed string to its AST representation + ## Expects a single expression + +proc parseStmt*(s: string) : stmt {.magic: "ParseStmtToAst".} + ## Compiles the passed string to its AST representation + ## Expects one or more statements + +proc getAst*(macroOrTemplate: expr): expr {.magic: "ExpandMacroToAst".} + ## Obtains the AST nodes returned from a macro or template invocation + ## example: + ## macro FooMacro() = + ## var ast = getAst(BarTemplate()) + 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 diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index d3346ecde..1f4e54b96 100755 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -76,6 +76,18 @@ proc parseIdent*(s: string, ident: var string, start = 0): int = ident = substr(s, start, i-1) result = i-start +proc parseIdent*(s: string, start = 0): string = + ## parses an identifier and stores it in ``ident``. + ## Returns the parsed identifier or an empty string in case of an error. + result = "" + var i = start + + if s[i] in IdentStartChars: + inc(i) + while s[i] in IdentChars: inc(i) + + result = substr(s, start, i-1) + proc parseToken*(s: string, token: var string, validChars: set[char], start = 0): int {.inline, deprecated.} = ## parses a token and stores it in ``token``. Returns @@ -254,4 +266,94 @@ proc parseFloat*(s: string, number: var float, start = 0): int {. result = parseBiggestFloat(s, bf, start) number = bf +proc isEscaped*(s: string, pos: int) : bool = + assert pos >= 0 and pos < s.len + + var + backslashes = 0 + j = pos - 1 + + while j >= 0: + if s[j] == '\\': + inc backslashes + dec j + else: + break + + return backslashes mod 2 != 0 + +type + TInterpolatedKind* = enum + ikString, ikExpr + + TInterpStrFragment* = tuple[kind: TInterpolatedKind, value: string] + +iterator interpolatedFragments*(s: string): TInterpStrFragment = + var + i = 0 + tokenStart = 0 + + proc token(kind: TInterpolatedKind, value: string): TInterpStrFragment = + result.kind = kind + result.value = value + + while i < s.len: + # The $ sign marks the start of an interpolation. + # + # It's followed either by a varialbe name or an opening bracket + # (so it should be before the end of the string) + # if the dollar sign is escaped, don't trigger interpolation + if s[i] == '$' and i < (s.len - 1) and not isEscaped(s, i): + # Interpolation starts here. + # Return any string that we've ran over so far. + if i != tokenStart: + yield token(ikString, s[tokenStart..i-1]) + + var next = s[i+1] + if next == '{': + # Complex expression: ${foo(bar) in {1..100}} + # Find closing braket, while respecting any nested brackets + inc i + tokenStart = i + 1 + + var + brackets = {'{', '}'} + nestingCount = 1 + + while i < s.len: + inc i, skipUntil(s, brackets, i+1) + 1 + + if not isEscaped(s, i): + if s[i] == '}': + dec nestingCount + if nestingCount == 0: break + else: + inc nestingCount + + yield token(ikExpr, s[tokenStart..(i-1)]) + + tokenStart = i + 1 + + else: + tokenStart = i + 1 + var identifier = parseIdent(s, i+1) + + if identifier.len > 0: + inc i, identifier.len + + yield token(ikExpr, s[tokenStart..i]) + + tokenStart = i + 1 + + else: + raise newException(EInvalidValue, "Unable to parse a varible name at " & s[i..s.len]) + + inc i + #end while + + # We've reached the end of the string without finding a new interpolation. + # Return the last fragment at string. + if i != tokenStart: + yield token(ikString, s[tokenStart..i]) + {.pop.} diff --git a/lib/pure/uuid.nim b/lib/pure/uuid.nim new file mode 100644 index 000000000..36fa9e445 --- /dev/null +++ b/lib/pure/uuid.nim @@ -0,0 +1,30 @@ +# This module implements the RFC 4122 specification for generating universally unique identifiers +# http://en.wikipedia.org/wiki/Universally_unique_identifier + +# This module is a work-in-progress +# If you want to help with the implementation, take a loot at: +# http://dsource.org/projects/tango/docs/current/tango.util.uuid.Uuid.html + +type TUuid* = array[0..15, char] + +when defined(windows): + # This is actually available only on Windows 2000+ + type PUuid* {.importc: "UUID __RPC_FAR *", header: "<Rpc.h>".} = ptr TUuid + proc uuid1Sys*(uuid: PUuid) {.importc: "UuidCreateSequential", header: "<Rpc.h>".} + +else: + type PUuid {.importc: "uuid_t", header: "<uuid/uuid.h>".} = ptr TUuid + proc uuid1Sys*(uuid: PUuid) {.importc: "uuid_generate_time", header: "<uuid/uuid.h>".} + +# v1 UUIDs include the MAC address of the machine generating the ID and a timestamp +# This scheme has the strongest guaranty of uniqueness, but discloses when the ID was generated +proc uuidMacTime* : TUuid = uuid1Sys(addr(result)) + +# v4 UUID are created entirely using a random number generator. +# Some bits have fixed value in order to indicate the UUID type +proc uuidRandom*[RandomGenerator](rand: RandomGenerator) : TUuid = nil + +# v3 and v5 UUIDs are derived from given namespace and name using a secure hashing algorithm. +# v3 uses MD5, v5 uses SHA1. +proc uuidByName*[Hash](namespace: TUuid, name: string, hasher: Hash, v: int) : TUuid = nil + diff --git a/lib/system.nim b/lib/system.nim index 2e754ece7..3fc4733b2 100755 --- a/lib/system.nim +++ b/lib/system.nim @@ -710,7 +710,7 @@ proc `&` * (x, y: string): string {. proc `&` * (x: char, y: string): string {. magic: "ConStrStr", noSideEffect, merge.} ## is the `concatenation operator`. It concatenates `x` and `y`. - + # implementation note: These must all have the same magic value "ConStrStr" so # that the merge optimization works properly. @@ -895,7 +895,12 @@ type # these work for most platforms: PFloat64* = ptr Float64 ## an alias for ``ptr float64`` PInt64* = ptr Int64 ## an alias for ``ptr int64`` PInt32* = ptr Int32 ## an alias for ``ptr int32`` - + +type TOptional*[T] = object + case hasValue* : bool + of true: value*: T + of false: nil + proc toFloat*(i: int): float {. magic: "ToFloat", noSideEffect, importc: "toFloat".} ## converts an integer `i` into a ``float``. If the conversion diff --git a/tests/accept/compile/tdumpast.nim b/tests/accept/compile/tdumpast.nim index fb31af0ec..8561c6e42 100755 --- a/tests/accept/compile/tdumpast.nim +++ b/tests/accept/compile/tdumpast.nim @@ -2,28 +2,26 @@ import macros -proc dumpit(n: PNimrodNode): string {.compileTime.} = - if n == nil: return "nil" - result = $n.kind - 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, dumpit(n[0])) - for j in 1..n.len-1: - add(result, ", ") - add(result, dumpit(n[j])) - add(result, ")") +template plus(a, b: expr): expr = + a + b + +macro call(e: expr): expr = + return newCall("foo", newStrLitNode("bar")) -macro dumpAST(n: stmt): stmt = +macro dumpAST(n: stmt): stmt = # dump AST as a side-effect and return the inner node - echo dumpit(n) + echo n.prettyPrint + echo n.toYaml + + var plusAst = getAst(plus(1, 2)) + echo plusAst.prettyPrint + + var callAst = getAst(call()) + echo callAst.prettyPrint + + var e = parseExpr("foo(bar + baz)") + echo e.prettyPrint + result = n[1] dumpAST: @@ -32,4 +30,3 @@ dumpAST: proc sub(x, y: int): int = return x - y - diff --git a/tests/accept/run/tstringinterp.nim b/tests/accept/run/tstringinterp.nim new file mode 100644 index 000000000..55baae7ec --- /dev/null +++ b/tests/accept/run/tstringinterp.nim @@ -0,0 +1,71 @@ +discard """ + file: "tstringinterp.nim" + output: "Hello Alice, 64 | Hello Bob, 10" +""" + +import macros, parseutils, strutils + +proc concat(strings: openarray[string]) : string = + result = newString(0) + for s in items(strings): result.add(s) + +# This will run though the intee +template ProcessInterpolations(e: expr) = + var + s = e[1].strVal + + for f in interpolatedFragments(s): + if f.kind == ikString: + addString(f.value) + else: + addExpr(f.value) + +macro formatStyleInterpolation(e: expr): expr = + var + formatString = "" + arrayNode = newNimNode(nnkBracket) + idx = 1 + + proc addString(s: string) = + formatString.add(s) + + proc addExpr(e: expr) = + arrayNode.add(e) + formatString.add("$" & $(idx)) + inc idx + + ProcessInterpolations(e) + + result = parseExpr("\"x\" % [y]") + result[1].strVal = formatString + result[2] = arrayNode + +macro concatStyleInterpolation(e: expr): expr = + var args : seq[PNimrodNode] + newSeq(args, 0) + + proc addString(s: string) = args.add(newStrLitNode(s)) + proc addExpr(e: expr) = args.add(e) + + ProcessInterpolations(e) + + result = newCall("concat", args) + +### + +proc sum(a, b, c: int): int = + return (a + b + c) + +var + alice = "Alice" + bob = "Bob" + a = 10 + b = 20 + c = 34 + +var + s1 = concatStyleInterpolation"Hello ${alice}, ${sum (a, b, c)}}" + s2 = formatStyleInterpolation"Hello ${bob}, ${sum (alice.len, bob.len, 2)}" + +write(stdout, s1 & " | " & s2) + diff --git a/tests/accept/run/tusingstatement.nim b/tests/accept/run/tusingstatement.nim new file mode 100644 index 000000000..0017af556 --- /dev/null +++ b/tests/accept/run/tusingstatement.nim @@ -0,0 +1,102 @@ +discard """ + file: "tusingstatement.nim" + output: "Using test.Closing test." +""" + +import + macros + +# This macro mimics the using statement from C# +# +# XXX: +# It doen't match the C# version exactly yet. +# In particular, it's not recursive, which prevents it from dealing +# with exceptions thrown from the variable initializers when multiple. +# variables are used. +# +# Also, since nimrod relies less on exceptions in general, a more +# idiomatic definition could be: +# var x = init() +# if opened(x): +# try: +# body +# finally: +# close(x) +# +# `opened` here could be an overloaded proc which any type can define. +# A common practice can be returing an Optional[Resource] obj for which +# `opened` is defined to `optional.hasValue` +macro using(e: expr) : stmt = + if e.len != 2: + error "Using statement: unexpected number of arguments. Got " & + $e.len & ", expected: 1 or more variable assignments and a block" + + var args = e[0] + var body = e[1] + + var + variables : seq[PNimrodNode] + closingCalls : seq[PNimrodNode] + + newSeq(variables, 0) + newSeq(closingCalls, 0) + + for i in countup(1, args.len-1): + if args[i].kind == nnkExprEqExpr: + var varName = args[i][0] + var varValue = args[i][1] + + var varAssignment = newNimNode(nnkIdentDefs) + varAssignment.add(varName) + varAssignment.add(newNimNode(nnkEmpty)) # empty means no type + varAssignment.add(varValue) + variables.add(varAssignment) + + closingCalls.add(newCall(!"close", varName)) + else: + error "Using statement: Unexpected expression. Got " & + $args[i].kind & " instead of assignment." + + var varSection = newNimNode(nnkVarSection) + varSection.add(variables) + + var finallyBlock = newNimNode(nnkStmtList) + finallyBlock.add(closingCalls) + + # XXX: Use a template here once getAst is working properly + var targetAst = parseStmt"""block: + var + x = foo() + y = bar() + + try: + body() + + finally: + close x + close y + """ + + targetAst[0][1][0] = varSection + targetAst[0][1][1][0] = body + targetAst[0][1][1][1][0] = finallyBlock + + return targetAst + +type + TResource* = object + field*: string + +proc openResource(param: string): TResource = + result.field = param + +proc close(r: var TResource) = + write(stdout, "Closing " & r.field & ".") + +proc use(r: var TResource) = + write(stdout, "Using " & r.field & ".") + +using(r = openResource("test")): + use r + + |