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.
 
 
 
 
 
 

468 lines
14 KiB

#!/usr/bin/python3
# Copyright 2014 Jussi Pakkanen
# 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 sys
class ParseException(Exception):
def __init__(self, text, lineno, colno):
super().__init__()
self.text = text
self.lineno = lineno
self.colno = colno
class Token:
def __init__(self, tid, lineno, colno, value):
self.tid = tid
self.lineno = lineno
self.colno = colno
self.value = value
def __eq__(self, other):
if isinstance(other, str):
return self.tid == other
return self.tid == other.tid
class Lexer:
def __init__(self):
self.keywords = {'true', 'false', 'if', 'else', 'elif',
'endif', 'and', 'or', 'not'}
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'\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'\]')),
('string', re.compile("'[^']*?'")),
('comma', re.compile(r',')),
('dot', re.compile(r'\.')),
('colon', re.compile(r':')),
('equal', re.compile(r'==')),
('nequal', re.compile(r'\!=')),
('assign', re.compile(r'=')),
]
def lex(self, code):
lineno = 1
line_start = 0
loc = 0;
par_count = 0
bracket_count = 0
col = 0
while(loc < len(code)):
matched = False
value = None
for (tid, reg) in self.token_specification:
mo = reg.match(code, loc)
if mo:
curline = lineno
col = mo.start()-line_start
matched = True
loc = mo.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 == 'string':
value = match_text[1:-1]
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)
elif tid == 'eol' or tid == 'eol_cont':
lineno += 1
line_start = loc
if par_count > 0 or bracket_count > 0:
break
elif tid == 'id':
if match_text in self.keywords:
tid = match_text
else:
value = match_text
yield Token(tid, curline, col, value)
if not matched:
raise ParseException('lexer', lineno, col)
class BooleanNode:
def __init__(self, token, value):
self.lineno = token.lineno
self.colno = token.colno
self.value = value
assert(isinstance(self.value, bool))
class IdNode:
def __init__(self, token):
self.lineno = token.lineno
self.colno = token.colno
self.value = token.value
assert(isinstance(self.value, str))
class NumberNode:
def __init__(self, token):
self.lineno = token.lineno
self.colno = token.colno
self.value = token.value
assert(isinstance(self.value, int))
class StringNode:
def __init__(self, token):
self.lineno = token.lineno
self.colno = token.colno
self.value = token.value
assert(isinstance(self.value, str))
class ArrayNode:
def __init__(self, args):
self.lineno = args.lineno
self.colno = args.colno
self.args = args
class EmptyNode:
def __init__(self):
self.lineno = 0
self.colno = 0
self.value = None
class OrNode:
def __init__(self, lineno, colno, left, right):
self.lineno = lineno
self.colno = colno
self.left = left
self.right = right
class AndNode:
def __init__(self, lineno, colno, left, right):
self.lineno = lineno
self.colno = colno
self.left = left
self.right = right
class ComparisonNode:
def __init__(self, lineno, colno, ctype, left, right):
self.lineno = lineno
self.colno = colno
self.left = left
self.right = right
self.ctype = ctype
class NotNode:
def __init__(self, lineno, colno, value):
self.lineno = lineno
self.colno = colno
self.value = value
class CodeBlockNode:
def __init__(self, lineno, colno):
self.lineno = lineno
self.colno = colno
self.lines = []
class MethodNode:
def __init__(self, lineno, colno, source_object, name, args):
self.lineno = lineno
self.colno = colno
self.source_object = source_object
self.name = name
assert(isinstance(self.name, str))
self.args = args
class FunctionNode:
def __init__(self, lineno, colno, func_name, args):
self.lineno = lineno
self.colno = colno
self.func_name = func_name
assert(isinstance(func_name, str))
self.args = args
class AssignmentNode:
def __init__(self, lineno, colno, var_name, value):
self.lineno = lineno
self.colno = colno
self.var_name = var_name
assert(isinstance(var_name, str))
self.value = value
class IfClauseNode():
def __init__(self, lineno, colno):
self.lineno = lineno
self.colno = colno
self.ifs = []
self.elseblock = EmptyNode()
class IfNode():
def __init__(self, lineno, colno, condition, block):
self.lineno = lineno
self.colno = colno
self.condition = condition
self.block = block
class ArgumentNode():
def __init__(self, token):
self.lineno = token.lineno
self.colno = token.colno
self.arguments = []
self.kwargs = {}
self.order_error = False
def prepend(self, statement):
if not isinstance(statement, EmptyNode):
self.arguments = [statement] + self.arguments
def append(self, statement):
if not isinstance(statement, EmptyNode):
self.arguments = self.arguments + [statement]
def set_kwarg(self, name, value):
if self.num_args() > 0:
self.order_error = True
self.kwargs[name] = value
def num_args(self):
return len(self.arguments)
def num_kwargs(self):
return len(self.kwargs)
def incorrect_order(self):
return self.order_error
def __len__(self):
return self.num_args() # Fixme
# 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 equality
# comparison, plus and multiplication would go here
# 5 negation
# 6 funcall, method call
# 7 parentheses
# 8 plain token
class Parser:
def __init__(self, code):
self.stream = Lexer().lex(code)
self.getsym()
def getsym(self):
try:
self.current = next(self.stream)
except StopIteration:
self.current = Token('eof', 0, 0, None)
def accept(self, s):
if self.current.tid == s:
self.getsym()
return True
return False
def expect(self, s):
if self.accept(s):
return True
raise ParseException('Expecting %s got %s.' % (s, self.current.tid), self.current.lineno, self.current.colno)
def parse(self):
block = self.codeblock()
self.expect('eof')
return block
def statement(self):
return self.e1()
def e1(self):
left = self.e2()
if self.accept('assign'):
value = self.e1()
if not isinstance(left, IdNode):
raise ParseException('Assignment target must be an id.',
left.lineno, left.colno)
return AssignmentNode(left.lineno, left.colno, left.value, value)
return left
def e2(self):
left = self.e3()
if self.accept('or'):
return OrNode(left.lineno, left.colno, left, self.e3())
return left
def e3(self):
left = self.e4()
if self.accept('and'):
return AndNode(left.lineno, left.colno, left, self.e4())
return left
def e4(self):
left = self.e5()
if self.accept('equal'):
return ComparisonNode(left.lineno, left.colno, '==', left, self.e5())
if self.accept('nequal'):
return ComparisonNode(left.lineno, left.colno, '!=', left, self.e5())
return left
def e5(self):
if self.accept('not'):
return NotNode(self.current.lineno, self.current.colno, self.e6())
return self.e6()
def e6(self):
left = self.e7()
if self.accept('dot'):
return self.method_call(left)
elif self.accept('lparen'):
args = self.args()
self.expect('rparen')
if not isinstance(left, IdNode):
raise ParseException('Function call must be applied to plain id',
left.lineno, left.colno)
return FunctionNode(left.lineno, left.colno, left.value, args)
return left
def e7(self):
if self.accept('lparen'):
e = self.statement()
self.expect('rparen')
return e
elif self.accept('lbracket'):
args = self.args()
self.expect('rbracket')
return ArrayNode(args)
else:
return self.e8()
def e8(self):
t = self.current
if self.accept('true'):
return BooleanNode(t, True);
if self.accept('false'):
return BooleanNode(t, False)
if self.accept('id'):
return IdNode(t)
if self.accept('number'):
return NumberNode(t)
if self.accept('string'):
return StringNode(t)
return EmptyNode()
def args(self):
s = self.statement()
if isinstance(s, EmptyNode):
ArgumentNode(s)
if self.accept('comma'):
rest = self.args()
rest.prepend(s)
return rest
if self.accept('colon'):
if not isinstance(s, IdNode):
raise ParseException('Keyword argument must be a plain identifier.',
s.lineno, s.colno)
value = self.statement()
if self.accept('comma'):
a = self.args()
else:
a = ArgumentNode(self.current)
a.set_kwarg(s.value, value)
return a
a = ArgumentNode(self.current)
a.append(s)
return a
def method_call(self, source_object):
methodname = self.e8()
if not(isinstance(methodname, IdNode)):
raise ParseException('Method name must be plain id',
self.current.lineno, self.current.colno)
self.expect('lparen')
args = self.args()
self.expect('rparen')
method = MethodNode(methodname.lineno, methodname.colno, source_object, methodname.value, args)
if self.accept('dot'):
return self.method_call(method)
return method
def ifblock(self):
condition = self.statement()
clause = IfClauseNode(condition.lineno, condition.colno)
block = self.codeblock()
clause.ifs.append(IfNode(clause.lineno, clause.colno, condition, block))
self.elseifblock(clause)
clause.elseblock = self.elseblock()
return clause
def elseifblock(self, clause):
while self.accept('elif'):
s = self.statement()
self.expect('eol')
b = self.codeblock()
clause.ifs.append(IfNode(s.lineno, s.colno, s, b))
def elseblock(self):
if self.accept('else'):
self.expect('eol')
return self.codeblock()
def line(self):
if self.current == 'eol':
return EmptyNode()
if self.accept('if'):
block = self.ifblock()
self.expect('endif')
return block
return self.statement()
def codeblock(self):
block = CodeBlockNode(self.current.lineno, self.current.colno)
cond = True
while cond:
curline = self.line()
if not isinstance(curline, EmptyNode):
block.lines.append(curline)
cond = self.accept('eol')
return block
if __name__ == '__main__':
for code in sys.argv[1:]:
parser = Parser(open(code).read())
try:
print(code)
parser.parse()
except ParseException as e:
print('Error', e.text, 'line', e.lineno, 'column', e.colno)