When parsing expansions, the parenthesis used for the expansion is special: keep a special parse state to track it.
authorBenjamin Smedberg <benjamin@smedbergs.us>
Thu, 26 Feb 2009 08:49:03 -0500
changeset 181 e0148d0161301ada4ba9214577714979a7165d66
parent 157 91ec8fafe9ce5840f5ee1c557e2dfa136a9d0f4e
child 182 d0cbe562ed8e3070e9656bfe41a072d85a377539
push id105
push userbsmedberg@mozilla.com
push dateThu, 26 Feb 2009 16:12:41 +0000
When parsing expansions, the parenthesis used for the expansion is special: keep a special parse state to track it.
pymake/parser.py
--- a/pymake/parser.py
+++ b/pymake/parser.py
@@ -666,27 +666,35 @@ def parsestream(fd, filename):
 PARSESTATE_TOPLEVEL = 0    # at the top level
 PARSESTATE_FUNCTION = 1    # expanding a function call. data is function
 
 # For the following three, data is a tuple of Expansions: (varname, substfrom, substto)
 PARSESTATE_VARNAME = 2     # expanding a variable expansion.
 PARSESTATE_SUBSTFROM = 3   # expanding a variable expansion substitution "from" value
 PARSESTATE_SUBSTTO = 4     # expanding a variable expansion substitution "to" value
 
+PARSESTATE_PARENMATCH = 5
+
 class ParseStackFrame(object):
-    def __init__(self, parsestate, expansion, tokenlist, closebrace, **kwargs):
+    def __init__(self, parsestate, expansion, tokenlist, openbrace, closebrace, **kwargs):
         self.parsestate = parsestate
         self.expansion = expansion
         self.tokenlist = tokenlist
+        self.openbrace = openbrace
         self.closebrace = closebrace
         for key, value in kwargs.iteritems():
             setattr(self, key, value)
 
 _functiontokenlist = None
 
