Move comment and line-continuation parsing into the syntax parser. This is needed for the next patch which implements commands on the same line as a rule (delimited with ;)... in that case, the line continuations are command-style continuations, and not makefile syntax continuations.
authorBenjamin Smedberg <benjamin@smedbergs.us>
Thu, 05 Feb 2009 22:25:55 -0500
changeset 46 2b81cff1aa0a0670f0a890b1e0b4b8e330571843
parent 45 996c5791e40cfff1b9956a1fce651788d3437946
child 47 23711d02d7b002a6058095530390f6d8070ce3e7
push id27
push userbsmedberg@mozilla.com
push dateFri, 06 Feb 2009 03:38:33 +0000
Move comment and line-continuation parsing into the syntax parser. This is needed for the next patch which implements commands on the same line as a rule (delimited with ;)... in that case, the line continuations are command-style continuations, and not makefile syntax continuations.
pymake/parser.py
tests/comment-parsing.mk
tests/eof-continuation.mk
tests/escape-chars.mk
tests/parsertests.py
--- a/pymake/parser.py
+++ b/pymake/parser.py
@@ -56,55 +56,16 @@ class Location(object):
         newcol = reduce(charlocation, data, self.column)
         if newcol == self.column:
             return self
         return Location(self.path, self.line, newcol)
 
     def __str__(self):
         return "%s:%s:%s" % (self.path, self.line, self.column)
 
-def findcommenthash(line):
-    """
-    Search a line for the location of a comment hash. Returns -1 if there is
-    no comment.
-    """
-    i = 0
-    limit = len(line)
-    while i < limit:
-        if line[i] == '#':
-            return i
-        if line[i] == '\\':
-            i += 1
-        i += 1
-    return -1
-
-def iscontinuation(line):
-    """
-    A line continues only when the last *unmatched* backslash is before the
-    newline... this isn't documented, though.
-    """
-    i = 0
-    limit = len(line)
-    while i < limit:
-        if line[i] == '\\':
-            i += 1
-            if i != limit and line[i] == '\n':
-                return True
-        i += 1
-
-    return False
-
-def lstripcount(line):
-    """
-    Do an lstrip, but keep track how many columns were stripped for location-
-    tracking purposes. Returns (stripped, column)
-    """
-    r = line.lstrip()
-    return (r, reduce(charlocation, line[:len(line) - len(r)], 0))
-
 def findlast(func, iterable):
     f = None
     for i in iterable:
         if func(i):
             f = i
         else:
             return f
 
@@ -118,46 +79,48 @@ def charlocation(start, char):
     if char != '\t':
         return start + 1
 
     return start + tabwidth - start % tabwidth
 
 class Data(object):
     """
     A single virtual "line", which can be multiple source lines joined with
-    continuations.
+    continuations. This object is short-lived and should not escape the parser.
     """
 
-    __slots__ = ('data', '_locs')
-
-    def __init__(self):
+    def __init__(self, lineiter, path):
         self.data = ""
+        self.lineiter = lineiter
+        self.path = path
 
         # _locs is a list of tuples
         # (dataoffset, location)
         self._locs = []
 
     def __len__(self):
         return len(self.data)
 
+    def readline(self):
+        try:
+            lineno, line = self.lineiter.next()
+            self.append(line, Location(self.path, lineno, 0))
+        except StopIteration:
+            pass
+
     def __getitem__(self, key):
         try:
             return self.data[key]
         except IndexError:
             return None
 
     def append(self, data, loc):
         self._locs.append( (len(self.data), loc) )
         self.data += data
 
-    def stripcomment(self):
-        cloc = findcommenthash(self.data)
-        if cloc != -1:
-            self.data = self.data[:cloc]
-
     def getloc(self, offset):
         """
         Get the location of an offset within data.
         """
         if offset >= len(self.data):
             raise IndexError("Invalid offset", offset)
 
         begin, loc = findlast(lambda (o, l): o <= offset, self._locs)
@@ -167,33 +130,30 @@ def _iterlines(fd):
     """Yield (lineno, line) for each line in fd"""
 
     lineno = 0
     for line in fd:
         lineno += 1
 
         if line.endswith('\r\n'):
             line = line[:-2] + '\n'
