mparser.py 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  1. # Copyright 2014-2017 The Meson development team
  2. # Licensed under the Apache License, Version 2.0 (the "License");
  3. # you may not use this file except in compliance with the License.
  4. # You may obtain a copy of the License at
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. # Unless required by applicable law or agreed to in writing, software
  7. # distributed under the License is distributed on an "AS IS" BASIS,
  8. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. # See the License for the specific language governing permissions and
  10. # limitations under the License.
  11. import re
  12. import codecs
  13. import textwrap
  14. import types
  15. import typing as T
  16. from .mesonlib import MesonException
  17. from . import mlog
  18. if T.TYPE_CHECKING:
  19. from .ast import AstVisitor
  20. # This is the regex for the supported escape sequences of a regular string
  21. # literal, like 'abc\x00'
  22. ESCAPE_SEQUENCE_SINGLE_RE = re.compile(r'''
  23. ( \\U[A-Fa-f0-9]{8} # 8-digit hex escapes
  24. | \\u[A-Fa-f0-9]{4} # 4-digit hex escapes
  25. | \\x[A-Fa-f0-9]{2} # 2-digit hex escapes
  26. | \\[0-7]{1,3} # Octal escapes
  27. | \\N\{[^}]+\} # Unicode characters by name
  28. | \\[\\'abfnrtv] # Single-character escapes
  29. )''', re.UNICODE | re.VERBOSE)
  30. class MesonUnicodeDecodeError(MesonException):
  31. def __init__(self, match):
  32. super().__init__("%s" % match)
  33. self.match = match
  34. def decode_match(match):
  35. try:
  36. return codecs.decode(match.group(0), 'unicode_escape')
  37. except UnicodeDecodeError:
  38. raise MesonUnicodeDecodeError(match.group(0))
  39. class ParseException(MesonException):
  40. def __init__(self, text, line, lineno, colno):
  41. # Format as error message, followed by the line with the error, followed by a caret to show the error column.
  42. super().__init__("%s\n%s\n%s" % (text, line, '%s^' % (' ' * colno)))
  43. self.lineno = lineno
  44. self.colno = colno
  45. class BlockParseException(MesonException):
  46. def __init__(self, text, line, lineno, colno, start_line, start_lineno, start_colno):
  47. # This can be formatted in two ways - one if the block start and end are on the same line, and a different way if they are on different lines.
  48. if lineno == start_lineno:
  49. # If block start and end are on the same line, it is formatted as:
  50. # Error message
  51. # Followed by the line with the error
  52. # Followed by a caret to show the block start
  53. # Followed by underscores
  54. # Followed by a caret to show the block end.
  55. super().__init__("%s\n%s\n%s" % (text, line, '%s^%s^' % (' ' * start_colno, '_' * (colno - start_colno - 1))))
  56. else:
  57. # If block start and end are on different lines, it is formatted as:
  58. # Error message
  59. # Followed by the line with the error
  60. # Followed by a caret to show the error column.
  61. # Followed by a message saying where the block started.
  62. # Followed by the line of the block start.
  63. # Followed by a caret for the block start.
  64. super().__init__("%s\n%s\n%s\nFor a block that started at %d,%d\n%s\n%s" % (text, line, '%s^' % (' ' * colno), start_lineno, start_colno, start_line, "%s^" % (' ' * start_colno)))
  65. self.lineno = lineno
  66. self.colno = colno
  67. TV_TokenTypes = T.TypeVar('TV_TokenTypes', int, str, bool)
  68. class Token(T.Generic[TV_TokenTypes]):
  69. def __init__(self, tid: str, filename: str, line_start: int, lineno: int, colno: int, bytespan: T.Tuple[int, int], value: TV_TokenTypes):
  70. self.tid = tid # type: str
  71. self.filename = filename # type: str
  72. self.line_start = line_start # type: int
  73. self.lineno = lineno # type: int
  74. self.colno = colno # type: int
  75. self.bytespan = bytespan # type: T.Tuple[int, int]
  76. self.value = value # type: TV_TokenTypes
  77. def __eq__(self, other) -> bool:
  78. if isinstance(other, str):
  79. return self.tid == other
  80. return self.tid == other.tid
  81. class Lexer:
  82. def __init__(self, code: str):
  83. self.code = code
  84. self.keywords = {'true', 'false', 'if', 'else', 'elif',
  85. 'endif', 'and', 'or', 'not', 'foreach', 'endforeach',
  86. 'in', 'continue', 'break'}
  87. self.future_keywords = {'return'}
  88. self.token_specification = [
  89. # Need to be sorted longest to shortest.
  90. ('ignore', re.compile(r'[ \t]')),
  91. ('id', re.compile('[_a-zA-Z][_0-9a-zA-Z]*')),
  92. ('number', re.compile(r'0[bB][01]+|0[oO][0-7]+|0[xX][0-9a-fA-F]+|0|[1-9]\d*')),
  93. ('eol_cont', re.compile(r'\\\n')),
  94. ('eol', re.compile(r'\n')),
  95. ('multiline_string', re.compile(r"'''(.|\n)*?'''", re.M)),
  96. ('comment', re.compile(r'#.*')),
  97. ('lparen', re.compile(r'\(')),
  98. ('rparen', re.compile(r'\)')),
  99. ('lbracket', re.compile(r'\[')),
  100. ('rbracket', re.compile(r'\]')),
  101. ('lcurl', re.compile(r'\{')),
  102. ('rcurl', re.compile(r'\}')),
  103. ('dblquote', re.compile(r'"')),
  104. ('string', re.compile(r"'([^'\\]|(\\.))*'")),
  105. ('comma', re.compile(r',')),
  106. ('plusassign', re.compile(r'\+=')),
  107. ('dot', re.compile(r'\.')),
  108. ('plus', re.compile(r'\+')),
  109. ('dash', re.compile(r'-')),
  110. ('star', re.compile(r'\*')),
  111. ('percent', re.compile(r'%')),
  112. ('fslash', re.compile(r'/')),
  113. ('colon', re.compile(r':')),
  114. ('equal', re.compile(r'==')),
  115. ('nequal', re.compile(r'!=')),
  116. ('assign', re.compile(r'=')),
  117. ('le', re.compile(r'<=')),
  118. ('lt', re.compile(r'<')),
  119. ('ge', re.compile(r'>=')),
  120. ('gt', re.compile(r'>')),
  121. ('questionmark', re.compile(r'\?')),
  122. ]
  123. def getline(self, line_start: int) -> str:
  124. return self.code[line_start:self.code.find('\n', line_start)]
  125. def lex(self, filename: str) -> T.Generator[Token, None, None]:
  126. line_start = 0
  127. lineno = 1
  128. loc = 0
  129. par_count = 0
  130. bracket_count = 0
  131. curl_count = 0
  132. col = 0
  133. while loc < len(self.code):
  134. matched = False
  135. value = None # type: T.Union[str, bool, int]
  136. for (tid, reg) in self.token_specification:
  137. mo = reg.match(self.code, loc)
  138. if mo:
  139. curline = lineno
  140. curline_start = line_start
  141. col = mo.start() - line_start
  142. matched = True
  143. span_start = loc
  144. loc = mo.end()
  145. span_end = loc
  146. bytespan = (span_start, span_end)
  147. match_text = mo.group()
  148. if tid == 'ignore' or tid == 'comment':
  149. break
  150. elif tid == 'lparen':
  151. par_count += 1
  152. elif tid == 'rparen':
  153. par_count -= 1
  154. elif tid == 'lbracket':
  155. bracket_count += 1
  156. elif tid == 'rbracket':
  157. bracket_count -= 1
  158. elif tid == 'lcurl':
  159. curl_count += 1
  160. elif tid == 'rcurl':
  161. curl_count -= 1
  162. elif tid == 'dblquote':
  163. raise ParseException('Double quotes are not supported. Use single quotes.', self.getline(line_start), lineno, col)
  164. elif tid == 'string':
  165. # Handle here and not on the regexp to give a better error message.
  166. if match_text.find("\n") != -1:
  167. mlog.warning(textwrap.dedent("""\
  168. Newline character in a string detected, use ''' (three single quotes) for multiline strings instead.
  169. This will become a hard error in a future Meson release.\
  170. """),
  171. self.getline(line_start),
  172. str(lineno),
  173. str(col)
  174. )
  175. value = match_text[1:-1]
  176. try:
  177. value = ESCAPE_SEQUENCE_SINGLE_RE.sub(decode_match, value)
  178. except MesonUnicodeDecodeError as err:
  179. raise MesonException("Failed to parse escape sequence: '{}' in string:\n {}".format(err.match, match_text))
  180. elif tid == 'multiline_string':
  181. tid = 'string'
  182. value = match_text[3:-3]
  183. lines = match_text.split('\n')
  184. if len(lines) > 1:
  185. lineno += len(lines) - 1
  186. line_start = mo.end() - len(lines[-1])
  187. elif tid == 'number':
  188. value = int(match_text, base=0)
  189. elif tid == 'eol_cont':
  190. lineno += 1
  191. line_start = loc
  192. break
  193. elif tid == 'eol':
  194. lineno += 1
  195. line_start = loc
  196. if par_count > 0 or bracket_count > 0 or curl_count > 0:
  197. break
  198. elif tid == 'id':
  199. if match_text in self.keywords:
  200. tid = match_text
  201. else:
  202. if match_text in self.future_keywords:
  203. mlog.warning("Identifier '{}' will become a reserved keyword in a future release. Please rename it.".format(match_text),
  204. location=types.SimpleNamespace(filename=filename, lineno=lineno))
  205. value = match_text
  206. yield Token(tid, filename, curline_start, curline, col, bytespan, value)
  207. break
  208. if not matched:
  209. raise ParseException('lexer', self.getline(line_start), lineno, col)
  210. class BaseNode:
  211. def __init__(self, lineno: int, colno: int, filename: str, end_lineno: T.Optional[int] = None, end_colno: T.Optional[int] = None):
  212. self.lineno = lineno # type: int
  213. self.colno = colno # type: int
  214. self.filename = filename # type: str
  215. self.end_lineno = end_lineno if end_lineno is not None else self.lineno
  216. self.end_colno = end_colno if end_colno is not None else self.colno
  217. # Attributes for the visitors
  218. self.level = 0 # type: int
  219. self.ast_id = '' # type: str
  220. self.condition_level = 0 # type: int
  221. def accept(self, visitor: 'AstVisitor') -> None:
  222. fname = 'visit_{}'.format(type(self).__name__)
  223. if hasattr(visitor, fname):
  224. func = getattr(visitor, fname)
  225. if callable(func):
  226. func(self)
  227. class ElementaryNode(T.Generic[TV_TokenTypes], BaseNode):
  228. def __init__(self, token: Token[TV_TokenTypes]):
  229. super().__init__(token.lineno, token.colno, token.filename)
  230. self.value = token.value # type: TV_TokenTypes
  231. self.bytespan = token.bytespan # type: T.Tuple[int, int]
  232. class BooleanNode(ElementaryNode[bool]):
  233. def __init__(self, token: Token[bool]):
  234. super().__init__(token)
  235. assert isinstance(self.value, bool)
  236. class IdNode(ElementaryNode[str]):
  237. def __init__(self, token: Token[str]):
  238. super().__init__(token)
  239. assert isinstance(self.value, str)
  240. def __str__(self):
  241. return "Id node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno)
  242. class NumberNode(ElementaryNode[int]):
  243. def __init__(self, token: Token[int]):
  244. super().__init__(token)
  245. assert isinstance(self.value, int)
  246. class StringNode(ElementaryNode[str]):
  247. def __init__(self, token: Token[str]):
  248. super().__init__(token)
  249. assert isinstance(self.value, str)
  250. def __str__(self):
  251. return "String node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno)
  252. class ContinueNode(ElementaryNode):
  253. pass
  254. class BreakNode(ElementaryNode):
  255. pass
  256. class ArgumentNode(BaseNode):
  257. def __init__(self, token: Token[TV_TokenTypes]):
  258. super().__init__(token.lineno, token.colno, token.filename)
  259. self.arguments = [] # type: T.List[BaseNode]
  260. self.commas = [] # type: T.List[Token[TV_TokenTypes]]
  261. self.kwargs = {} # type: T.Dict[BaseNode, BaseNode]
  262. self.order_error = False
  263. def prepend(self, statement: BaseNode) -> None:
  264. if self.num_kwargs() > 0:
  265. self.order_error = True
  266. if not isinstance(statement, EmptyNode):
  267. self.arguments = [statement] + self.arguments
  268. def append(self, statement: BaseNode) -> None:
  269. if self.num_kwargs() > 0:
  270. self.order_error = True
  271. if not isinstance(statement, EmptyNode):
  272. self.arguments += [statement]
  273. def set_kwarg(self, name: IdNode, value: BaseNode) -> None:
  274. if name.value in [x.value for x in self.kwargs.keys() if isinstance(x, IdNode)]:
  275. mlog.warning('Keyword argument "{}" defined multiple times.'.format(name.value), location=self)
  276. mlog.warning('This will be an error in future Meson releases.')
  277. self.kwargs[name] = value
  278. def set_kwarg_no_check(self, name: BaseNode, value: BaseNode) -> None:
  279. self.kwargs[name] = value
  280. def num_args(self) -> int:
  281. return len(self.arguments)
  282. def num_kwargs(self) -> int:
  283. return len(self.kwargs)
  284. def incorrect_order(self) -> bool:
  285. return self.order_error
  286. def __len__(self) -> int:
  287. return self.num_args() # Fixme
  288. class ArrayNode(BaseNode):
  289. def __init__(self, args: ArgumentNode, lineno: int, colno: int, end_lineno: int, end_colno: int):
  290. super().__init__(lineno, colno, args.filename, end_lineno=end_lineno, end_colno=end_colno)
  291. self.args = args # type: ArgumentNode
  292. class DictNode(BaseNode):
  293. def __init__(self, args: ArgumentNode, lineno: int, colno: int, end_lineno: int, end_colno: int):
  294. super().__init__(lineno, colno, args.filename, end_lineno=end_lineno, end_colno=end_colno)
  295. self.args = args
  296. class EmptyNode(BaseNode):
  297. def __init__(self, lineno: int, colno: int, filename: str):
  298. super().__init__(lineno, colno, filename)
  299. self.value = None
  300. class OrNode(BaseNode):
  301. def __init__(self, left: BaseNode, right: BaseNode):
  302. super().__init__(left.lineno, left.colno, left.filename)
  303. self.left = left # type: BaseNode
  304. self.right = right # type: BaseNode
  305. class AndNode(BaseNode):
  306. def __init__(self, left: BaseNode, right: BaseNode):
  307. super().__init__(left.lineno, left.colno, left.filename)
  308. self.left = left # type: BaseNode
  309. self.right = right # type: BaseNode
  310. class ComparisonNode(BaseNode):
  311. def __init__(self, ctype: str, left: BaseNode, right: BaseNode):
  312. super().__init__(left.lineno, left.colno, left.filename)
  313. self.left = left # type: BaseNode
  314. self.right = right # type: BaseNode
  315. self.ctype = ctype # type: str
  316. class ArithmeticNode(BaseNode):
  317. def __init__(self, operation: str, left: BaseNode, right: BaseNode):
  318. super().__init__(left.lineno, left.colno, left.filename)
  319. self.left = left # type: BaseNode
  320. self.right = right # type: BaseNode
  321. self.operation = operation # type: str
  322. class NotNode(BaseNode):
  323. def __init__(self, token: Token[TV_TokenTypes], value: BaseNode):
  324. super().__init__(token.lineno, token.colno, token.filename)
  325. self.value = value # type: BaseNode
  326. class CodeBlockNode(BaseNode):
  327. def __init__(self, token: Token[TV_TokenTypes]):
  328. super().__init__(token.lineno, token.colno, token.filename)
  329. self.lines = [] # type: T.List[BaseNode]
  330. class IndexNode(BaseNode):
  331. def __init__(self, iobject: BaseNode, index: BaseNode):
  332. super().__init__(iobject.lineno, iobject.colno, iobject.filename)
  333. self.iobject = iobject # type: BaseNode
  334. self.index = index # type: BaseNode
  335. class MethodNode(BaseNode):
  336. def __init__(self, filename: str, lineno: int, colno: int, source_object: BaseNode, name: str, args: ArgumentNode):
  337. super().__init__(lineno, colno, filename)
  338. self.source_object = source_object # type: BaseNode
  339. self.name = name # type: str
  340. assert(isinstance(self.name, str))
  341. self.args = args # type: ArgumentNode
  342. class FunctionNode(BaseNode):
  343. def __init__(self, filename: str, lineno: int, colno: int, end_lineno: int, end_colno: int, func_name: str, args: ArgumentNode):
  344. super().__init__(lineno, colno, filename, end_lineno=end_lineno, end_colno=end_colno)
  345. self.func_name = func_name # type: str
  346. assert(isinstance(func_name, str))
  347. self.args = args # type: ArgumentNode
  348. class AssignmentNode(BaseNode):
  349. def __init__(self, filename: str, lineno: int, colno: int, var_name: str, value: BaseNode):
  350. super().__init__(lineno, colno, filename)
  351. self.var_name = var_name # type: str
  352. assert(isinstance(var_name, str))
  353. self.value = value # type: BaseNode
  354. class PlusAssignmentNode(BaseNode):
  355. def __init__(self, filename: str, lineno: int, colno: int, var_name: str, value: BaseNode):
  356. super().__init__(lineno, colno, filename)
  357. self.var_name = var_name # type: str
  358. assert(isinstance(var_name, str))
  359. self.value = value # type: BaseNode
  360. class ForeachClauseNode(BaseNode):
  361. def __init__(self, token: Token, varnames: T.List[str], items: BaseNode, block: CodeBlockNode):
  362. super().__init__(token.lineno, token.colno, token.filename)
  363. self.varnames = varnames # type: T.List[str]
  364. self.items = items # type: BaseNode
  365. self.block = block # type: CodeBlockNode
  366. class IfNode(BaseNode):
  367. def __init__(self, linenode: BaseNode, condition: BaseNode, block: CodeBlockNode):
  368. super().__init__(linenode.lineno, linenode.colno, linenode.filename)
  369. self.condition = condition # type: BaseNode
  370. self.block = block # type: CodeBlockNode
  371. class IfClauseNode(BaseNode):
  372. def __init__(self, linenode: BaseNode):
  373. super().__init__(linenode.lineno, linenode.colno, linenode.filename)
  374. self.ifs = [] # type: T.List[IfNode]
  375. self.elseblock = EmptyNode(linenode.lineno, linenode.colno, linenode.filename) # type: T.Union[EmptyNode, CodeBlockNode]
  376. class UMinusNode(BaseNode):
  377. def __init__(self, current_location: Token, value: BaseNode):
  378. super().__init__(current_location.lineno, current_location.colno, current_location.filename)
  379. self.value = value # type: BaseNode
  380. class TernaryNode(BaseNode):
  381. def __init__(self, condition: BaseNode, trueblock: BaseNode, falseblock: BaseNode):
  382. super().__init__(condition.lineno, condition.colno, condition.filename)
  383. self.condition = condition # type: BaseNode
  384. self.trueblock = trueblock # type: BaseNode
  385. self.falseblock = falseblock # type: BaseNode
  386. comparison_map = {'equal': '==',
  387. 'nequal': '!=',
  388. 'lt': '<',
  389. 'le': '<=',
  390. 'gt': '>',
  391. 'ge': '>=',
  392. 'in': 'in',
  393. 'notin': 'not in',
  394. }
  395. # Recursive descent parser for Meson's definition language.
  396. # Very basic apart from the fact that we have many precedence
  397. # levels so there are not enough words to describe them all.
  398. # Enter numbering:
  399. #
  400. # 1 assignment
  401. # 2 or
  402. # 3 and
  403. # 4 comparison
  404. # 5 arithmetic
  405. # 6 negation
  406. # 7 funcall, method call
  407. # 8 parentheses
  408. # 9 plain token
  409. class Parser:
  410. def __init__(self, code: str, filename: str):
  411. self.lexer = Lexer(code)
  412. self.stream = self.lexer.lex(filename)
  413. self.current = Token('eof', '', 0, 0, 0, (0, 0), None) # type: Token
  414. self.getsym()
  415. self.in_ternary = False
  416. def getsym(self) -> None:
  417. try:
  418. self.current = next(self.stream)
  419. except StopIteration:
  420. self.current = Token('eof', '', self.current.line_start, self.current.lineno, self.current.colno + self.current.bytespan[1] - self.current.bytespan[0], (0, 0), None)
  421. def getline(self) -> str:
  422. return self.lexer.getline(self.current.line_start)
  423. def accept(self, s: str) -> bool:
  424. if self.current.tid == s:
  425. self.getsym()
  426. return True
  427. return False
  428. def accept_any(self, tids: T.Sequence[str]) -> str:
  429. tid = self.current.tid
  430. if tid in tids:
  431. self.getsym()
  432. return tid
  433. return ''
  434. def expect(self, s: str) -> bool:
  435. if self.accept(s):
  436. return True
  437. raise ParseException('Expecting %s got %s.' % (s, self.current.tid), self.getline(), self.current.lineno, self.current.colno)
  438. def block_expect(self, s: str, block_start: Token) -> bool:
  439. if self.accept(s):
  440. return True
  441. raise BlockParseException('Expecting %s got %s.' % (s, self.current.tid), self.getline(), self.current.lineno, self.current.colno, self.lexer.getline(block_start.line_start), block_start.lineno, block_start.colno)
  442. def parse(self) -> CodeBlockNode:
  443. block = self.codeblock()
  444. self.expect('eof')
  445. return block
  446. def statement(self) -> BaseNode:
  447. return self.e1()
  448. def e1(self) -> BaseNode:
  449. left = self.e2()
  450. if self.accept('plusassign'):
  451. value = self.e1()
  452. if not isinstance(left, IdNode):
  453. raise ParseException('Plusassignment target must be an id.', self.getline(), left.lineno, left.colno)
  454. assert isinstance(left.value, str)
  455. return PlusAssignmentNode(left.filename, left.lineno, left.colno, left.value, value)
  456. elif self.accept('assign'):
  457. value = self.e1()
  458. if not isinstance(left, IdNode):
  459. raise ParseException('Assignment target must be an id.',
  460. self.getline(), left.lineno, left.colno)
  461. assert isinstance(left.value, str)
  462. return AssignmentNode(left.filename, left.lineno, left.colno, left.value, value)
  463. elif self.accept('questionmark'):
  464. if self.in_ternary:
  465. raise ParseException('Nested ternary operators are not allowed.',
  466. self.getline(), left.lineno, left.colno)
  467. self.in_ternary = True
  468. trueblock = self.e1()
  469. self.expect('colon')
  470. falseblock = self.e1()
  471. self.in_ternary = False
  472. return TernaryNode(left, trueblock, falseblock)
  473. return left
  474. def e2(self) -> BaseNode:
  475. left = self.e3()
  476. while self.accept('or'):
  477. if isinstance(left, EmptyNode):
  478. raise ParseException('Invalid or clause.',
  479. self.getline(), left.lineno, left.colno)
  480. left = OrNode(left, self.e3())
  481. return left
  482. def e3(self) -> BaseNode:
  483. left = self.e4()
  484. while self.accept('and'):
  485. if isinstance(left, EmptyNode):
  486. raise ParseException('Invalid and clause.',
  487. self.getline(), left.lineno, left.colno)
  488. left = AndNode(left, self.e4())
  489. return left
  490. def e4(self) -> BaseNode:
  491. left = self.e5()
  492. for nodename, operator_type in comparison_map.items():
  493. if self.accept(nodename):
  494. return ComparisonNode(operator_type, left, self.e5())
  495. if self.accept('not') and self.accept('in'):
  496. return ComparisonNode('notin', left, self.e5())
  497. return left
  498. def e5(self) -> BaseNode:
  499. return self.e5addsub()
  500. def e5addsub(self) -> BaseNode:
  501. op_map = {
  502. 'plus': 'add',
  503. 'dash': 'sub',
  504. }
  505. left = self.e5muldiv()
  506. while True:
  507. op = self.accept_any(tuple(op_map.keys()))
  508. if op:
  509. left = ArithmeticNode(op_map[op], left, self.e5muldiv())
  510. else:
  511. break
  512. return left
  513. def e5muldiv(self) -> BaseNode:
  514. op_map = {
  515. 'percent': 'mod',
  516. 'star': 'mul',
  517. 'fslash': 'div',
  518. }
  519. left = self.e6()
  520. while True:
  521. op = self.accept_any(tuple(op_map.keys()))
  522. if op:
  523. left = ArithmeticNode(op_map[op], left, self.e6())
  524. else:
  525. break
  526. return left
  527. def e6(self) -> BaseNode:
  528. if self.accept('not'):
  529. return NotNode(self.current, self.e7())
  530. if self.accept('dash'):
  531. return UMinusNode(self.current, self.e7())
  532. return self.e7()
  533. def e7(self) -> BaseNode:
  534. left = self.e8()
  535. block_start = self.current
  536. if self.accept('lparen'):
  537. args = self.args()
  538. self.block_expect('rparen', block_start)
  539. if not isinstance(left, IdNode):
  540. raise ParseException('Function call must be applied to plain id',
  541. self.getline(), left.lineno, left.colno)
  542. assert isinstance(left.value, str)
  543. left = FunctionNode(left.filename, left.lineno, left.colno, self.current.lineno, self.current.colno, left.value, args)
  544. go_again = True
  545. while go_again:
  546. go_again = False
  547. if self.accept('dot'):
  548. go_again = True
  549. left = self.method_call(left)
  550. if self.accept('lbracket'):
  551. go_again = True
  552. left = self.index_call(left)
  553. return left
  554. def e8(self) -> BaseNode:
  555. block_start = self.current
  556. if self.accept('lparen'):
  557. e = self.statement()
  558. self.block_expect('rparen', block_start)
  559. return e
  560. elif self.accept('lbracket'):
  561. args = self.args()
  562. self.block_expect('rbracket', block_start)
  563. return ArrayNode(args, block_start.lineno, block_start.colno, self.current.lineno, self.current.colno)
  564. elif self.accept('lcurl'):
  565. key_values = self.key_values()
  566. self.block_expect('rcurl', block_start)
  567. return DictNode(key_values, block_start.lineno, block_start.colno, self.current.lineno, self.current.colno)
  568. else:
  569. return self.e9()
  570. def e9(self) -> BaseNode:
  571. t = self.current
  572. if self.accept('true'):
  573. t.value = True
  574. return BooleanNode(t)
  575. if self.accept('false'):
  576. t.value = False
  577. return BooleanNode(t)
  578. if self.accept('id'):
  579. return IdNode(t)
  580. if self.accept('number'):
  581. return NumberNode(t)
  582. if self.accept('string'):
  583. return StringNode(t)
  584. return EmptyNode(self.current.lineno, self.current.colno, self.current.filename)
  585. def key_values(self) -> ArgumentNode:
  586. s = self.statement() # type: BaseNode
  587. a = ArgumentNode(self.current)
  588. while not isinstance(s, EmptyNode):
  589. if self.accept('colon'):
  590. a.set_kwarg_no_check(s, self.statement())
  591. potential = self.current
  592. if not self.accept('comma'):
  593. return a
  594. a.commas.append(potential)
  595. else:
  596. raise ParseException('Only key:value pairs are valid in dict construction.',
  597. self.getline(), s.lineno, s.colno)
  598. s = self.statement()
  599. return a
  600. def args(self) -> ArgumentNode:
  601. s = self.statement() # type: BaseNode
  602. a = ArgumentNode(self.current)
  603. while not isinstance(s, EmptyNode):
  604. potential = self.current
  605. if self.accept('comma'):
  606. a.commas.append(potential)
  607. a.append(s)
  608. elif self.accept('colon'):
  609. if not isinstance(s, IdNode):
  610. raise ParseException('Dictionary key must be a plain identifier.',
  611. self.getline(), s.lineno, s.colno)
  612. a.set_kwarg(s, self.statement())
  613. potential = self.current
  614. if not self.accept('comma'):
  615. return a
  616. a.commas.append(potential)
  617. else:
  618. a.append(s)
  619. return a
  620. s = self.statement()
  621. return a
  622. def method_call(self, source_object) -> MethodNode:
  623. methodname = self.e9()
  624. if not(isinstance(methodname, IdNode)):
  625. raise ParseException('Method name must be plain id',
  626. self.getline(), self.current.lineno, self.current.colno)
  627. assert isinstance(methodname.value, str)
  628. self.expect('lparen')
  629. args = self.args()
  630. self.expect('rparen')
  631. method = MethodNode(methodname.filename, methodname.lineno, methodname.colno, source_object, methodname.value, args)
  632. if self.accept('dot'):
  633. return self.method_call(method)
  634. return method
  635. def index_call(self, source_object) -> IndexNode:
  636. index_statement = self.statement()
  637. self.expect('rbracket')
  638. return IndexNode(source_object, index_statement)
  639. def foreachblock(self) -> ForeachClauseNode:
  640. t = self.current
  641. self.expect('id')
  642. assert isinstance(t.value, str)
  643. varname = t
  644. varnames = [t.value] # type: T.List[str]
  645. if self.accept('comma'):
  646. t = self.current
  647. self.expect('id')
  648. assert isinstance(t.value, str)
  649. varnames.append(t.value)
  650. self.expect('colon')
  651. items = self.statement()
  652. block = self.codeblock()
  653. return ForeachClauseNode(varname, varnames, items, block)
  654. def ifblock(self) -> IfClauseNode:
  655. condition = self.statement()
  656. clause = IfClauseNode(condition)
  657. self.expect('eol')
  658. block = self.codeblock()
  659. clause.ifs.append(IfNode(clause, condition, block))
  660. self.elseifblock(clause)
  661. elseblock = self.elseblock()
  662. if elseblock:
  663. clause.elseblock = elseblock
  664. return clause
  665. def elseifblock(self, clause) -> None:
  666. while self.accept('elif'):
  667. s = self.statement()
  668. self.expect('eol')
  669. b = self.codeblock()
  670. clause.ifs.append(IfNode(s, s, b))
  671. def elseblock(self) -> T.Optional[CodeBlockNode]:
  672. if self.accept('else'):
  673. self.expect('eol')
  674. return self.codeblock()
  675. return None
  676. def line(self) -> BaseNode:
  677. block_start = self.current
  678. if self.current == 'eol':
  679. return EmptyNode(self.current.lineno, self.current.colno, self.current.filename)
  680. if self.accept('if'):
  681. ifblock = self.ifblock()
  682. self.block_expect('endif', block_start)
  683. return ifblock
  684. if self.accept('foreach'):
  685. forblock = self.foreachblock()
  686. self.block_expect('endforeach', block_start)
  687. return forblock
  688. if self.accept('continue'):
  689. return ContinueNode(self.current)
  690. if self.accept('break'):
  691. return BreakNode(self.current)
  692. return self.statement()
  693. def codeblock(self) -> CodeBlockNode:
  694. block = CodeBlockNode(self.current)
  695. cond = True
  696. while cond:
  697. curline = self.line()
  698. if not isinstance(curline, EmptyNode):
  699. block.lines.append(curline)
  700. cond = self.accept('eol')
  701. return block