summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorZahary Karadjov <zahary@gmail.comy>2011-09-11 21:43:12 +0300
committerZahary Karadjov <zahary@gmail.comy>2011-09-20 14:13:45 +0300
commit9acfc43119d42d7265497fd6ae60eda3903d9938 (patch)
treee9d729a2080c5bca1b45c979865977e43babb3f5
parentdbcca9b3b994e3339c9a03c3846b7162147679fd (diff)
downloadNim-9acfc43119d42d7265497fd6ae60eda3903d9938.tar.gz
using statement (ala C#) implemented as macro (added as test).
simplified the usage of the interpolatedFragments iterator.
-rwxr-xr-xcompiler/parser.nim3
-rwxr-xr-xcompiler/semexprs.nim2
-rwxr-xr-xlib/pure/parseutils.nim69
-rw-r--r--tests/accept/run/tstringinterp.nim25
-rw-r--r--tests/accept/run/tusingstatement.nim102
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
+
+