+
         yield (lineno, line)
 
 def skipwhitespace(d, offset):
     """
     Return the offset into data after skipping whitespace.
     """
-    while d[offset].isspace():
+    while True:
+        c = d[offset]
+        if c is None or not c.isspace():
+            break
         offset += 1
     return offset
 
-def parsetoend(d, offset, skipws):
-    if skipws:
-        offset = skipwhitespace(d, offset)
-    value, offset = parsemakesyntax(d, offset, '')
-    assert offset == -1
-    return value
-
 def setvariable(variables, vname, recursive, value, fromcl=False):
     """
     Parse the remaining data at d[offset] into a variables object.
 
     @param vname an string holding the variable name
     """
     if len(vname) == 0:
         raise SyntaxError("Empty variable name", loc=d.getloc(offset))
@@ -224,19 +184,19 @@ def parsecommandlineargs(makefile, args)
         eqpos = a.find('=')
         if eqpos != -1:
             if a[eqpos-1] == ':':
                 vname = a[:eqpos-1]
             else:
                 vname = a[:eqpos]
             vname = vname.strip()
             valtext = a[eqpos+1:].lstrip()
-            d = Data()
+            d = Data(None, None)
             d.append(valtext, Location('<command-line>', 1, eqpos + 1))
-            value, offset = parsemakesyntax(d, 0, '')
+            value, offset = parsemakesyntax(d, 0, '', iscommand=False)
             assert offset == -1
             setvariable(makefile.variables, vname, a[eqpos-1] != ':', value, True)
         else:
             r.append(a)
 
     return r
 
 def parsestream(fd, filename, makefile):
