PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/build/pymake/pymake/parser.py

https://bitbucket.org/ehsan/broken-inbound
Python | 772 lines | 755 code | 0 blank | 17 comment | 0 complexity | 5ccf5ec5cb112cb20eb925f5cf974629 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-3.0, AGPL-1.0, MIT, LGPL-2.1, 0BSD, BSD-2-Clause, MPL-2.0, BSD-3-Clause, Apache-2.0, GPL-2.0, JSON
  1. """
  2. Module for parsing Makefile syntax.
  3. Makefiles use a line-based parsing system. Continuations and substitutions are handled differently based on the
  4. type of line being parsed:
  5. Lines with makefile syntax condense continuations to a single space, no matter the actual trailing whitespace
  6. of the first line or the leading whitespace of the continuation. In other situations, trailing whitespace is
  7. relevant.
  8. Lines with command syntax do not condense continuations: the backslash and newline are part of the command.
  9. (GNU Make is buggy in this regard, at least on mac).
  10. Lines with an initial tab are commands if they can be (there is a rule or a command immediately preceding).
  11. Otherwise, they are parsed as makefile syntax.
  12. This file parses into the data structures defined in the parserdata module. Those classes are what actually
  13. do the dirty work of "executing" the parsed data into a data.Makefile.
  14. Four iterator functions are available:
  15. * iterdata
  16. * itermakefilechars
  17. * itercommandchars
  18. The iterators handle line continuations and comments in different ways, but share a common calling
  19. convention:
  20. Called with (data, startoffset, tokenlist, finditer)
  21. yield 4-tuples (flatstr, token, tokenoffset, afteroffset)
  22. flatstr is data, guaranteed to have no tokens (may be '')
  23. token, tokenoffset, afteroffset *may be None*. That means there is more text
  24. coming.
  25. """
  26. import logging, re, os, sys
  27. import data, functions, util, parserdata
  28. _log = logging.getLogger('pymake.parser')
  29. class SyntaxError(util.MakeError):
  30. pass
  31. _skipws = re.compile('\S')
  32. class Data(object):
  33. """
  34. A single virtual "line", which can be multiple source lines joined with
  35. continuations.
  36. """
  37. __slots__ = ('s', 'lstart', 'lend', 'loc')
  38. def __init__(self, s, lstart, lend, loc):
  39. self.s = s
  40. self.lstart = lstart
  41. self.lend = lend
  42. self.loc = loc
  43. @staticmethod
  44. def fromstring(s, path):
  45. return Data(s, 0, len(s), parserdata.Location(path, 1, 0))
  46. def getloc(self, offset):
  47. assert offset >= self.lstart and offset <= self.lend
  48. return self.loc.offset(self.s, self.lstart, offset)
  49. def skipwhitespace(self, offset):
  50. """
  51. Return the offset of the first non-whitespace character in data starting at offset, or None if there are
  52. only whitespace characters remaining.
  53. """
  54. m = _skipws.search(self.s, offset, self.lend)
  55. if m is None:
  56. return self.lend
  57. return m.start(0)
  58. _linere = re.compile(r'\\*\n')
  59. def enumeratelines(s, filename):
  60. """
  61. Enumerate lines in a string as Data objects, joining line
  62. continuations.
  63. """
  64. off = 0
  65. lineno = 1
  66. curlines = 0
  67. for m in _linere.finditer(s):
  68. curlines += 1
  69. start, end = m.span(0)
  70. if (start - end) % 2 == 0:
  71. # odd number of backslashes is a continuation
  72. continue
  73. yield Data(s, off, end - 1, parserdata.Location(filename, lineno, 0))
  74. lineno += curlines
  75. curlines = 0
  76. off = end
  77. yield Data(s, off, len(s), parserdata.Location(filename, lineno, 0))
  78. _alltokens = re.compile(r'''\\*\# | # hash mark preceeded by any number of backslashes
  79. := |
  80. \+= |
  81. \?= |
  82. :: |
  83. (?:\$(?:$|[\(\{](?:%s)\s+|.)) | # dollar sign followed by EOF, a function keyword with whitespace, or any character
  84. :(?![\\/]) | # colon followed by anything except a slash (Windows path detection)
  85. [=#{}();,|'"]''' % '|'.join(functions.functionmap.iterkeys()), re.VERBOSE)
  86. def iterdata(d, offset, tokenlist, it):
  87. """
  88. Iterate over flat data without line continuations, comments, or any special escaped characters.
  89. Typically used to parse recursively-expanded variables.
  90. """
  91. assert len(tokenlist), "Empty tokenlist passed to iterdata is meaningless!"
  92. assert offset >= d.lstart and offset <= d.lend, "offset %i should be between %i and %i" % (offset, d.lstart, d.lend)
  93. if offset == d.lend:
  94. return
  95. s = d.s
  96. for m in it:
  97. mstart, mend = m.span(0)
  98. token = s[mstart:mend]
  99. if token in tokenlist or (token[0] == '$' and '$' in tokenlist):
  100. yield s[offset:mstart], token, mstart, mend
  101. else:
  102. yield s[offset:mend], None, None, mend
  103. offset = mend
  104. yield s[offset:d.lend], None, None, None
  105. # multiple backslashes before a newline are unescaped, halving their total number
  106. _makecontinuations = re.compile(r'(?:\s*|((?:\\\\)+))\\\n\s*')
  107. def _replacemakecontinuations(m):
  108. start, end = m.span(1)
  109. if start == -1:
  110. return ' '
  111. return ' '.rjust((end - start) / 2 + 1, '\\')
  112. def itermakefilechars(d, offset, tokenlist, it, ignorecomments=False):
  113. """
  114. Iterate over data in makefile syntax. Comments are found at unescaped # characters, and escaped newlines
  115. are converted to single-space continuations.
  116. """
  117. assert offset >= d.lstart and offset <= d.lend, "offset %i should be between %i and %i" % (offset, d.lstart, d.lend)
  118. if offset == d.lend:
  119. return
  120. s = d.s
  121. for m in it:
  122. mstart, mend = m.span(0)
  123. token = s[mstart:mend]
  124. starttext = _makecontinuations.sub(_replacemakecontinuations, s[offset:mstart])
  125. if token[-1] == '#' and not ignorecomments:
  126. l = mend - mstart
  127. # multiple backslashes before a hash are unescaped, halving their total number
  128. if l % 2:
  129. # found a comment
  130. yield starttext + token[:(l - 1) / 2], None, None, None
  131. return
  132. else:
  133. yield starttext + token[-l / 2:], None, None, mend
  134. elif token in tokenlist or (token[0] == '$' and '$' in tokenlist):
  135. yield starttext, token, mstart, mend
  136. else:
  137. yield starttext + token, None, None, mend
  138. offset = mend
  139. yield _makecontinuations.sub(_replacemakecontinuations, s[offset:d.lend]), None, None, None
  140. _findcomment = re.compile(r'\\*\#')
  141. def flattenmakesyntax(d, offset):
  142. """
  143. A shortcut method for flattening line continuations and comments in makefile syntax without
  144. looking for other tokens.
  145. """
  146. assert offset >= d.lstart and offset <= d.lend, "offset %i should be between %i and %i" % (offset, d.lstart, d.lend)
  147. if offset == d.lend:
  148. return ''
  149. s = _makecontinuations.sub(_replacemakecontinuations, d.s[offset:d.lend])
  150. elements = []
  151. offset = 0
  152. for m in _findcomment.finditer(s):
  153. mstart, mend = m.span(0)
  154. elements.append(s[offset:mstart])
  155. if (mend - mstart) % 2:
  156. # even number of backslashes... it's a comment
  157. elements.append(''.ljust((mend - mstart - 1) / 2, '\\'))
  158. return ''.join(elements)
  159. # odd number of backslashes
  160. elements.append(''.ljust((mend - mstart - 2) / 2, '\\') + '#')
  161. offset = mend
  162. elements.append(s[offset:])
  163. return ''.join(elements)
  164. def itercommandchars(d, offset, tokenlist, it):
  165. """
  166. Iterate over command syntax. # comment markers are not special, and escaped newlines are included
  167. in the output text.
  168. """
  169. assert offset >= d.lstart and offset <= d.lend, "offset %i should be between %i and %i" % (offset, d.lstart, d.lend)
  170. if offset == d.lend:
  171. return
  172. s = d.s
  173. for m in it:
  174. mstart, mend = m.span(0)
  175. token = s[mstart:mend]
  176. starttext = s[offset:mstart].replace('\n\t', '\n')
  177. if token in tokenlist or (token[0] == '$' and '$' in tokenlist):
  178. yield starttext, token, mstart, mend
  179. else:
  180. yield starttext + token, None, None, mend
  181. offset = mend
  182. yield s[offset:d.lend].replace('\n\t', '\n'), None, None, None
  183. _redefines = re.compile('\s*define|\s*endef')
  184. def iterdefinelines(it, startloc):
  185. """
  186. Process the insides of a define. Most characters are included literally. Escaped newlines are treated
  187. as they would be in makefile syntax. Internal define/endef pairs are ignored.
  188. """
  189. results = []
  190. definecount = 1
  191. for d in it:
  192. m = _redefines.match(d.s, d.lstart, d.lend)
  193. if m is not None:
  194. directive = m.group(0).strip()
  195. if directive == 'endef':
  196. definecount -= 1
  197. if definecount == 0:
  198. return _makecontinuations.sub(_replacemakecontinuations, '\n'.join(results))
  199. else:
  200. definecount += 1
  201. results.append(d.s[d.lstart:d.lend])
  202. # Falling off the end is an unterminated define!
  203. raise SyntaxError("define without matching endef", startloc)
  204. def _ensureend(d, offset, msg):
  205. """
  206. Ensure that only whitespace remains in this data.
  207. """
  208. s = flattenmakesyntax(d, offset)
  209. if s != '' and not s.isspace():
  210. raise SyntaxError(msg, d.getloc(offset))
  211. _eqargstokenlist = ('(', "'", '"')
  212. def ifeq(d, offset):
  213. if offset > d.lend - 1:
  214. raise SyntaxError("No arguments after conditional", d.getloc(offset))
  215. # the variety of formats for this directive is rather maddening
  216. token = d.s[offset]
  217. if token not in _eqargstokenlist:
  218. raise SyntaxError("No arguments after conditional", d.getloc(offset))
  219. offset += 1
  220. if token == '(':
  221. arg1, t, offset = parsemakesyntax(d, offset, (',',), itermakefilechars)
  222. if t is None:
  223. raise SyntaxError("Expected two arguments in conditional", d.getloc(d.lend))
  224. arg1.rstrip()
  225. offset = d.skipwhitespace(offset)
  226. arg2, t, offset = parsemakesyntax(d, offset, (')',), itermakefilechars)
  227. if t is None:
  228. raise SyntaxError("Unexpected text in conditional", d.getloc(offset))
  229. _ensureend(d, offset, "Unexpected text after conditional")
  230. else:
  231. arg1, t, offset = parsemakesyntax(d, offset, (token,), itermakefilechars)
  232. if t is None:
  233. raise SyntaxError("Unexpected text in conditional", d.getloc(d.lend))
  234. offset = d.skipwhitespace(offset)
  235. if offset == d.lend:
  236. raise SyntaxError("Expected two arguments in conditional", d.getloc(offset))
  237. token = d.s[offset]
  238. if token not in '\'"':
  239. raise SyntaxError("Unexpected text in conditional", d.getloc(offset))
  240. arg2, t, offset = parsemakesyntax(d, offset + 1, (token,), itermakefilechars)
  241. _ensureend(d, offset, "Unexpected text after conditional")
  242. return parserdata.EqCondition(arg1, arg2)
  243. def ifneq(d, offset):
  244. c = ifeq(d, offset)
  245. c.expected = False
  246. return c
  247. def ifdef(d, offset):
  248. e, t, offset = parsemakesyntax(d, offset, (), itermakefilechars)
  249. e.rstrip()
  250. return parserdata.IfdefCondition(e)
  251. def ifndef(d, offset):
  252. c = ifdef(d, offset)
  253. c.expected = False
  254. return c
  255. _conditionkeywords = {
  256. 'ifeq': ifeq,
  257. 'ifneq': ifneq,
  258. 'ifdef': ifdef,
  259. 'ifndef': ifndef
  260. }
  261. _conditiontokens = tuple(_conditionkeywords.iterkeys())
  262. _conditionre = re.compile(r'(%s)(?:$|\s+)' % '|'.join(_conditiontokens))
  263. _directivestokenlist = _conditiontokens + \
  264. ('else', 'endif', 'define', 'endef', 'override', 'include', '-include', 'includedeps', '-includedeps', 'vpath', 'export', 'unexport')
  265. _directivesre = re.compile(r'(%s)(?:$|\s+)' % '|'.join(_directivestokenlist))
  266. _varsettokens = (':=', '+=', '?=', '=')
  267. def _parsefile(pathname):
  268. fd = open(pathname, "rU")
  269. stmts = parsestring(fd.read(), pathname)
  270. stmts.mtime = os.fstat(fd.fileno()).st_mtime
  271. fd.close()
  272. return stmts
  273. def _checktime(path, stmts):
  274. mtime = os.path.getmtime(path)
  275. if mtime != stmts.mtime:
  276. _log.debug("Re-parsing makefile '%s': mtimes differ", path)
  277. return False
  278. return True
  279. _parsecache = util.MostUsedCache(15, _parsefile, _checktime)
  280. def parsefile(pathname):
  281. """
  282. Parse a filename into a parserdata.StatementList. A cache is used to avoid re-parsing
  283. makefiles that have already been parsed and have not changed.
  284. """
  285. pathname = os.path.realpath(pathname)
  286. return _parsecache.get(pathname)
  287. def parsestring(s, filename):
  288. """
  289. Parse a string containing makefile data into a parserdata.StatementList.
  290. """
  291. currule = False
  292. condstack = [parserdata.StatementList()]
  293. fdlines = enumeratelines(s, filename)
  294. for d in fdlines:
  295. assert len(condstack) > 0
  296. offset = d.lstart
  297. if currule and offset < d.lend and d.s[offset] == '\t':
  298. e, token, offset = parsemakesyntax(d, offset + 1, (), itercommandchars)
  299. assert token is None
  300. assert offset is None
  301. condstack[-1].append(parserdata.Command(e))
  302. continue
  303. # To parse Makefile syntax, we first strip leading whitespace and
  304. # look for initial keywords. If there are no keywords, it's either
  305. # setting a variable or writing a rule.
  306. offset = d.skipwhitespace(offset)
  307. if offset is None:
  308. continue
  309. m = _directivesre.match(d.s, offset, d.lend)
  310. if m is not None:
  311. kword = m.group(1)
  312. offset = m.end(0)
  313. if kword == 'endif':
  314. _ensureend(d, offset, "Unexpected data after 'endif' directive")
  315. if len(condstack) == 1:
  316. raise SyntaxError("unmatched 'endif' directive",
  317. d.getloc(offset))
  318. condstack.pop().endloc = d.getloc(offset)
  319. continue
  320. if kword == 'else':
  321. if len(condstack) == 1:
  322. raise SyntaxError("unmatched 'else' directive",
  323. d.getloc(offset))
  324. m = _conditionre.match(d.s, offset, d.lend)
  325. if m is None:
  326. _ensureend(d, offset, "Unexpected data after 'else' directive.")
  327. condstack[-1].addcondition(d.getloc(offset), parserdata.ElseCondition())
  328. else:
  329. kword = m.group(1)
  330. if kword not in _conditionkeywords:
  331. raise SyntaxError("Unexpected condition after 'else' directive.",
  332. d.getloc(offset))
  333. startoffset = offset
  334. offset = d.skipwhitespace(m.end(1))
  335. c = _conditionkeywords[kword](d, offset)
  336. condstack[-1].addcondition(d.getloc(startoffset), c)
  337. continue
  338. if kword in _conditionkeywords:
  339. c = _conditionkeywords[kword](d, offset)
  340. cb = parserdata.ConditionBlock(d.getloc(d.lstart), c)
  341. condstack[-1].append(cb)
  342. condstack.append(cb)
  343. continue
  344. if kword == 'endef':
  345. raise SyntaxError("endef without matching define", d.getloc(offset))
  346. if kword == 'define':
  347. currule = False
  348. vname, t, i = parsemakesyntax(d, offset, (), itermakefilechars)
  349. vname.rstrip()
  350. startloc = d.getloc(d.lstart)
  351. value = iterdefinelines(fdlines, startloc)
  352. condstack[-1].append(parserdata.SetVariable(vname, value=value, valueloc=startloc, token='=', targetexp=None))
  353. continue
  354. if kword in ('include', '-include', 'includedeps', '-includedeps'):
  355. if kword.startswith('-'):
  356. required = False
  357. kword = kword[1:]
  358. else:
  359. required = True
  360. deps = kword == 'includedeps'
  361. currule = False
  362. incfile, t, offset = parsemakesyntax(d, offset, (), itermakefilechars)
  363. condstack[-1].append(parserdata.Include(incfile, required, deps))
  364. continue
  365. if kword == 'vpath':
  366. currule = False
  367. e, t, offset = parsemakesyntax(d, offset, (), itermakefilechars)
  368. condstack[-1].append(parserdata.VPathDirective(e))
  369. continue
  370. if kword == 'override':
  371. currule = False
  372. vname, token, offset = parsemakesyntax(d, offset, _varsettokens, itermakefilechars)
  373. vname.lstrip()
  374. vname.rstrip()
  375. if token is None:
  376. raise SyntaxError("Malformed override directive, need =", d.getloc(d.lstart))
  377. value = flattenmakesyntax(d, offset).lstrip()
  378. condstack[-1].append(parserdata.SetVariable(vname, value=value, valueloc=d.getloc(offset), token=token, targetexp=None, source=data.Variables.SOURCE_OVERRIDE))
  379. continue
  380. if kword == 'export':
  381. currule = False
  382. e, token, offset = parsemakesyntax(d, offset, _varsettokens, itermakefilechars)
  383. e.lstrip()
  384. e.rstrip()
  385. if token is None:
  386. condstack[-1].append(parserdata.ExportDirective(e, concurrent_set=False))
  387. else:
  388. condstack[-1].append(parserdata.ExportDirective(e, concurrent_set=True))
  389. value = flattenmakesyntax(d, offset).lstrip()
  390. condstack[-1].append(parserdata.SetVariable(e, value=value, valueloc=d.getloc(offset), token=token, targetexp=None))
  391. continue
  392. if kword == 'unexport':
  393. e, token, offset = parsemakesyntax(d, offset, (), itermakefilechars)
  394. condstack[-1].append(parserdata.UnexportDirective(e))
  395. continue
  396. e, token, offset = parsemakesyntax(d, offset, _varsettokens + ('::', ':'), itermakefilechars)
  397. if token is None:
  398. e.rstrip()
  399. e.lstrip()
  400. if not e.isempty():
  401. condstack[-1].append(parserdata.EmptyDirective(e))
  402. continue
  403. # if we encountered real makefile syntax, the current rule is over
  404. currule = False
  405. if token in _varsettokens:
  406. e.lstrip()
  407. e.rstrip()
  408. value = flattenmakesyntax(d, offset).lstrip()
  409. condstack[-1].append(parserdata.SetVariable(e, value=value, valueloc=d.getloc(offset), token=token, targetexp=None))
  410. else:
  411. doublecolon = token == '::'
  412. # `e` is targets or target patterns, which can end up as
  413. # * a rule
  414. # * an implicit rule
  415. # * a static pattern rule
  416. # * a target-specific variable definition
  417. # * a pattern-specific variable definition
  418. # any of the rules may have order-only prerequisites
  419. # delimited by |, and a command delimited by ;
  420. targets = e
  421. e, token, offset = parsemakesyntax(d, offset,
  422. _varsettokens + (':', '|', ';'),
  423. itermakefilechars)
  424. if token in (None, ';'):
  425. condstack[-1].append(parserdata.Rule(targets, e, doublecolon))
  426. currule = True
  427. if token == ';':
  428. offset = d.skipwhitespace(offset)
  429. e, t, offset = parsemakesyntax(d, offset, (), itercommandchars)
  430. condstack[-1].append(parserdata.Command(e))
  431. elif token in _varsettokens:
  432. e.lstrip()
  433. e.rstrip()
  434. value = flattenmakesyntax(d, offset).lstrip()
  435. condstack[-1].append(parserdata.SetVariable(e, value=value, valueloc=d.getloc(offset), token=token, targetexp=targets))
  436. elif token == '|':
  437. raise SyntaxError('order-only prerequisites not implemented', d.getloc(offset))
  438. else:
  439. assert token == ':'
  440. # static pattern rule
  441. pattern = e
  442. deps, token, offset = parsemakesyntax(d, offset, (';',), itermakefilechars)
  443. condstack[-1].append(parserdata.StaticPatternRule(targets, pattern, deps, doublecolon))
  444. currule = True
  445. if token == ';':
  446. offset = d.skipwhitespace(offset)
  447. e, token, offset = parsemakesyntax(d, offset, (), itercommandchars)
  448. condstack[-1].append(parserdata.Command(e))
  449. if len(condstack) != 1:
  450. raise SyntaxError("Condition never terminated with endif", condstack[-1].loc)
  451. return condstack[0]
  452. _PARSESTATE_TOPLEVEL = 0 # at the top level
  453. _PARSESTATE_FUNCTION = 1 # expanding a function call
  454. _PARSESTATE_VARNAME = 2 # expanding a variable expansion.
  455. _PARSESTATE_SUBSTFROM = 3 # expanding a variable expansion substitution "from" value
  456. _PARSESTATE_SUBSTTO = 4 # expanding a variable expansion substitution "to" value
  457. _PARSESTATE_PARENMATCH = 5 # inside nested parentheses/braces that must be matched
  458. class ParseStackFrame(object):
  459. __slots__ = ('parsestate', 'parent', 'expansion', 'tokenlist', 'openbrace', 'closebrace', 'function', 'loc', 'varname', 'substfrom')
  460. def __init__(self, parsestate, parent, expansion, tokenlist, openbrace, closebrace, function=None, loc=None):
  461. self.parsestate = parsestate
  462. self.parent = parent
  463. self.expansion = expansion
  464. self.tokenlist = tokenlist
  465. self.openbrace = openbrace
  466. self.closebrace = closebrace
  467. self.function = function
  468. self.loc = loc
  469. def __str__(self):
  470. return "<state=%i expansion=%s tokenlist=%s openbrace=%s closebrace=%s>" % (self.parsestate, self.expansion, self.tokenlist, self.openbrace, self.closebrace)
  471. _matchingbrace = {
  472. '(': ')',
  473. '{': '}',
  474. }
  475. def parsemakesyntax(d, offset, stopon, iterfunc):
  476. """
  477. Given Data, parse it into a data.Expansion.
  478. @param stopon (sequence)
  479. Indicate characters where toplevel parsing should stop.
  480. @param iterfunc (generator function)
  481. A function which is used to iterate over d, yielding (char, offset, loc)
  482. @see iterdata
  483. @see itermakefilechars
  484. @see itercommandchars
  485. @return a tuple (expansion, token, offset). If all the data is consumed,
  486. token and offset will be None
  487. """
  488. assert callable(iterfunc)
  489. stacktop = ParseStackFrame(_PARSESTATE_TOPLEVEL, None, data.Expansion(loc=d.getloc(d.lstart)),
  490. tokenlist=stopon + ('$',),
  491. openbrace=None, closebrace=None)
  492. tokeniterator = _alltokens.finditer(d.s, offset, d.lend)
  493. di = iterfunc(d, offset, stacktop.tokenlist, tokeniterator)
  494. while True: # this is not a for loop because `di` changes during the function
  495. assert stacktop is not None
  496. try:
  497. s, token, tokenoffset, offset = di.next()
  498. except StopIteration:
  499. break
  500. stacktop.expansion.appendstr(s)
  501. if token is None:
  502. continue
  503. parsestate = stacktop.parsestate
  504. if token[0] == '$':
  505. if tokenoffset + 1 == d.lend:
  506. # an unterminated $ expands to nothing
  507. break
  508. loc = d.getloc(tokenoffset)
  509. c = token[1]
  510. if c == '$':
  511. assert len(token) == 2
  512. stacktop.expansion.appendstr('$')
  513. elif c in ('(', '{'):
  514. closebrace = _matchingbrace[c]
  515. if len(token) > 2:
  516. fname = token[2:].rstrip()
  517. fn = functions.functionmap[fname](loc)
  518. e = data.Expansion()
  519. if len(fn) + 1 == fn.maxargs:
  520. tokenlist = (c, closebrace, '$')
  521. else:
  522. tokenlist = (',', c, closebrace, '$')
  523. stacktop = ParseStackFrame(_PARSESTATE_FUNCTION, stacktop,
  524. e, tokenlist, function=fn,
  525. openbrace=c, closebrace=closebrace)
  526. else:
  527. e = data.Expansion()
  528. tokenlist = (':', c, closebrace, '$')
  529. stacktop = ParseStackFrame(_PARSESTATE_VARNAME, stacktop,
  530. e, tokenlist,
  531. openbrace=c, closebrace=closebrace, loc=loc)
  532. else:
  533. assert len(token) == 2
  534. e = data.Expansion.fromstring(c, loc)
  535. stacktop.expansion.appendfunc(functions.VariableRef(loc, e))
  536. elif token in ('(', '{'):
  537. assert token == stacktop.openbrace
  538. stacktop.expansion.appendstr(token)
  539. stacktop = ParseStackFrame(_PARSESTATE_PARENMATCH, stacktop,
  540. stacktop.expansion,
  541. (token, stacktop.closebrace, '$'),
  542. openbrace=token, closebrace=stacktop.closebrace, loc=d.getloc(tokenoffset))
  543. elif parsestate == _PARSESTATE_PARENMATCH:
  544. assert token == stacktop.closebrace
  545. stacktop.expansion.appendstr(token)
  546. stacktop = stacktop.parent
  547. elif parsestate == _PARSESTATE_TOPLEVEL:
  548. assert stacktop.parent is None
  549. return stacktop.expansion.finish(), token, offset
  550. elif parsestate == _PARSESTATE_FUNCTION:
  551. if token == ',':
  552. stacktop.function.append(stacktop.expansion.finish())
  553. stacktop.expansion = data.Expansion()
  554. if len(stacktop.function) + 1 == stacktop.function.maxargs:
  555. tokenlist = (stacktop.openbrace, stacktop.closebrace, '$')
  556. stacktop.tokenlist = tokenlist
  557. elif token in (')', '}'):
  558. fn = stacktop.function
  559. fn.append(stacktop.expansion.finish())
  560. fn.setup()
  561. stacktop = stacktop.parent
  562. stacktop.expansion.appendfunc(fn)
  563. else:
  564. assert False, "Not reached, _PARSESTATE_FUNCTION"
  565. elif parsestate == _PARSESTATE_VARNAME:
  566. if token == ':':
  567. stacktop.varname = stacktop.expansion
  568. stacktop.parsestate = _PARSESTATE_SUBSTFROM
  569. stacktop.expansion = data.Expansion()
  570. stacktop.tokenlist = ('=', stacktop.openbrace, stacktop.closebrace, '$')
  571. elif token in (')', '}'):
  572. fn = functions.VariableRef(stacktop.loc, stacktop.expansion.finish())
  573. stacktop = stacktop.parent
  574. stacktop.expansion.appendfunc(fn)
  575. else:
  576. assert False, "Not reached, _PARSESTATE_VARNAME"
  577. elif parsestate == _PARSESTATE_SUBSTFROM:
  578. if token == '=':
  579. stacktop.substfrom = stacktop.expansion
  580. stacktop.parsestate = _PARSESTATE_SUBSTTO
  581. stacktop.expansion = data.Expansion()
  582. stacktop.tokenlist = (stacktop.openbrace, stacktop.closebrace, '$')
  583. elif token in (')', '}'):
  584. # A substitution of the form $(VARNAME:.ee) is probably a mistake, but make
  585. # parses it. Issue a warning. Combine the varname and substfrom expansions to
  586. # make the compatible varname. See tests/var-substitutions.mk SIMPLE3SUBSTNAME
  587. _log.warning("%s: Variable reference looks like substitution without =", stacktop.loc)
  588. stacktop.varname.appendstr(':')
  589. stacktop.varname.concat(stacktop.expansion)
  590. fn = functions.VariableRef(stacktop.loc, stacktop.varname.finish())
  591. stacktop = stacktop.parent
  592. stacktop.expansion.appendfunc(fn)
  593. else:
  594. assert False, "Not reached, _PARSESTATE_SUBSTFROM"
  595. elif parsestate == _PARSESTATE_SUBSTTO:
  596. assert token in (')','}'), "Not reached, _PARSESTATE_SUBSTTO"
  597. fn = functions.SubstitutionRef(stacktop.loc, stacktop.varname.finish(),
  598. stacktop.substfrom.finish(), stacktop.expansion.finish())
  599. stacktop = stacktop.parent
  600. stacktop.expansion.appendfunc(fn)
  601. else:
  602. assert False, "Unexpected parse state %s" % stacktop.parsestate
  603. if stacktop.parent is not None and iterfunc == itercommandchars:
  604. di = itermakefilechars(d, offset, stacktop.tokenlist, tokeniterator,
  605. ignorecomments=True)
  606. else:
  607. di = iterfunc(d, offset, stacktop.tokenlist, tokeniterator)
  608. if stacktop.parent is not None:
  609. raise SyntaxError("Unterminated function call", d.getloc(offset))
  610. assert stacktop.parsestate == _PARSESTATE_TOPLEVEL
  611. return stacktop.expansion.finish(), None, None