The Meson Build System http://mesonbuild.com/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

818 lines
32 KiB

# 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.
from dataclasses import dataclass
import re
import codecs
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: str) -> None:
super().__init__(match)
self.match = match
def decode_match(match: T.Match[str]) -> str:
try:
return codecs.decode(match.group(0).encode(), 'unicode_escape')
except UnicodeDecodeError:
raise MesonUnicodeDecodeError(match.group(0))
class ParseException(MesonException):
def __init__(self, text: str, line: str, lineno: int, colno: int) -> None:
# Format as error message, followed by the line with the error, followed by a caret to show the error column.
super().__init__("{}\n{}\n{}".format(text, line, '{}^'.format(' ' * colno)))
self.lineno = lineno
self.colno = colno
class BlockParseException(MesonException):
def __init__(
self,
text: str,
line: str,
lineno: int,
colno: int,
start_line: str,
start_lineno: int,
start_colno: int,
) -> None:
# 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__("{}\n{}\n{}".format(text, line, '{}^{}^'.format(' ' * 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)
@dataclass(eq=False)
class Token(T.Generic[TV_TokenTypes]):
tid: str
filename: str
line_start: int
lineno: int
colno: int
bytespan: T.Tuple[int, int]
value: TV_TokenTypes
def __eq__(self, other: object) -> bool:
if isinstance(other, str):
return self.tid == other
elif isinstance(other, Token):
return self.tid == other.tid
return NotImplemented
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]')),
('fstring', re.compile(r"f'([^'\\]|(\\.))*'")),
('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 in {'string', 'fstring'}:
# Handle here and not on the regexp to give a better error message.
if match_text.find("\n") != -1:
msg = ParseException("Newline character in a string detected, use ''' (three single quotes) "
"for multiline strings instead.\n"
"This will become a hard error in a future Meson release.",
self.getline(line_start), lineno, col)
mlog.warning(msg, location=BaseNode(lineno, col, filename))
value = match_text[2 if tid == 'fstring' else 1:-1]
try:
value = ESCAPE_SEQUENCE_SINGLE_RE.sub(decode_match, value)
except MesonUnicodeDecodeError as err:
raise MesonException(f"Failed to parse escape sequence: '{err.match}' in string:\n {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(f"Identifier '{match_text}' will become a reserved keyword in a future release. Please rename it.",
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)
@dataclass(eq=False)
class BaseNode:
lineno: int
colno: int
filename: str
end_lineno: T.Optional[int] = None
end_colno: T.Optional[int] = None
def __post_init__(self) -> None:
if self.end_lineno is None:
self.end_lineno = self.lineno
if self.end_colno is None:
self.end_colno = 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) -> str:
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) -> str:
return "String node: '%s' (%d, %d)." % (self.value, self.lineno, self.colno)
class FormatStringNode(ElementaryNode[str]):
def __init__(self, token: Token[str]):
super().__init__(token)
assert isinstance(self.value, str)
def __str__(self) -> str:
return "Format string node: '{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 any((isinstance(x, IdNode) and name.value == x.value) for x in self.kwargs):
mlog.warning(f'Keyword argument "{name.value}" defined multiple times.', 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 = None # 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(f'Expecting {s} got {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(f'Expecting {s} got {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)
if self.accept('fstring'):
return FormatStringNode(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: BaseNode) -> 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: BaseNode) -> 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)
clause.elseblock = self.elseblock()
return clause
def elseifblock(self, clause: IfClauseNode) -> 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.Union[CodeBlockNode, EmptyNode]:
if self.accept('else'):
self.expect('eol')
return self.codeblock()
return EmptyNode(self.current.lineno, self.current.colno, self.current.filename)
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