@@ -246,73 +206,54 @@ def parsestream(fd, filename, makefile):
     @param fd A file-like object containing the makefile data.
     """
 
     currule = None
 
     fdlines = _iterlines(fd)
 
     for lineno, line in fdlines:
+        d = Data(fdlines, filename)
         if line.startswith('\t') and currule is not None:
-            d = Data()
-            isc = iscontinuation(line)
-            if not isc:
-                line = line.rstrip('\n')
             d.append(line[1:], Location(filename, lineno, tabwidth))
-            while isc:
-                lineno, line = fdlines.next()
-                startcol = 0
-                if line.startswith('\t'):
-                    startcol = tabwidth
-                    line = line[1:]
-                isc = iscontinuation(line)
-                if not isc:
-                    line = line.rstrip('\n')
-                d.append(line, Location(filename, lineno, startcol))
-            currule.addcommand(parsetoend(d, 0, False))
+            e, stoppedat = parsemakesyntax(d, 0, '', iscommand=True)
+            assert stoppedat == -1
+            currule.addcommand(e)
         else:
             # To parse Makefile syntax, we first strip leading whitespace and
-            # join continued lines, then look for initial keywords. If there
-            # are no keywords, it's either setting a variable or writing a
-            # rule.
+            # look for initial keywords. If there are no keywords, it's either
+            # setting a variable or writing a rule.
 
-            d = Data()
-
-            while True:
-                line, colno = lstripcount(line)
-                continues = iscontinuation(line)
+            d = Data(fdlines, filename)
+            d.append(line, Location(filename, lineno, 0))
 
-                if continues:
-                    line = line[:-2].rstrip() + ' '
-                else:
-                    line = line[:-1] # just strip the newline
-                d.append(line, Location(filename, lineno, colno))
+            offset = skipwhitespace(d, 0)
 
-                if not continues:
-                    break
+            # TODO: look for keywords
 
-                lineno, line = fdlines.next()
-
-            d.stripcomment()
-
-            e, stoppedat = parsemakesyntax(d, 0, ':=')
+            e, stoppedat = parsemakesyntax(d, 0, ':=', iscommand=False)
             if stoppedat == -1:
                 v = e.resolve(makefile.variables, None)
                 if v.strip() != '':
                     raise SyntaxError("Bad syntax: non-empty line is not a variable assignment or rule.", loc=d.getloc(0))
                 continue
 
             # if we encountered real makefile syntax, the current rule is over
             currule = None
 
             if d[stoppedat] == '=' or d[stoppedat:stoppedat+2] == ':=':
+                isrecursive = d[stoppedat] == '='
+
+                e.lstrip()
                 e.rstrip()
                 vname = e.resolve(makefile.variables, None)
-                value = parsetoend(d, stoppedat + (d[stoppedat] == '=' and 1 or 2), True)
-                setvariable(makefile.variables, vname, d[stoppedat] == '=', value)
+                value, stoppedat = parsemakesyntax(d, stoppedat + (isrecursive and 1 or 2), '', iscommand=False)
+                assert stoppedat == -1
+                value.lstrip()
+                setvariable(makefile.variables, vname, isrecursive, value)
             else:
                 assert d[stoppedat] == ':'
 
                 if d[stoppedat+1] == ':':
                     doublecolon = True
                     stoppedat += 1
                 else:
                     doublecolon = False
@@ -330,38 +271,42 @@ def parsestream(fd, filename, makefile):
                     raise SyntaxError("No targets in rule", g.getloc(0))
 
                 ispatterns = set((t.ispattern() for t in targets))
                 if len(ispatterns) == 2:
                     raise SyntaxError("Mixed implicit and normal rule", d.getloc(0))
                 ispattern, = ispatterns
 
                 stoppedat += 1
-                e, stoppedat = parsemakesyntax(d, stoppedat, ':=|;')
+                e, stoppedat = parsemakesyntax(d, stoppedat, ':=|;', iscommand=False)
                 if stoppedat == -1:
                     prereqs = data.splitwords(e.resolve(makefile.variables, None))
                     if ispattern:
                         currule = data.PatternRule(targets, map(data.Pattern, prereqs), doublecolon)
                         makefile.appendimplicitrule(currule)
                     else:
                         currule = data.Rule(prereqs, doublecolon)
                         for t in targets:
                             makefile.gettarget(t.gettarget()).addrule(currule)
                         makefile.foundtarget(targets[0].gettarget())
                 elif d[stoppedat] == '=' or d[stoppedat:stoppedat+2] == ':=':
+                    isrecursive = d[stoppedat] == '='
                     e.lstrip()
                     e.rstrip()
                     vname = e.resolve(makefile.variables, None)
-                    value = parsetoend(d, stoppedat + (d[stoppedat] == '=' and 1 or 2), True)
+                    value, stoppedat = parsemakesyntax(d, stoppedat + (isrecursive and 1 or 2), '', iscommand=False)
+                    assert stoppedat == -1
+                    value.lstrip()
+
                     if ispattern:
                         for target in targets:
-                            setvariable(makefile.getpatternvariables(target), vname, d[stoppedat] == '=', value)
+                            setvariable(makefile.getpatternvariables(target), vname, isrecursive, value)
                     else:
                         for target in targets:
-                            setvariable(makefile.gettarget(target.gettarget()).variables, vname, d[stoppedat] == '=', value)
+                            setvariable(makefile.gettarget(target.gettarget()).variables, vname, isrecursive, value)
                 elif d[stoppedat] == '|':
                     raise NotImplementedError('order-only prerequisites not implemented')
                 elif d[stoppedat] == ';':
                     raise NotImplementedError('rule after command not implemented')
                 else:
                     assert d[stoppedat] == ':'
 
                     # static pattern rule
@@ -370,17 +315,17 @@ def parsestream(fd, filename, makefile):
 
                     patstr = e.resolve(makefile.variables, None)
                     patterns = data.splitwords(patstr)
                     if len(patterns) != 1:
                         raise SyntaxError("A static pattern rule may have only one pattern", d.getloc(stoppedat))
 
                     pattern = data.Pattern(patterns[0])
 
-                    e, stoppedat = parsemakesyntax(d, stoppedat, ';')
+                    e, stoppedat = parsemakesyntax(d, stoppedat, ';', iscommand=False)
                     prereqs = map(data.Pattern, data.splitwords(e.resolve(makefile.variables, None)))
                     currule = data.PatternRule([pattern], prereqs, doublecolon)
                     for t in targets:
                         makefile.gettarget(t.gettarget()).addrule(currule)
 
                     makefile.foundtarget(targets[0].gettarget())
 
                     if stoppedat != -1:
@@ -391,77 +336,138 @@ PARSESTATE_TOPLEVEL = 0    # at the top 
 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
 
 class ParseStackFrame(object):
-    def __init__(self, parsestate, expansion, stopat, **kwargs):
+    def __init__(self, parsestate, expansion, stopon, **kwargs):
         self.parsestate = parsestate
         self.expansion = expansion
-        self.stopat = stopat
+        self.stopon = stopon
         for key, value in kwargs.iteritems():
             setattr(self, key, value)
 
-def parsemakesyntax(d, startat, stopat):
+def parsemakesyntax(d, startat, stopon, iscommand):
     """
     Given Data, parse it into a data.Expansion.
 
