PageRenderTime 55ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/build/pymake/pymake/parser.py

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