+_matchingbrace = {
+    '(': ')',
+    '{': '}',
+    }
+
 def parsemakesyntax(d, startat, stopon, iterfunc):
     """
     Given Data, parse it into a data.Expansion.
 
     @param stopon (sequence)
         Indicate characters where toplevel parsing should stop.
 
     @param iterfunc (generator function)
@@ -707,17 +715,17 @@ def parsemakesyntax(d, startat, stopon, 
         functiontokens.sort(key=len, reverse=True)
         _functiontokenlist = TokenList.get(tuple(functiontokens))
 
     assert callable(iterfunc)
 
     stack = [
         ParseStackFrame(PARSESTATE_TOPLEVEL, data.Expansion(loc=d.getloc(startat)),
                         tokenlist=TokenList.get(stopon + ('$',)),
-                        stopon=stopon, closebrace=None)
+                        stopon=stopon, openbrace=None, closebrace=None)
     ]
 
     di = iterfunc(d, startat, stack[-1].tokenlist)
     while True: # this is not a for loop because `di` changes during the function
         stacktop = stack[-1]
         try:
             s, token, tokenoffset, offset = di.next()
         except StopIteration:
@@ -734,78 +742,91 @@ def parsemakesyntax(d, startat, stopon, 
 
             loc = d.getloc(tokenoffset)
 
             c = d.data[offset]
             if c == '$':
                 stacktop.expansion.append('$')
                 offset = offset + 1
             elif c in ('(', '{'):
-                closebrace = c == '(' and ')' or '}'
+                closebrace = _matchingbrace[c]
 
                 # look forward for a function name
                 fname, offset = d.findtoken(offset + 1, _functiontokenlist, True)
                 if fname is not None:
                     fn = functions.functionmap[fname](loc)
                     e = data.Expansion()
                     fn.append(e)
                     if len(fn) == fn.maxargs:
-                        tokenlist = TokenList.get((closebrace, '$'))
+                        tokenlist = TokenList.get((c, closebrace, '$'))
                     else:
-                        tokenlist = TokenList.get((',', closebrace, '$'))
+                        tokenlist = TokenList.get((',', c, closebrace, '$'))
 
                     stack.append(ParseStackFrame(PARSESTATE_FUNCTION,
                                                  e, tokenlist, function=fn,
-                                                 closebrace=closebrace))
+                                                 openbrace=c, closebrace=closebrace))
                     di = iterfunc(d, offset, tokenlist)
                     continue
 
                 e = data.Expansion()
-                tokenlist = TokenList.get((':', closebrace, '$'))
-                stack.append(ParseStackFrame(PARSESTATE_VARNAME, e, tokenlist, closebrace=closebrace, loc=loc))
+                tokenlist = TokenList.get((':', c, closebrace, '$'))
+                stack.append(ParseStackFrame(PARSESTATE_VARNAME, e, tokenlist,
+                                             openbrace=c, closebrace=closebrace, loc=loc))
                 di = iterfunc(d, offset, tokenlist)
                 continue
             else:
                 e = data.Expansion.fromstring(c)
                 stacktop.expansion.append(functions.VariableRef(loc, e))
                 offset += 1
+        elif token in ('(', '{'):
+            assert token == stacktop.openbrace
+
+            stacktop.expansion.append(token)
+            stack.append(ParseStackFrame(PARSESTATE_PARENMATCH,
+                                         stacktop.expansion,
+                                         TokenList.get((token, stacktop.closebrace,)),
+                                         openbrace=token, closebrace=stacktop.closebrace, loc=d.getloc(tokenoffset)))
+        elif stacktop.parsestate == PARSESTATE_PARENMATCH:
+            assert token == stacktop.closebrace
+            stacktop.expansion.append(token)
+            stack.pop()
         elif stacktop.parsestate == PARSESTATE_TOPLEVEL:
             assert len(stack) == 1
             return stacktop.expansion, token, offset
         elif stacktop.parsestate == PARSESTATE_FUNCTION:
             if token == ',':
                 stacktop.expansion = data.Expansion()
                 stacktop.function.append(stacktop.expansion)
 
                 if len(stacktop.function) == stacktop.function.maxargs:
-                    tokenlist = TokenList.get((stacktop.closebrace, '$'))
+                    tokenlist = TokenList.get((stacktop.openbrace, stacktop.closebrace, '$'))
                     stacktop.tokenlist = tokenlist
             elif token in (')', '}'):
                     stacktop.function.setup()
                     stack.pop()
                     stack[-1].expansion.append(stacktop.function)
             else:
                 assert False, "Not reached, PARSESTATE_FUNCTION"
         elif stacktop.parsestate == PARSESTATE_VARNAME:
             if token == ':':
                 stacktop.varname = stacktop.expansion
                 stacktop.parsestate = PARSESTATE_SUBSTFROM
                 stacktop.expansion = data.Expansion()
-                stacktop.tokenlist = TokenList.get(('=', stacktop.closebrace, '$'))
+                stacktop.tokenlist = TokenList.get(('=', stacktop.openbrace, stacktop.closebrace, '$'))
             elif token in (')', '}'):
                 stack.pop()
                 stack[-1].expansion.append(functions.VariableRef(stacktop.loc, stacktop.expansion))
             else:
                 assert False, "Not reached, PARSESTATE_VARNAME"
         elif stacktop.parsestate == PARSESTATE_SUBSTFROM:
             if token == '=':
                 stacktop.substfrom = stacktop.expansion
                 stacktop.parsestate = PARSESTATE_SUBSTTO
                 stacktop.expansion = data.Expansion()
-                stacktop.tokenlist = TokenList.get((stacktop.closebrace, '$'))
+                stacktop.tokenlist = TokenList.get((stacktop.openbrace, stacktop.closebrace, '$'))
             elif token in (')', '}'):
                 # A substitution of the form $(VARNAME:.ee) is probably a mistake, but make
                 # parses it. Issue a warning. Combine the varname and substfrom expansions to
                 # make the compatible varname. See tests/var-substitutions.mk SIMPLE3SUBSTNAME
                 log.warning("%s: Variable reference looks like substitution without =" % (stacktop.loc, ))
                 stacktop.varname.append(':')
                 stacktop.varname.concat(stacktop.expansion)
                 stack.pop()