123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795 |
- # Copyright 2014-2017 The Meson development team
- # Licensed under the Apache License, Version 2.0 (the "License");
- # you may not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- # http://www.apache.org/licenses/LICENSE-2.0
- # Unless required by applicable law or agreed to in writing, software
- # distributed under the License is distributed on an "AS IS" BASIS,
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- # See the License for the specific language governing permissions and
- # limitations under the License.
- import re
- import codecs
- import textwrap
- import types
- import typing as T
- from .mesonlib import MesonException
- from . import mlog
- if T.TYPE_CHECKING:
- from .ast import AstVisitor
- # This is the regex for the supported escape sequences of a regular string
- # literal, like 'abc\x00'
- ESCAPE_SEQUENCE_SINGLE_RE = re.compile(r'''
- ( \\U[A-Fa-f0-9]{8} # 8-digit hex escapes
- | \\u[A-Fa-f0-9]{4} # 4-digit hex escapes
- | \\x[A-Fa-f0-9]{2} # 2-digit hex escapes
- | \\[0-7]{1,3} # Octal escapes
- | \\N\{[^}]+\} # Unicode characters by name
- | \\[\\'abfnrtv] # Single-character escapes
- )''', re.UNICODE | re.VERBOSE)
- class MesonUnicodeDecodeError(MesonException):
- def __init__(self, match):
- super().__init__("%s" % match)
- self.match = match
- def decode_match(match):
- try:
- return codecs.decode(match.group(0), 'unicode_escape')
- except UnicodeDecodeError:
- raise MesonUnicodeDecodeError(match.group(0))
- class ParseException(MesonException):
- def __init__(self, text, line, lineno, colno):
- # Format as error message, followed by the line with the error, followed by a caret to show the error column.
- super().__init__("%s\n%s\n%s" % (text, line, '%s^' % (' ' * colno)))
- self.lineno = lineno
- self.colno = colno
- class BlockParseException(MesonException):
- def __init__(self, text, line, lineno, colno, start_line, start_lineno, start_colno):
- # 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.
- if lineno == start_lineno:
- # If block start and end are on the same line, it is formatted as:
- # Error message
- # Followed by the line with the error
- # Followed by a caret to show the block start
- # Followed by underscores
- # Followed by a caret to show the block end.
- super().__init__("%s\n%s\n%s" % (text, line, '%s^%s^' % (' ' * start_colno, '_' * (colno - start_colno - 1))))
- else:
- # If block start and end are on different lines, it is formatted as:
- # Error message
- # Followed by the line with the error
- # Followed by a caret to show the error column.
- # Followed by a message saying where the block started.
- # Followed by the line of the block start.
- # Followed by a caret for the block start.
- 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)))
- self.lineno = lineno
- self.colno = colno
- TV_TokenTypes = T.TypeVar('TV_TokenTypes', int, str, bool)
- class Token(T.Generic[TV_TokenTypes]):
- def __init__(self, tid: str, filename: str, line_start: int, lineno: int, colno: int, bytespan: T.Tuple[int, int], value: TV_TokenTypes):
- self.tid = tid # type: str
- self.filename = filename # type: str
- self.line_start = line_start # type: int
- self.lineno = lineno # type: int
- self.colno = colno # type: int
- self.bytespan = bytespan # type: T.Tuple[int, int]
- self.value = value # type: TV_TokenTypes
- def __eq__(self, other) -> bool:
- if isinstance(other, str):
- return self.tid == other
- return self.tid == other.tid
- class Lexer:
- def __init__(self, code: str):
- self.code = code
- self.keywords = {'true', 'false', 'if', 'else', 'elif',
- 'endif', 'and', 'or', 'not', 'foreach', 'endforeach',
- 'in', 'continue', 'break'}
- self.future_keywords = {'return'}
- self.token_specification = [
- # Need to be sorted longest to shortest.
- ('ignore', re.compile(r'[ \t]')),
- ('id', re.compile('[_a-zA-Z][_0-9a-zA-Z]*')),
- ('number', re.compile(r'0[bB][01]+|0[oO][0-7]+|0[xX][0-9a-fA-F]+|0|[1-9]\d*')),
- ('eol_cont', re.compile(r'\\\n')),
- ('eol', re.compile(r'\n')),
- ('multiline_string', re.compile(r"'''(.|\n)*?'''", re.M)),
- ('comment', re.compile(r'#.*')),
- ('lparen', re.compile(r'\(')),
- ('rparen', re.compile(r'\)')),
- ('lbracket', re.compile(r'\[')),
- ('rbracket', re.compile(r'\]')),
- ('lcurl', re.compile(r'\{')),
- ('rcurl', re.compile(r'\}')),
- ('dblquote', re.compile(r'"')),
- ('string', re.compile(r"'([^'\\]|(\\.))*'")),
- ('comma', re.compile(r',')),
- ('plusassign', re.compile(r'\+=')),
- ('dot', re.compile(r'\.')),
- ('plus', re.compile(r'\+')),
- ('dash', re.compile(r'-')),
- ('star', re.compile(r'\*')),
- ('percent', re.compile(r'%')),
- ('fslash', re.compile(r'/')),
- ('colon', re.compile(r':')),
- ('equal', re.compile(r'==')),
- ('nequal', re.compile(r'!=')),
- ('assign', re.compile(r'=')),
- ('le', re.compile(r'<=')),
- ('lt', re.compile(r'<')),
- ('ge', re.compile(r'>=')),
- ('gt', re.compile(r'>')),
- ('questionmark', re.compile(r'\?')),
- ]
- def getline(self, line_start: int) -> str:
- return self.code[line_start:self.code.find('\n', line_start)]
- def lex(self, filename: str) -> T.Generator[Token, None, None]:
- line_start = 0
- lineno = 1
- loc = 0
- par_count = 0
- bracket_count = 0
- curl_count = 0
- col = 0
- while loc < len(self.code):
- matched = False
- value = None # type: T.Union[str, bool, int]
- for (tid, reg) in self.token_specification:
- mo = reg.match(self.code, loc)
- if mo:
- curline = lineno
- curline_start = line_start
- col = mo.start() - line_start
- matched = True
- span_start = loc
- loc = mo.end()
- span_end = loc
- bytespan = (span_start, span_end)
- match_text = mo.group()
- if tid == 'ignore' or tid == 'comment':
- break
- elif tid == 'lparen':
- par_count += 1
- elif tid == 'rparen':
- par_count -= 1
- elif tid == 'lbracket':
- bracket_count += 1
- elif tid == 'rbracket':
- bracket_count -= 1
- elif tid == 'lcurl':
- curl_count += 1
- elif tid == 'rcurl':
- curl_count -= 1
- elif tid == 'dblquote':
- raise ParseException('Double quotes are not supported. Use single quotes.', self.getline(line_start), lineno, col)
- elif tid == 'string':
- # Handle here and not on the regexp to give a better error message.
- if match_text.find("\n") != -1:
- mlog.warning(textwrap.dedent("""\
- Newline character in a string detected, use ''' (three single quotes) for multiline strings instead.
- This will become a hard error in a future Meson release.\
- """),
- self.getline(line_start),
- str(lineno),
- str(col)
- )
- value = match_text[1:-1]
- try:
- value = ESCAPE_SEQUENCE_SINGLE_RE.sub(decode_match, value)
- except MesonUnicodeDecodeError as err:
- raise MesonException("Failed to parse escape sequence: '{}' in string:\n {}".format(err.match, match_text))
- elif tid == 'multiline_string':
- tid = 'string'
- value = match_text[3:-3]
- lines = match_text.split('\n')
- if len(lines) > 1:
- lineno += len(lines) - 1
- line_start = mo.end() - len(lines[-1])
- elif tid == 'number':
- value = int(match_text, base=0)
- elif tid == 'eol_cont':
- lineno += 1
- line_start = loc
- break
- elif tid == 'eol':
- lineno += 1
- line_start = loc
- if par_count > 0 or bracket_count > 0 or curl_count > 0:
- break
- elif tid == 'id':
- if match_text in self.keywords:
- tid = match_text
- else:
- if match_text in self.future_keywords:
- mlog.warning("Identifier '{}' will become a reserved keyword in a future release. Please rename it.".format(match_text),
- location=types.SimpleNamespace(filename=filename, lineno=lineno))
- value = match_text
- yield Token(tid, filename, curline_start, curline, col, bytespan, value)
- break
- if not matched:
- raise ParseException('lexer', self.getline(line_start), lineno, col)
- class BaseNode:
- def __init__(self, lineno: int, colno: int, filename: str, end_lineno: T.Optional[int] = None, end_colno: T.Optional[int] = None):
- self.lineno = lineno # type: int
- self.colno = colno # type: int
- self.filename = filename # type: str
- self.end_lineno = end_lineno if end_lineno is not None else self.lineno
- self.end_colno = end_colno if end_colno is not None else self.colno
- # Attributes for the visitors
- self.level = 0 # type: int
- self.ast_id = '' # type: str
- self.condition_level = 0 # type: int
- def accept(self, visitor: 'AstVisitor') -> None:
- fname = 'visit_{}'.format(type(self).__name__)
- if hasattr(visitor, fname):
- func = getattr(visitor, fname)
- if callable(func):
- func(self)
- class ElementaryNode(T.Generic[TV_TokenTypes], BaseNode):
- def __init__(self, token: Token[TV_TokenTypes]):
- super().__init__(token.lineno, token.colno, token.filename)
- self.value = token.value # type: TV_TokenTypes
- self.bytespan = token.bytespan # type: T.Tuple[int, int]
- class BooleanNode(ElementaryNode[bool]):
- def __init__(self, token: Token[bool]):
- super().__init__(token)
- assert isinstance(self.value, bool)
- class IdNode(ElementaryNode[str]):
- def __init__(self, token: Token[str]):
- super().__init__(token)
- assert isinstance(self.value, str)
- def __str__(self):
- return "Id node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno)
- class NumberNode(ElementaryNode[int]):
- def __init__(self, token: Token[int]):
- super().__init__(token)
- assert isinstance(self.value, int)
- class StringNode(ElementaryNode[str]):
- def __init__(self, token: Token[str]):
- super().__init__(token)
- assert isinstance(self.value, str)
- def __str__(self):
- return "String node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno)
- class ContinueNode(ElementaryNode):
- pass
- class BreakNode(ElementaryNode):
- pass
- class ArgumentNode(BaseNode):
- def __init__(self, token: Token[TV_TokenTypes]):
- super().__init__(token.lineno, token.colno, token.filename)
- self.arguments = [] # type: T.List[BaseNode]
- self.commas = [] # type: T.List[Token[TV_TokenTypes]]
- self.kwargs = {} # type: T.Dict[BaseNode, BaseNode]
- self.order_error = False
- def prepend(self, statement: BaseNode) -> None:
- if self.num_kwargs() > 0:
- self.order_error = True
- if not isinstance(statement, EmptyNode):
- self.arguments = [statement] + self.arguments
- def append(self, statement: BaseNode) -> None:
- if self.num_kwargs() > 0:
- self.order_error = True
- if not isinstance(statement, EmptyNode):
- self.arguments += [statement]
- def set_kwarg(self, name: IdNode, value: BaseNode) -> None:
- if name.value in [x.value for x in self.kwargs.keys() if isinstance(x, IdNode)]:
- mlog.warning('Keyword argument "{}" defined multiple times.'.format(name.value), location=self)
- mlog.warning('This will be an error in future Meson releases.')
- self.kwargs[name] = value
- def set_kwarg_no_check(self, name: BaseNode, value: BaseNode) -> None:
- self.kwargs[name] = value
- def num_args(self) -> int:
- return len(self.arguments)
- def num_kwargs(self) -> int:
- return len(self.kwargs)
- def incorrect_order(self) -> bool:
- return self.order_error
- def __len__(self) -> int:
- return self.num_args() # Fixme
- class ArrayNode(BaseNode):
- def __init__(self, args: ArgumentNode, lineno: int, colno: int, end_lineno: int, end_colno: int):
- super().__init__(lineno, colno, args.filename, end_lineno=end_lineno, end_colno=end_colno)
- self.args = args # type: ArgumentNode
- class DictNode(BaseNode):
- def __init__(self, args: ArgumentNode, lineno: int, colno: int, end_lineno: int, end_colno: int):
- super().__init__(lineno, colno, args.filename, end_lineno=end_lineno, end_colno=end_colno)
- self.args = args
- class EmptyNode(BaseNode):
- def __init__(self, lineno: int, colno: int, filename: str):
- super().__init__(lineno, colno, filename)
- self.value = None
- class OrNode(BaseNode):
- def __init__(self, left: BaseNode, right: BaseNode):
- super().__init__(left.lineno, left.colno, left.filename)
- self.left = left # type: BaseNode
- self.right = right # type: BaseNode
- class AndNode(BaseNode):
- def __init__(self, left: BaseNode, right: BaseNode):
- super().__init__(left.lineno, left.colno, left.filename)
- self.left = left # type: BaseNode
- self.right = right # type: BaseNode
- class ComparisonNode(BaseNode):
- def __init__(self, ctype: str, left: BaseNode, right: BaseNode):
- super().__init__(left.lineno, left.colno, left.filename)
- self.left = left # type: BaseNode
- self.right = right # type: BaseNode
- self.ctype = ctype # type: str
- class ArithmeticNode(BaseNode):
- def __init__(self, operation: str, left: BaseNode, right: BaseNode):
- super().__init__(left.lineno, left.colno, left.filename)
- self.left = left # type: BaseNode
- self.right = right # type: BaseNode
- self.operation = operation # type: str
- class NotNode(BaseNode):
- def __init__(self, token: Token[TV_TokenTypes], value: BaseNode):
- super().__init__(token.lineno, token.colno, token.filename)
- self.value = value # type: BaseNode
- class CodeBlockNode(BaseNode):
- def __init__(self, token: Token[TV_TokenTypes]):
- super().__init__(token.lineno, token.colno, token.filename)
- self.lines = [] # type: T.List[BaseNode]
- class IndexNode(BaseNode):
- def __init__(self, iobject: BaseNode, index: BaseNode):
- super().__init__(iobject.lineno, iobject.colno, iobject.filename)
- self.iobject = iobject # type: BaseNode
- self.index = index # type: BaseNode
- class MethodNode(BaseNode):
- def __init__(self, filename: str, lineno: int, colno: int, source_object: BaseNode, name: str, args: ArgumentNode):
- super().__init__(lineno, colno, filename)
- self.source_object = source_object # type: BaseNode
- self.name = name # type: str
- assert(isinstance(self.name, str))
- self.args = args # type: ArgumentNode
- class FunctionNode(BaseNode):
- def __init__(self, filename: str, lineno: int, colno: int, end_lineno: int, end_colno: int, func_name: str, args: ArgumentNode):
- super().__init__(lineno, colno, filename, end_lineno=end_lineno, end_colno=end_colno)
- self.func_name = func_name # type: str
- assert(isinstance(func_name, str))
- self.args = args # type: ArgumentNode
- class AssignmentNode(BaseNode):
- def __init__(self, filename: str, lineno: int, colno: int, var_name: str, value: BaseNode):
- super().__init__(lineno, colno, filename)
- self.var_name = var_name # type: str
- assert(isinstance(var_name, str))
- self.value = value # type: BaseNode
- class PlusAssignmentNode(BaseNode):
- def __init__(self, filename: str, lineno: int, colno: int, var_name: str, value: BaseNode):
- super().__init__(lineno, colno, filename)
- self.var_name = var_name # type: str
- assert(isinstance(var_name, str))
- self.value = value # type: BaseNode
- class ForeachClauseNode(BaseNode):
- def __init__(self, token: Token, varnames: T.List[str], items: BaseNode, block: CodeBlockNode):
- super().__init__(token.lineno, token.colno, token.filename)
- self.varnames = varnames # type: T.List[str]
- self.items = items # type: BaseNode
- self.block = block # type: CodeBlockNode
- class IfNode(BaseNode):
- def __init__(self, linenode: BaseNode, condition: BaseNode, block: CodeBlockNode):
- super().__init__(linenode.lineno, linenode.colno, linenode.filename)
- self.condition = condition # type: BaseNode
- self.block = block # type: CodeBlockNode
- class IfClauseNode(BaseNode):
- def __init__(self, linenode: BaseNode):
- super().__init__(linenode.lineno, linenode.colno, linenode.filename)
- self.ifs = [] # type: T.List[IfNode]
- self.elseblock = EmptyNode(linenode.lineno, linenode.colno, linenode.filename) # type: T.Union[EmptyNode, CodeBlockNode]
- class UMinusNode(BaseNode):
- def __init__(self, current_location: Token, value: BaseNode):
- super().__init__(current_location.lineno, current_location.colno, current_location.filename)
- self.value = value # type: BaseNode
- class TernaryNode(BaseNode):
- def __init__(self, condition: BaseNode, trueblock: BaseNode, falseblock: BaseNode):
- super().__init__(condition.lineno, condition.colno, condition.filename)
- self.condition = condition # type: BaseNode
- self.trueblock = trueblock # type: BaseNode
- self.falseblock = falseblock # type: BaseNode
- comparison_map = {'equal': '==',
- 'nequal': '!=',
- 'lt': '<',
- 'le': '<=',
- 'gt': '>',
- 'ge': '>=',
- 'in': 'in',
- 'notin': 'not in',
- }
- # Recursive descent parser for Meson's definition language.
- # Very basic apart from the fact that we have many precedence
- # levels so there are not enough words to describe them all.
- # Enter numbering:
- #
- # 1 assignment
- # 2 or
- # 3 and
- # 4 comparison
- # 5 arithmetic
- # 6 negation
- # 7 funcall, method call
- # 8 parentheses
- # 9 plain token
- class Parser:
- def __init__(self, code: str, filename: str):
- self.lexer = Lexer(code)
- self.stream = self.lexer.lex(filename)
- self.current = Token('eof', '', 0, 0, 0, (0, 0), None) # type: Token
- self.getsym()
- self.in_ternary = False
- def getsym(self) -> None:
- try:
- self.current = next(self.stream)
- except StopIteration:
- 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)
- def getline(self) -> str:
- return self.lexer.getline(self.current.line_start)
- def accept(self, s: str) -> bool:
- if self.current.tid == s:
- self.getsym()
- return True
- return False
- def accept_any(self, tids: T.Sequence[str]) -> str:
- tid = self.current.tid
- if tid in tids:
- self.getsym()
- return tid
- return ''
- def expect(self, s: str) -> bool:
- if self.accept(s):
- return True
- raise ParseException('Expecting %s got %s.' % (s, self.current.tid), self.getline(), self.current.lineno, self.current.colno)
- def block_expect(self, s: str, block_start: Token) -> bool:
- if self.accept(s):
- return True
- 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)
- def parse(self) -> CodeBlockNode:
- block = self.codeblock()
- self.expect('eof')
- return block
- def statement(self) -> BaseNode:
- return self.e1()
- def e1(self) -> BaseNode:
- left = self.e2()
- if self.accept('plusassign'):
- value = self.e1()
- if not isinstance(left, IdNode):
- raise ParseException('Plusassignment target must be an id.', self.getline(), left.lineno, left.colno)
- assert isinstance(left.value, str)
- return PlusAssignmentNode(left.filename, left.lineno, left.colno, left.value, value)
- elif self.accept('assign'):
- value = self.e1()
- if not isinstance(left, IdNode):
- raise ParseException('Assignment target must be an id.',
- self.getline(), left.lineno, left.colno)
- assert isinstance(left.value, str)
- return AssignmentNode(left.filename, left.lineno, left.colno, left.value, value)
- elif self.accept('questionmark'):
- if self.in_ternary:
- raise ParseException('Nested ternary operators are not allowed.',
- self.getline(), left.lineno, left.colno)
- self.in_ternary = True
- trueblock = self.e1()
- self.expect('colon')
- falseblock = self.e1()
- self.in_ternary = False
- return TernaryNode(left, trueblock, falseblock)
- return left
- def e2(self) -> BaseNode:
- left = self.e3()
- while self.accept('or'):
- if isinstance(left, EmptyNode):
- raise ParseException('Invalid or clause.',
- self.getline(), left.lineno, left.colno)
- left = OrNode(left, self.e3())
- return left
- def e3(self) -> BaseNode:
- left = self.e4()
- while self.accept('and'):
- if isinstance(left, EmptyNode):
- raise ParseException('Invalid and clause.',
- self.getline(), left.lineno, left.colno)
- left = AndNode(left, self.e4())
- return left
- def e4(self) -> BaseNode:
- left = self.e5()
- for nodename, operator_type in comparison_map.items():
- if self.accept(nodename):
- return ComparisonNode(operator_type, left, self.e5())
- if self.accept('not') and self.accept('in'):
- return ComparisonNode('notin', left, self.e5())
- return left
- def e5(self) -> BaseNode:
- return self.e5addsub()
- def e5addsub(self) -> BaseNode:
- op_map = {
- 'plus': 'add',
- 'dash': 'sub',
- }
- left = self.e5muldiv()
- while True:
- op = self.accept_any(tuple(op_map.keys()))
- if op:
- left = ArithmeticNode(op_map[op], left, self.e5muldiv())
- else:
- break
- return left
- def e5muldiv(self) -> BaseNode:
- op_map = {
- 'percent': 'mod',
- 'star': 'mul',
- 'fslash': 'div',
- }
- left = self.e6()
- while True:
- op = self.accept_any(tuple(op_map.keys()))
- if op:
- left = ArithmeticNode(op_map[op], left, self.e6())
- else:
- break
- return left
- def e6(self) -> BaseNode:
- if self.accept('not'):
- return NotNode(self.current, self.e7())
- if self.accept('dash'):
- return UMinusNode(self.current, self.e7())
- return self.e7()
- def e7(self) -> BaseNode:
- left = self.e8()
- block_start = self.current
- if self.accept('lparen'):
- args = self.args()
- self.block_expect('rparen', block_start)
- if not isinstance(left, IdNode):
- raise ParseException('Function call must be applied to plain id',
- self.getline(), left.lineno, left.colno)
- assert isinstance(left.value, str)
- left = FunctionNode(left.filename, left.lineno, left.colno, self.current.lineno, self.current.colno, left.value, args)
- go_again = True
- while go_again:
- go_again = False
- if self.accept('dot'):
- go_again = True
- left = self.method_call(left)
- if self.accept('lbracket'):
- go_again = True
- left = self.index_call(left)
- return left
- def e8(self) -> BaseNode:
- block_start = self.current
- if self.accept('lparen'):
- e = self.statement()
- self.block_expect('rparen', block_start)
- return e
- elif self.accept('lbracket'):
- args = self.args()
- self.block_expect('rbracket', block_start)
- return ArrayNode(args, block_start.lineno, block_start.colno, self.current.lineno, self.current.colno)
- elif self.accept('lcurl'):
- key_values = self.key_values()
- self.block_expect('rcurl', block_start)
- return DictNode(key_values, block_start.lineno, block_start.colno, self.current.lineno, self.current.colno)
- else:
- return self.e9()
- def e9(self) -> BaseNode:
- t = self.current
- if self.accept('true'):
- t.value = True
- return BooleanNode(t)
- if self.accept('false'):
- t.value = False
- return BooleanNode(t)
- if self.accept('id'):
- return IdNode(t)
- if self.accept('number'):
- return NumberNode(t)
- if self.accept('string'):
- return StringNode(t)
- return EmptyNode(self.current.lineno, self.current.colno, self.current.filename)
- def key_values(self) -> ArgumentNode:
- s = self.statement() # type: BaseNode
- a = ArgumentNode(self.current)
- while not isinstance(s, EmptyNode):
- if self.accept('colon'):
- a.set_kwarg_no_check(s, self.statement())
- potential = self.current
- if not self.accept('comma'):
- return a
- a.commas.append(potential)
- else:
- raise ParseException('Only key:value pairs are valid in dict construction.',
- self.getline(), s.lineno, s.colno)
- s = self.statement()
- return a
- def args(self) -> ArgumentNode:
- s = self.statement() # type: BaseNode
- a = ArgumentNode(self.current)
- while not isinstance(s, EmptyNode):
- potential = self.current
- if self.accept('comma'):
- a.commas.append(potential)
- a.append(s)
- elif self.accept('colon'):
- if not isinstance(s, IdNode):
- raise ParseException('Dictionary key must be a plain identifier.',
- self.getline(), s.lineno, s.colno)
- a.set_kwarg(s, self.statement())
- potential = self.current
- if not self.accept('comma'):
- return a
- a.commas.append(potential)
- else:
- a.append(s)
- return a
- s = self.statement()
- return a
- def method_call(self, source_object) -> MethodNode:
- methodname = self.e9()
- if not(isinstance(methodname, IdNode)):
- raise ParseException('Method name must be plain id',
- self.getline(), self.current.lineno, self.current.colno)
- assert isinstance(methodname.value, str)
- self.expect('lparen')
- args = self.args()
- self.expect('rparen')
- method = MethodNode(methodname.filename, methodname.lineno, methodname.colno, source_object, methodname.value, args)
- if self.accept('dot'):
- return self.method_call(method)
- return method
- def index_call(self, source_object) -> IndexNode:
- index_statement = self.statement()
- self.expect('rbracket')
- return IndexNode(source_object, index_statement)
- def foreachblock(self) -> ForeachClauseNode:
- t = self.current
- self.expect('id')
- assert isinstance(t.value, str)
- varname = t
- varnames = [t.value] # type: T.List[str]
- if self.accept('comma'):
- t = self.current
- self.expect('id')
- assert isinstance(t.value, str)
- varnames.append(t.value)
- self.expect('colon')
- items = self.statement()
- block = self.codeblock()
- return ForeachClauseNode(varname, varnames, items, block)
- def ifblock(self) -> IfClauseNode:
- condition = self.statement()
- clause = IfClauseNode(condition)
- self.expect('eol')
- block = self.codeblock()
- clause.ifs.append(IfNode(clause, condition, block))
- self.elseifblock(clause)
- elseblock = self.elseblock()
- if elseblock:
- clause.elseblock = elseblock
- return clause
- def elseifblock(self, clause) -> None:
- while self.accept('elif'):
- s = self.statement()
- self.expect('eol')
- b = self.codeblock()
- clause.ifs.append(IfNode(s, s, b))
- def elseblock(self) -> T.Optional[CodeBlockNode]:
- if self.accept('else'):
- self.expect('eol')
- return self.codeblock()
- return None
- def line(self) -> BaseNode:
- block_start = self.current
- if self.current == 'eol':
- return EmptyNode(self.current.lineno, self.current.colno, self.current.filename)
- if self.accept('if'):
- ifblock = self.ifblock()
- self.block_expect('endif', block_start)
- return ifblock
- if self.accept('foreach'):
- forblock = self.foreachblock()
- self.block_expect('endforeach', block_start)
- return forblock
- if self.accept('continue'):
- return ContinueNode(self.current)
- if self.accept('break'):
- return BreakNode(self.current)
- return self.statement()
- def codeblock(self) -> CodeBlockNode:
- block = CodeBlockNode(self.current)
- cond = True
- while cond:
- curline = self.line()
- if not isinstance(curline, EmptyNode):
- block.lines.append(curline)
- cond = self.accept('eol')
- return block
|