using statement (ala C#) implemented as macro (added as test).

simplified the usage of the interpolatedFragments iterator.
This commit is contained in:
Zahary Karadjov 2011-09-11 21:43:12 +03:00
commit 9acfc43119
5 changed files with 152 additions and 49 deletions

View file

@ -1381,6 +1381,7 @@ proc parseString(s: string, filename: string = "", line: int = 0): PNode =
var parser : TParser var parser : TParser
OpenParser(parser, filename, stream) OpenParser(parser, filename, stream)
result = parser.parseAll result = parser.parseAll
CloseParser(parser)

View file

@ -903,7 +903,7 @@ proc semExpandMacroToAst(c: PContext, n: PNode, flags: TExprFlags): PNode =
var s = qualifiedLookup(c, macroCall.sons[0], {checkUndeclared}) var s = qualifiedLookup(c, macroCall.sons[0], {checkUndeclared})
if s == nil: if s == nil:
GlobalError(n.info, errUndeclaredIdentifier, macroCall.sons[0].strVal) GlobalError(n.info, errUndeclaredIdentifier, macroCall.sons[0].renderTree)
var expanded : Pnode var expanded : Pnode

View file

@ -76,18 +76,17 @@ proc parseIdent*(s: string, ident: var string, start = 0): int =
ident = substr(s, start, i-1) ident = substr(s, start, i-1)
result = i-start result = i-start
proc parseIdent*(s: string, start = 0): TOptional[string] = proc parseIdent*(s: string, start = 0): string =
## parses an identifier and stores it in ``ident``. Returns ## parses an identifier and stores it in ``ident``.
## the number of the parsed characters or 0 in case of an error. ## Returns the parsed identifier or an empty string in case of an error.
result.hasValue = false result = ""
var i = start var i = start
if s[i] in IdentStartChars: if s[i] in IdentStartChars:
inc(i) inc(i)
while s[i] in IdentChars: inc(i) while s[i] in IdentChars: inc(i)
result.hasValue = true result = substr(s, start, i-1)
result.value = substr(s, start, i-1)
proc parseToken*(s: string, token: var string, validChars: set[char], proc parseToken*(s: string, token: var string, validChars: set[char],
start = 0): int {.inline, deprecated.} = start = 0): int {.inline, deprecated.} =
@ -284,10 +283,20 @@ proc isEscaped*(s: string, pos: int) : bool =
return backslashes mod 2 != 0 return backslashes mod 2 != 0
type type
TInterpStrFragment* = tuple[interpStart, interpEnd, exprStart, exprEnd: int] TInterpolatedKind* = enum
ikString, ikExpr
TInterpStrFragment* = tuple[kind: TInterpolatedKind, value: string]
iterator interpolatedFragments*(s: string): TInterpStrFragment = 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: while i < s.len:
# The $ sign marks the start of an interpolation. # 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) # (so it should be before the end of the string)
# if the dollar sign is escaped, don't trigger interpolation # if the dollar sign is escaped, don't trigger interpolation
if s[i] == '$' and i < (s.len - 1) and not isEscaped(s, i): 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] var next = s[i+1]
if next == '{': if next == '{':
# Complex expression: ${foo(bar) in {1..100}}
# Find closing braket, while respecting any nested brackets
inc i inc i
tokenStart = i + 1
var var
brackets = {'{', '}'} brackets = {'{', '}'}
nestingCount = 1 nestingCount = 1
start = i + 1
# find closing braket, while respecting any nested brackets
while i < s.len: while i < s.len:
inc i, skipUntil(s, brackets, i+1) + 1 inc i, skipUntil(s, brackets, i+1) + 1
@ -316,33 +330,30 @@ iterator interpolatedFragments*(s: string): TInterpStrFragment =
else: else:
inc nestingCount inc nestingCount
var t : TInterpStrFragment yield token(ikExpr, s[tokenStart..(i-1)])
t.interpStart = start - 2
t.interpEnd = i
t.exprStart = start
t.exprEnd = i - 1
yield t tokenStart = i + 1
else: else:
var tokenStart = i + 1
start = i + 1 var identifier = parseIdent(s, i+1)
identifier = parseIdent(s, i+1)
if identifier.hasValue: if identifier.len > 0:
inc i, identifier.value.len inc i, identifier.len
var t : TInterpStrFragment yield token(ikExpr, s[tokenStart..i])
t.interpStart = start - 1
t.interpEnd = i
t.exprStart = start
t.exprEnd = i
yield t tokenStart = i + 1
else: else:
raise newException(EInvalidValue, "Unable to parse a varible name at " & s[i..s.len]) raise newException(EInvalidValue, "Unable to parse a varible name at " & s[i..s.len])
inc i 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.} {.pop.}

View file

@ -1,6 +1,6 @@
discard """ discard """
file: "tstringinterp.nim" file: "tstringinterp.nim"
output: "Hello Alice \$ 64 | Hello Bob, 10" output: "Hello Alice, 64 | Hello Bob, 10"
""" """
import macros, parseutils, strutils import macros, parseutils, strutils
@ -13,23 +13,12 @@ proc concat(strings: openarray[string]) : string =
template ProcessInterpolations(e: expr) = template ProcessInterpolations(e: expr) =
var var
s = e[1].strVal 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 for f in interpolatedFragments(s):
if f.kind == ikString:
if stringStart != s.len: addString(f.value)
var endingString = s[stringStart..s.len] else:
addString(endingString) addExpr(f.value)
macro formatStyleInterpolation(e: expr): expr = macro formatStyleInterpolation(e: expr): expr =
var var
@ -75,7 +64,7 @@ var
c = 34 c = 34
var 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)}" s2 = formatStyleInterpolation"Hello ${bob}, ${sum (alice.len, bob.len, 2)}"
write(stdout, s1 & " | " & s2) write(stdout, s1 & " | " & s2)

View file

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