-    @param stopat (sequence)
+    @param stopon (sequence)
         Indicate characters where toplevel parsing should stop.
  
     @return a tuple (expansion, stopoffset). If all the data is consumed, stopoffset will be -1
     """
 
+    # print "parsemakesyntax iscommand=%s" % iscommand
+
     stack = [
-        ParseStackFrame(PARSESTATE_TOPLEVEL, data.Expansion(), stopat)
+        ParseStackFrame(PARSESTATE_TOPLEVEL, data.Expansion(), stopon)
     ]
 
-    i = startat - 1
-    limit = len(d)
-    while True:
-        i += 1
-        if i >= limit:
-            break
-
+    i = startat
+    while i < len(d):
         stacktop = stack[-1]
         c = d[i]
-        if c == '$':
+
+        # print "i=%i c=%c parsestate=%i len(d)=%i" % (i, c, stacktop.parsestate, len(d))
+
+        if c == '#' and not iscommand:
+            # we need to keep reading lines until there are no more continuations
+            while i < len(d):
+                if d[i] == '\\':
+                    if d[i+1] == '\\':
+                        i += 2
+                        continue
+                    elif d[i+1] == '\n':
+                        i += 2
+                        assert i == len(d)
+                        d.readline()
+                elif d[i] == '\n':
+                    i += 1
+                    assert i == len(d)
+                    break
+                i += 1
+            break
+        elif c == '\\':
+            # in makefile syntax, backslashes can escape # specially, but nothing else. Fun, huh?
+            if d[i+1] is None:
+                stacktop.expansion.append('\\')
+                i += 1
+                break
+            elif d[i+1] == '#' and not iscommand:
+                stacktop.expansion.append('#')
+                i += 2
+                continue
+            elif d[i+1:i+3] == '\\#':
+                # This is an edge case that I discovered. It's undocumented, and
+                # totally absolutely weird. See escape-chars.mk VARAWFUL
+                stacktop.expansion.append('\\')
+                i += 2
+                continue
+            elif d[i+1] == '\\' and not iscommand:
+                stacktop.expansion.append('\\\\')
+                i += 2
+                continue
+            elif d[i+1] == '\n':
+                i += 2
+                assert i == len(d), "newline isn't last character?"
+
+                d.readline()
+
+                if iscommand:
+                    stacktop.expansion.append('\\\n')
+                    if d[i] == '\t':
+                        i += 1
+                else:
+                    stacktop.expansion.rstrip()
+                    stacktop.expansion.append(' ')
+                    i = skipwhitespace(d, i)
+                continue
+            else:
+                stacktop.expansion.append(c)
+                i += 1
+                continue
+        elif c == '\n':
+            i += 1
+            assert i == len(d), "newline isn't last character?"
+            break
+        elif c == '$':
             loc = d.getloc(i)
             i += 1
             c = d[i]
 
             if c == '$':
                 stacktop.expansion.append('$')
             elif c == '(':
                 # look forward for a function name
                 j = i + 1
-                while d[j] >= 'a' and d[j] <= 'z':
+                while d[j] == '-' or (d[j] >= 'a' and d[j] <= 'z'):
                     j += 1
                 fname = d[i + 1:j]
-                if d[j].isspace() and fname in functions.functionmap:
+                if d[j] is not None and d[j].isspace() and fname in functions.functionmap:
                     fn = functions.functionmap[fname](loc)
                     stack.append(ParseStackFrame(PARSESTATE_FUNCTION,
                                                  data.Expansion(), ',)',
                                                  function=fn))
                     # skip whitespace before the first argument
                     i = j
-                    while d[i+1].isspace():
-                        i += 1
+                    i = skipwhitespace(d, i + 1)
+                    continue
                 else:
                     e = data.Expansion()
                     stack.append(ParseStackFrame(PARSESTATE_VARNAME, e, ':)', loc=loc))
             else:
                 fe = data.Expansion()
                 fe.append(d[i])
                 stacktop.expansion.append(functions.VariableRef(loc, fe))
                 i += 1
-        elif c in stacktop.stopat:
+                continue
+        elif c in stacktop.stopon:
             if stacktop.parsestate == PARSESTATE_TOPLEVEL:
                 break
 
             if stacktop.parsestate == PARSESTATE_FUNCTION:
                 if c == ',':
                     stacktop.function.append(stacktop.expansion)
                     stacktop.expansion = data.Expansion()
                 elif c == ')':
@@ -471,28 +477,28 @@ def parsemakesyntax(d, startat, stopat):
                     stack[-1].expansion.append(stacktop.function)
                 else:
                     assert False, "Not reached, PARSESTATE_FUNCTION"
             elif stacktop.parsestate == PARSESTATE_VARNAME:
                 if c == ':':
                     stacktop.varname = stacktop.expansion
                     stacktop.parsestate = PARSESTATE_SUBSTFROM
                     stacktop.expansion = data.Expansion()
-                    stacktop.stopat = '=)'
+                    stacktop.stopon = '=)'
                 elif c == ')':
                     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 c == '=':
                     stacktop.substfrom = stacktop.expansion
                     stacktop.parsestate = PARSESTATE_SUBSTTO
                     stacktop.expansion = data.Expansion()
-                    stacktop.stopat = ')'
+                    stacktop.stopon = ')'
                 elif c == ')':
                     # 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()
@@ -504,16 +510,20 @@ def parsemakesyntax(d, startat, stopat):
 
                 stack.pop()
                 stack[-1].expansion.append(functions.SubstitutionRef(stacktop.loc, stacktop.varname,
                                                                      stacktop.substfrom, stacktop.expansion))
             else:
                 assert False, "Unexpected parse state %s" % stacktop.parsestate
         else:
             stacktop.expansion.append(c)
+        i += 1
+
     if len(stack) != 1:
         raise SyntaxError("Unterminated function call", d.getloc(len(d) - 1))
 
     assert stack[0].parsestate == PARSESTATE_TOPLEVEL
 
-    if i >= limit:
+    assert i <= len(d), 'overwrote the end: i=%i len(d)=%i' % (i, len(d))
+
+    if i == len(d):
         i = -1
     return stack[0].expansion, i
--- a/tests/comment-parsing.mk
+++ b/tests/comment-parsing.mk
@@ -1,13 +1,17 @@
 # where do comments take effect?
 
 VAR = val1 # comment
 VAR2 = literal\#hash
 VAR3 = val3
+VAR4 = literal\\#backslash
+VAR5 = literal\char
 # This comment extends to the next line \
 VAR3 = ignored
 
 all:
 	test "$(VAR)" = "val1 "
 	test "$(VAR2)" = "literal#hash"
 	test "$(VAR3)" = "val3"
+	test '$(VAR4)' = 'literal\'
+	test '$(VAR5)' = 'literal\char'
 	@echo "TEST-PASS"
--- a/tests/eof-continuation.mk
+++ b/tests/eof-continuation.mk
@@ -1,6 +1,5 @@
-#T returncode: 2
-
 all:
-	test "$(TESTVAR)" = "testval"
+	test '$(TESTVAR)' = 'testval\'
+	@echo TEST-PASS
 
 TESTVAR = testval\
\ No newline at end of file
--- a/tests/escape-chars.mk
+++ b/tests/escape-chars.mk
@@ -1,14 +1,20 @@
 space = $(NULL) $(NULL)
 hello$(space)world$(space) = hellovalue
 
+A = aval
+
 VAR = value1\\
+VARAWFUL = value1\\#comment
 VAR2 = value2
+VAR3 = test\$A
 
 EPERCENT = \%
 
 all:
 	test "$(hello world )" = "hellovalue"
 	test "$(VAR)" = "value1\\"
+	test '$(VARAWFUL)' = 'value1\'
 	test "$(VAR2)" = "value2"
+	test "$(VAR3)" = "test\aval"
 	test "$(EPERCENT)" = "\%"
 	@echo TEST-PASS
--- a/tests/parsertests.py
+++ b/tests/parsertests.py
@@ -4,67 +4,27 @@ import logging
 
 from cStringIO import StringIO
 
 class TestBase(unittest.TestCase):
     def assertEqual(self, a, b, msg=""):
         """Actually print the values which weren't equal, if things don't work out!"""
         unittest.TestCase.assertEqual(self, a, b, "%s got %r expected %r" % (msg, a, b))
 
-class FindCommentTest(TestBase):
-    testdata = (
-        ("Hello # Comment", 6),
-        ("# Line comment", 0),
-        ("No comment", -1),
-    )
-
-    def runTest(self):
-        for line, expected in self.testdata:
-            self.assertEqual(pymake.parser.findcommenthash(line), expected,
-                             "findcommenthash: %r" % (line,) )
-
-class IsContinuationTest(TestBase):
-    testdata = (
-        ("Hello", False),
-        ("Hello \\", False),
-        ("Hello \\\n", True),
-        ("Hello \\\\", False),
-        ("Hello \\\\\n", False),
-    )
-
-    def runTest(self):
-        for line, expected in self.testdata:
-            self.assertEqual(pymake.parser.iscontinuation(line), expected,
-                             "iscontinuation: %r" % (line,) )
-
-class LStripCountTest(TestBase):
-    testdata = (
-        ("Hello", 0, "Hello"),
-        ("  Hello", 2, "Hello"),
-        ("\tHello", 4, "Hello"),
-        ("\t  Hello  ", 6, "Hello  "),
-    )
-
-    def runTest(self):
-        for line, col, result in self.testdata:
-            aresult, acol = pymake.parser.lstripcount(line)
-            self.assertEqual(acol, col, "lstripcount column: %r" % (line,))
-            self.assertEqual(aresult, result, "lstripcount result: %r" % (line,))
-
 class DataTest(TestBase):
     testdata = (
         ((("He\tllo", "f", 1, 0),),
          ((0, "f", 1, 0), (2, "f", 1, 2), (3, "f", 1, 4))),
         ((("line1 ", "f", 1, 4), ("l\tine2", "f", 2, 11)),
          ((0, "f", 1, 4), (5, "f", 1, 9), (6, "f", 2, 11), (7, "f", 2, 12), (8, "f", 2, 16))),
     )
 
     def runTest(self):
         for datas, results in self.testdata:
-            d = pymake.parser.Data()
+            d = pymake.parser.Data(None, None)
             for line, file, lineno, col in datas:
                 d.append(line, pymake.parser.Location(file, lineno, col))
             for pos, file, lineno, col in results:
                 loc = d.getloc(pos)
                 self.assertEqual(loc.path, file, "data file")
                 self.assertEqual(loc.line, lineno, "data line")
                 self.assertEqual(loc.column, col, "data %r col, got %i expected %i" % (d.data, loc.column, col))
 
@@ -133,20 +93,20 @@ class MakeSyntaxTest(TestBase):
                         item = k[1:]
                         proppath = ipath + [item]
                         self.compareRecursive(getattr(a, item), v, proppath)
                     else:
                         raise Exception("Unexpected property at %s: %s" % (ipath, k))
 
     def runTest(self):
         for s, startat, stopat, stopoffset, expansion in self.testdata:
-            d = pymake.parser.Data()
+            d = pymake.parser.Data(None, None)
             d.append(s, pymake.parser.Location('testdata', 1, 0))
 
-            a, stoppedat = pymake.parser.parsemakesyntax(d, startat, stopat)
+            a, stoppedat = pymake.parser.parsemakesyntax(d, startat, stopat, iscommand=False)
             self.compareRecursive(a, expansion, [])
             self.assertEqual(stoppedat, stopoffset)
 
 class VariableTest(TestBase):
     testdata = """
     VAR = value
     VARNAME = TESTVAR
     $(VARNAME) = testvalue