Python packages does not allow for the use of hyphen as it's a reserved token. This change needs to be made to be imported by unit-tests in the future This change will require a new venv to be generated locally: `$ rm -rf venv` `$ ./setup-venv.sh` `$ source venv/bin/activate` Test: Run meson_to_hermetic/build-android/fuchsia-turnip.sh Bug: 360173803 Change-Id: Ie6fab4689ef5fc985e15747b0c06bfae662c387c
343 lines
12 KiB
Python
343 lines
12 KiB
Python
import re
|
|
import sys
|
|
|
|
from lark import Lark, Tree
|
|
from lark.visitors import Interpreter
|
|
|
|
# This grammar derived from:
|
|
# https://mesonbuild.com/Syntax.html#grammar
|
|
meson_grammar = r"""
|
|
?start: (statement | COMMENT | NEWLINE)*
|
|
|
|
?additive_expression: multiplicative_expression | (additive_expression additive_operator multiplicative_expression)
|
|
additive_operator: PLUS | MINUS
|
|
argument_list: positional_arguments [COMMA keyword_arguments] [COMMA] | keyword_arguments
|
|
array_literal: LBRACKET [expression_list] RBRACKET
|
|
?assignment_statement: assignment_expression
|
|
assignment_expression: expression assignment_operator expression
|
|
assignment_operator: EQUALS | PLUS_EQUALS
|
|
binary_literal: "0b" BINARY_NUMBER
|
|
BINARY_NUMBER: /[01]+/
|
|
boolean_literal: TRUE | FALSE
|
|
build_definition: (NEWLINE | statement)*
|
|
condition: expression
|
|
?conditional_expression: logical_or_expression | (logical_or_expression "?" expression ":" expression)
|
|
decimal_literal: DECIMAL_NUMBER
|
|
DECIMAL_NUMBER: /[0-9][0-9]*/
|
|
dictionary_literal: LBRACE [key_value_list] RBRACE
|
|
?equality_expression: relational_expression | (equality_expression equality_operator relational_expression)
|
|
equality_operator: DOUBLE_EQUAL | NOT_EQUAL
|
|
?expression: conditional_expression | logical_or_expression
|
|
expression_list: expression (COMMA expression)* COMMA?
|
|
?expression_statement: expression
|
|
?function_expression: id_expression LPAREN [argument_list] RPAREN
|
|
hex_literal: "0x" HEX_NUMBER
|
|
HEX_NUMBER: /[a-fA-F0-9]+/
|
|
id_expression: IDENTIFIER
|
|
IDENTIFIER: /[a-zA-Z_][a-zA-Z_0-9]*/
|
|
identifier_list: id_expression (COMMA id_expression)*
|
|
integer_literal: decimal_literal | octal_literal | hex_literal
|
|
iteration_statement: FOREACH identifier_list COLON expression NEWLINE (statement | jump_statement)* ENDFOREACH
|
|
jump_statement: (BREAK | CONTINUE) NEWLINE
|
|
key_value_item: expression COLON expression
|
|
key_value_list: key_value_item (COMMA key_value_item)* COMMA?
|
|
keyword_item: id_expression ":" expression
|
|
keyword_arguments: keyword_item (COMMA keyword_item)* COMMA?
|
|
?literal: integer_literal | string_literal | boolean_literal | array_literal | dictionary_literal
|
|
?logical_and_expression: equality_expression | (logical_and_expression AND ["\\"] equality_expression)
|
|
?logical_or_expression: logical_and_expression | (logical_or_expression OR ["\\"] logical_and_expression)
|
|
?method_expression: postfix_expression ["\\"] DOT function_expression
|
|
?multiplicative_expression: unary_expression | (multiplicative_expression multiplicative_operator unary_expression)
|
|
multiplicative_operator: ASTERISK | SLASH | PERCENT
|
|
octal_literal: "0o" OCTAL_NUMBER
|
|
OCTAL_NUMBER: /[0-7]+/
|
|
positional_arguments: expression (COMMA expression)*
|
|
postfix_expression: primary_expression | subscript_expression | function_expression | method_expression
|
|
?primary_expression: literal | (LPAREN expression RPAREN) | id_expression
|
|
?relational_expression: additive_expression | (relational_expression relational_operator additive_expression)
|
|
relational_operator: GREATER | LESSTHAN | GREATER_OR_EQUAL | LESSTHAN_OR_EQUAL | IN | (NOT IN)
|
|
selection_statement: IF condition NEWLINE (statement)* (ELIF condition NEWLINE (statement)*)* [ELSE NEWLINE (statement)*] ENDIF
|
|
statement: (expression_statement | selection_statement | iteration_statement | assignment_statement) NEWLINE
|
|
string_literal: STRING_SIMPLE_VALUE | STRING_MULTILINE_VALUE
|
|
?subscript_expression: postfix_expression LBRACKET expression RBRACKET
|
|
?unary_expression: postfix_expression | (unary_operator unary_expression)
|
|
unary_operator: NOT | DASH
|
|
|
|
AND: /and/
|
|
ASTERISK: /\*/
|
|
BREAK: /break/
|
|
CONTINUE: /continue/
|
|
COLON: /:/
|
|
COMMA: /,/
|
|
DASH: /-/
|
|
DOT: /\./
|
|
DOUBLE_EQUAL: /==/
|
|
EQUALS: /=/
|
|
FOREACH: /foreach/
|
|
GREATER: />/
|
|
GREATER_OR_EQUAL: />=/
|
|
# Raise priorities to avoid elif parsed as a statement
|
|
ELIF.1: /elif/
|
|
ELSE.1: /else/
|
|
ENDIF.1: /endif/
|
|
ENDFOREACH: /endforeach/
|
|
FALSE: /false/
|
|
IF: /if /
|
|
IN: / in /
|
|
LBRACKET: /\[/
|
|
NOT: /not /
|
|
NOT_EQUAL: /!=/
|
|
RBRACKET: /\]/
|
|
LESSTHAN: /</
|
|
LESSTHAN_OR_EQUAL: /<=/
|
|
LBRACE: /{/
|
|
LPAREN: /\(/
|
|
RBRACE: /}/
|
|
RPAREN: /\)/
|
|
OR: /or/
|
|
PERCENT: /%/
|
|
PLUS: /\+/
|
|
MINUS: /-/
|
|
PLUS_EQUALS: /\+=/
|
|
NEWLINE: ( / *\r?\n/ | COMMENT )+
|
|
COMMENT: / *\#.*\n/
|
|
SLASH: /\//
|
|
STRING_SIMPLE_VALUE: /f?'(.*\\')*.*?'/
|
|
STRING_MULTILINE_VALUE: /f?'''.*?'''/s
|
|
TRUE: /true/
|
|
|
|
%import common.WS
|
|
|
|
%ignore WS
|
|
# Comments would be nice to keep, but parsing fails end-of-line comments
|
|
%ignore COMMENT
|
|
"""
|
|
|
|
|
|
class TreeToCode(Interpreter):
|
|
indent = ''
|
|
|
|
def statement(self, tree):
|
|
string = ''
|
|
for child in tree.children:
|
|
if isinstance(child, Tree):
|
|
string += self.visit(child)
|
|
elif child is not None:
|
|
string += child
|
|
return self.indent + string
|
|
|
|
def more_indent(self):
|
|
self.indent += ' '
|
|
|
|
def less_indent(self):
|
|
self.indent = self.indent[0 : len(self.indent) - 2]
|
|
|
|
# Ensure spaces around 'and'
|
|
def logical_and_expression(self, tree):
|
|
assert len(tree.children) == 3
|
|
lhs = self.visit(tree.children[0])
|
|
rhs = self.visit(tree.children[2])
|
|
return lhs + ' and ' + rhs
|
|
|
|
# Ensure spaces around 'or'
|
|
def logical_or_expression(self, tree):
|
|
assert len(tree.children) == 3
|
|
lhs = self.visit(tree.children[0])
|
|
rhs = self.visit(tree.children[2])
|
|
return lhs + ' or ' + rhs
|
|
|
|
# A ? B : C becomes B if A else C
|
|
def conditional_expression(self, tree):
|
|
assert len(tree.children) == 3
|
|
expr = self.visit(tree.children[0])
|
|
first = self.visit(tree.children[1])
|
|
second = self.visit(tree.children[2])
|
|
return first + ' if ' + expr + ' else ' + second
|
|
|
|
def assignment_expression(self, tree):
|
|
assert len(tree.children) == 3
|
|
lhs = self.visit(tree.children[0])
|
|
operator = self.visit(tree.children[1])
|
|
rhs = self.visit(tree.children[2])
|
|
if operator == '+=' and rhs.startswith('{'):
|
|
# Convert += to |= for dictionaries
|
|
return lhs + ' |= ' + rhs
|
|
elif operator == '+=' and rhs.startswith("'"):
|
|
# Handle literal string append to list or string
|
|
return (
|
|
lhs
|
|
+ ' += '
|
|
+ '['
|
|
+ rhs
|
|
+ '] if isinstance('
|
|
+ lhs
|
|
+ ', list) else '
|
|
+ rhs
|
|
)
|
|
return lhs + operator + rhs
|
|
|
|
def iteration_statement(self, tree):
|
|
# foreach = tree.children[0]
|
|
identifier_list = self.visit(tree.children[1])
|
|
# colon = tree.children[2]
|
|
id_expression = self.visit(tree.children[3])
|
|
# newline = tree.children[4]
|
|
string = 'for ' + identifier_list + ' in ' + id_expression
|
|
string += (
|
|
'.items():\n' if re.search(r',', identifier_list) is not None else ':\n'
|
|
)
|
|
self.more_indent()
|
|
lastindex = len(tree.children) - 1
|
|
for child in tree.children[5:lastindex]:
|
|
if isinstance(child, Tree):
|
|
string += self.visit(child)
|
|
elif child is not None:
|
|
string += child
|
|
self.less_indent()
|
|
return string
|
|
|
|
def selection_statement(self, tree):
|
|
string = ''
|
|
index = 0
|
|
while index < len(tree.children):
|
|
prefix = tree.children[index]
|
|
index = index + 1
|
|
if prefix is None:
|
|
continue
|
|
if isinstance(prefix, Tree):
|
|
exit('unexpected prefix: ' + prefix.pretty())
|
|
if re.match(r' *endif', prefix) is not None:
|
|
break
|
|
|
|
if re.match(r'if', prefix) is not None:
|
|
condition = self.visit(tree.children[index])
|
|
index += 1
|
|
# Skip indent here because all statements are prepended with the indentation
|
|
string += 'if ' + condition + ':\n'
|
|
elif re.match(r'elif', prefix) is not None:
|
|
condition = self.visit(tree.children[index])
|
|
index = index + 1
|
|
string += self.indent + 'elif ' + condition + ':\n'
|
|
elif re.match(r'else', prefix) is not None:
|
|
string += self.indent + 'else:\n'
|
|
else:
|
|
exit('Not a prefix: ' + prefix)
|
|
|
|
# newline = tree.children[index]
|
|
index += 1
|
|
|
|
statement_count = 0
|
|
self.more_indent()
|
|
while index < len(tree.children):
|
|
statement = tree.children[index]
|
|
if not isinstance(statement, Tree):
|
|
break
|
|
string += self.visit(statement)
|
|
index = index + 1
|
|
statement_count = statement_count + 1
|
|
if statement_count == 0:
|
|
string += self.indent + 'noop()\n'
|
|
self.less_indent()
|
|
|
|
return string
|
|
|
|
def postfix_expression(self, tree):
|
|
string = ''
|
|
for child in tree.children:
|
|
if isinstance(child, Tree):
|
|
subtree = self.visit(child)
|
|
subtree = re.sub(r'(.+)\.to_int\(\)', r'int(\g<1>)', subtree)
|
|
subtree = re.sub(r'(.+)\.to_string\(\)', r'str(\g<1>)', subtree)
|
|
subtree = re.sub(r'(.+)\.length\(\)', r'len(\g<1>)', subtree)
|
|
subtree = re.sub(r'(.+)\.to_upper\(\)', r'\g<1>.upper()', subtree)
|
|
subtree = re.sub(
|
|
r'(.+)\.underscorify\(\)',
|
|
r"\g<1>.replace('.', '_').replace('/', '_')",
|
|
subtree,
|
|
)
|
|
string += subtree
|
|
elif child is not None:
|
|
string += child
|
|
return string
|
|
|
|
def function_expression(self, tree):
|
|
assert len(tree.children) == 4
|
|
identifier = self.visit(tree.children[0])
|
|
if identifier == 'import':
|
|
identifier = 'module_import'
|
|
lparen = tree.children[1]
|
|
args = (
|
|
self.visit(tree.children[2]) if isinstance(tree.children[2], Tree) else ''
|
|
)
|
|
rparen = tree.children[3]
|
|
if identifier == 'contains':
|
|
return 'count' + lparen + args + rparen + ' > 0'
|
|
return identifier + lparen + args + rparen
|
|
|
|
def multiplicative_expression(self, tree):
|
|
assert len(tree.children) == 3
|
|
lhs = self.visit(tree.children[0])
|
|
operator = self.visit(tree.children[1])
|
|
rhs = self.visit(tree.children[2])
|
|
# Slash used mostly to concatenate strings
|
|
if operator == '/':
|
|
return (
|
|
'('
|
|
+ lhs
|
|
+ ' + '
|
|
+ rhs
|
|
+ ') if isinstance('
|
|
+ lhs
|
|
+ ', str) else ('
|
|
+ lhs
|
|
+ ' / '
|
|
+ rhs
|
|
+ ')'
|
|
)
|
|
return lhs + operator + rhs
|
|
|
|
# Switch from colon to equals
|
|
def keyword_item(self, tree):
|
|
id_ = self.visit(tree.children[0])
|
|
args = self.visit(tree.children[1])
|
|
return id_ + '=' + args
|
|
|
|
def boolean_literal(self, tree):
|
|
assert len(tree.children) == 1
|
|
value = tree.children[0]
|
|
if value == 'true':
|
|
return 'True'
|
|
elif value == 'false':
|
|
return 'False'
|
|
exit('Unhandled value: ' + value)
|
|
|
|
def string_literal(self, tree):
|
|
assert len(tree.children) == 1
|
|
string = tree.children[0]
|
|
string = re.sub(r'(@[0-9]@)', r'{}', string)
|
|
if string.startswith('f'):
|
|
string = re.sub(r'(@(.+)@)', r'{\g<2>}', string)
|
|
return string
|
|
|
|
def __default__(self, tree):
|
|
string = ''
|
|
for child in tree.children:
|
|
if isinstance(child, Tree):
|
|
string += self.visit(child)
|
|
elif child is not None:
|
|
string += child
|
|
return string
|
|
|
|
|
|
# Converts the given file from meson to python and returns the content as a string
|
|
def meson2python(file_name):
|
|
meson_parser = Lark(meson_grammar, parser='earley')
|
|
with open(file_name) as f:
|
|
# Ensure newline before end of file
|
|
tree = meson_parser.parse(f.read() + '\n')
|
|
code = TreeToCode().visit(tree)
|
|
return code
|
|
|
|
|
|
if __name__ == '__main__':
|
|
meson2python(sys.argv[1])
|