using statement (ala C#) implemented as macro (added as test).
simplified the usage of the interpolatedFragments iterator.
This commit is contained in:
parent
dbcca9b3b9
commit
9acfc43119
5 changed files with 152 additions and 49 deletions
|
|
@ -1383,4 +1383,5 @@ proc parseString(s: string, filename: string = "", line: int = 0): PNode =
|
||||||
OpenParser(parser, filename, stream)
|
OpenParser(parser, filename, stream)
|
||||||
|
|
||||||
result = parser.parseAll
|
result = parser.parseAll
|
||||||
|
CloseParser(parser)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
var next = s[i+1]
|
# 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 == '{':
|
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.}
|
||||||
|
|
|
||||||
|
|
@ -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):
|
for f in interpolatedFragments(s):
|
||||||
var leadingString = s[stringStart..i.interpStart-1]
|
if f.kind == ikString:
|
||||||
var interpolatedExpr = s[i.exprStart..i.exprEnd]
|
addString(f.value)
|
||||||
|
else:
|
||||||
addString(leadingString)
|
addExpr(f.value)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
||||||
102
tests/accept/run/tusingstatement.nim
Normal file
102
tests/accept/run/tusingstatement.nim
Normal 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
|
||||||
|
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue