diff options
-rwxr-xr-x | compiler/parser.nim | 3 | ||||
-rwxr-xr-x | compiler/semexprs.nim | 2 | ||||
-rwxr-xr-x | lib/pure/parseutils.nim | 69 | ||||
-rw-r--r-- | tests/accept/run/tstringinterp.nim | 25 | ||||
-rw-r--r-- | tests/accept/run/tusingstatement.nim | 102 |
5 files changed, 152 insertions, 49 deletions
diff --git a/compiler/parser.nim b/compiler/parser.nim index c371e09cb..df21c9916 100755 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -1381,6 +1381,7 @@ proc parseString(s: string, filename: string = "", line: int = 0): PNode = var parser : TParser OpenParser(parser, filename, stream) - + result = parser.parseAll + CloseParser(parser) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index af4d4b6bc..6dbbba7b8 100755 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -903,7 +903,7 @@ proc semExpandMacroToAst(c: PContext, n: PNode, flags: TExprFlags): PNode = var s = qualifiedLookup(c, macroCall.sons[0], {checkUndeclared}) if s == nil: - GlobalError(n.info, errUndeclaredIdentifier, macroCall.sons[0].strVal) + GlobalError(n.info, errUndeclaredIdentifier, macroCall.sons[0].renderTree) var expanded : Pnode diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index a046aff36..1f4e54b96 100755 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -76,18 +76,17 @@ 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): TOptional[string] = - ## parses an identifier and stores it in ``ident``. Returns - ## the number of the parsed characters or 0 in case of an error. - result.hasValue = false +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.hasValue = true - result.value = substr(s, start, i-1) + result = substr(s, start, i-1) proc parseToken*(s: string, token: var string, validChars: set[char], start = 0): int {.inline, deprecated.} = @@ -284,10 +283,20 @@ proc isEscaped*(s: string, pos: int) : bool = return backslashes mod 2 != 0 type - TInterpStrFragment* = tuple[interpStart, interpEnd, exprStart, exprEnd: int] + TInterpolatedKind* = enum + ikString, ikExpr + + TInterpStrFragment* = tuple[kind: TInterpolatedKind, value: string] iterator interpolatedFragments*(s: string): TInterpStrFragment = - var i = 0 + 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. # @@ -295,17 +304,22 @@ iterator interpolatedFragments*(s: string): TInterpStrFragment = # (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 - start = i + 1 - - # find closing braket, while respecting any nested brackets + while i < s.len: inc i, skipUntil(s, brackets, i+1) + 1 @@ -316,33 +330,30 @@ iterator interpolatedFragments*(s: string): TInterpStrFragment = else: inc nestingCount - var t : TInterpStrFragment - t.interpStart = start - 2 - t.interpEnd = i - t.exprStart = start - t.exprEnd = i - 1 + yield token(ikExpr, s[tokenStart..(i-1)]) - yield t + tokenStart = i + 1 else: - var - start = i + 1 - identifier = parseIdent(s, i+1) + tokenStart = i + 1 + var identifier = parseIdent(s, i+1) - if identifier.hasValue: - inc i, identifier.value.len + if identifier.len > 0: + inc i, identifier.len - var t : TInterpStrFragment - t.interpStart = start - 1 - t.interpEnd = i - t.exprStart = start - t.exprEnd = i + yield token(ikExpr, s[tokenStart..i]) - yield t + 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/tests/accept/run/tstringinterp.nim b/tests/accept/run/tstringinterp.nim index 83bd37709..55baae7ec 100644 --- a/tests/accept/run/tstringinterp.nim +++ b/tests/accept/run/tstringinterp.nim @@ -1,6 +1,6 @@ discard """ file: "tstringinterp.nim" - output: "Hello Alice \$ 64 | Hello Bob, 10" + output: "Hello Alice, 64 | Hello Bob, 10" """ import macros, parseutils, strutils @@ -13,23 +13,12 @@ proc concat(strings: openarray[string]) : string = template ProcessInterpolations(e: expr) = var s = e[1].strVal - stringStart = 0 - - for i in interpolatedFragments(s): - var leadingString = s[stringStart..i.interpStart-1] - var interpolatedExpr = s[i.exprStart..i.exprEnd] - - addString(leadingString) - - var interpTargetAst = parseExpr("$(x)") - interpTargetAst[1][0] = parseExpr(interpolatedExpr) - addExpr(interpTargetAst) - stringStart = i.interpEnd + 1 - - if stringStart != s.len: - var endingString = s[stringStart..s.len] - addString(endingString) + for f in interpolatedFragments(s): + if f.kind == ikString: + addString(f.value) + else: + addExpr(f.value) macro formatStyleInterpolation(e: expr): expr = var @@ -75,7 +64,7 @@ var c = 34 var - s1 = concatStyleInterpolation"Hello ${alice} \$ ${sum (a, b, c)}" + 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 + + |