blob: e75b28a3faa7fc6486f1b8c5a9527a94fb2448cc [file] [log] [blame]
#!/usr/bin/env python
# Copyright 2014 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Generates a syntax tree from a Mojo IDL file."""
import imp
import os.path
import sys
# Disable lint check for finding modules:
# pylint: disable=F0401
def _GetDirAbove(dirname):
"""Returns the directory "above" this file containing |dirname| (which must
also be "above" this file)."""
path = os.path.abspath(__file__)
while True:
path, tail = os.path.split(path)
assert tail
if tail == dirname:
return path
try:
imp.find_module("ply")
except ImportError:
sys.path.append(os.path.join(_GetDirAbove("mojo"), "third_party"))
from ply import lex
from ply import yacc
from ..error import Error
import ast
from lexer import Lexer
_MAX_ORDINAL_VALUE = 0xffffffff
def _ListFromConcat(*items):
"""Generate list by concatenating inputs (note: only concatenates lists, not
tuples or other iterables)."""
itemsout = []
for item in items:
if item is None:
continue
if type(item) is not type([]):
itemsout.append(item)
else:
itemsout.extend(item)
return itemsout
# Disable lint check for exceptions deriving from Exception:
# pylint: disable=W0710
class ParseError(Error):
"""Class for errors from the parser."""
def __init__(self, filename, message, lineno=None, snippet=None):
Error.__init__(self, filename, message, lineno=lineno,
addenda=([snippet] if snippet else None))
# We have methods which look like they could be functions:
# pylint: disable=R0201
class Parser(object):
def __init__(self, lexer, source, filename):
self.tokens = lexer.tokens
self.source = source
self.filename = filename
def p_root(self, p):
"""root : import root
| module
| definitions"""
if len(p) > 2:
p[0] = _ListFromConcat(p[1], p[2])
else:
# Generator expects a module. If one wasn't specified insert one with an
# empty name.
if p[1][0] != 'MODULE':
p[0] = [('MODULE', '', p[1])]
else:
p[0] = [p[1]]
def p_import(self, p):
"""import : IMPORT STRING_LITERAL"""
# 'eval' the literal to strip the quotes.
p[0] = ('IMPORT', eval(p[2]))
def p_module(self, p):
"""module : MODULE identifier LBRACE definitions RBRACE"""
p[0] = ('MODULE', p[2], p[4])
def p_definitions(self, p):
"""definitions : definition definitions
| """
if len(p) > 1:
p[0] = _ListFromConcat(p[1], p[2])
def p_definition(self, p):
"""definition : struct
| interface
| enum"""
p[0] = p[1]
def p_attribute_section(self, p):
"""attribute_section : LBRACKET attributes RBRACKET
| """
if len(p) > 3:
p[0] = p[2]
def p_attributes(self, p):
"""attributes : attribute
| attribute COMMA attributes
| """
if len(p) == 2:
p[0] = _ListFromConcat(p[1])
elif len(p) > 3:
p[0] = _ListFromConcat(p[1], p[3])
def p_attribute(self, p):
"""attribute : NAME EQUALS expression
| NAME EQUALS NAME"""
p[0] = ('ATTRIBUTE', p[1], p[3])
def p_struct(self, p):
"""struct : attribute_section STRUCT NAME LBRACE struct_body RBRACE SEMI"""
p[0] = ('STRUCT', p[3], p[1], p[5])
def p_struct_body(self, p):
"""struct_body : field struct_body
| enum struct_body
| """
if len(p) > 1:
p[0] = _ListFromConcat(p[1], p[2])
def p_field(self, p):
"""field : typename NAME default ordinal SEMI"""
p[0] = ('FIELD', p[1], p[2], p[4], p[3])
def p_default(self, p):
"""default : EQUALS expression
| EQUALS expression_object
| """
if len(p) > 2:
p[0] = p[2]
def p_interface(self, p):
"""interface : attribute_section INTERFACE NAME LBRACE interface_body \
RBRACE SEMI"""
p[0] = ('INTERFACE', p[3], p[1], p[5])
def p_interface_body(self, p):
"""interface_body : method interface_body
| enum interface_body
| """
if len(p) > 1:
p[0] = _ListFromConcat(p[1], p[2])
def p_response(self, p):
"""response : RESPONSE LPAREN parameters RPAREN
| """
if len(p) > 3:
p[0] = p[3]
def p_method(self, p):
"""method : NAME ordinal LPAREN parameters RPAREN response SEMI"""
p[0] = ('METHOD', p[1], p[4], p[2], p[6])
def p_parameters(self, p):
"""parameters : parameter
| parameter COMMA parameters
| """
if len(p) == 1:
p[0] = []
elif len(p) == 2:
p[0] = _ListFromConcat(p[1])
elif len(p) > 3:
p[0] = _ListFromConcat(p[1], p[3])
def p_parameter(self, p):
"""parameter : typename NAME ordinal"""
p[0] = ('PARAM', p[1], p[2], p[3])
def p_typename(self, p):
"""typename : basictypename
| array"""
p[0] = p[1]
def p_basictypename(self, p):
"""basictypename : identifier
| HANDLE
| specializedhandle"""
p[0] = p[1]
def p_specializedhandle(self, p):
"""specializedhandle : HANDLE LANGLE specializedhandlename RANGLE"""
p[0] = "handle<" + p[3] + ">"
def p_specializedhandlename(self, p):
"""specializedhandlename : DATA_PIPE_CONSUMER
| DATA_PIPE_PRODUCER
| MESSAGE_PIPE
| SHARED_BUFFER"""
p[0] = p[1]
def p_array(self, p):
"""array : typename LBRACKET RBRACKET"""
p[0] = p[1] + "[]"
def p_ordinal(self, p):
"""ordinal : ORDINAL
| """
if len(p) > 1:
value = int(p[1][1:])
if value > _MAX_ORDINAL_VALUE:
raise ParseError(self.filename, "Ordinal value %d too large:" % value,
lineno=p.lineno(1),
snippet=self._GetSnippet(p.lineno(1)))
p[0] = ast.Ordinal(value, filename=self.filename, lineno=p.lineno(1))
else:
p[0] = ast.Ordinal(None)
def p_enum(self, p):
"""enum : ENUM NAME LBRACE enum_fields RBRACE SEMI"""
p[0] = ('ENUM', p[2], p[4])
def p_enum_fields(self, p):
"""enum_fields : enum_field
| enum_field COMMA enum_fields
| """
if len(p) == 2:
p[0] = _ListFromConcat(p[1])
elif len(p) > 3:
p[0] = _ListFromConcat(p[1], p[3])
def p_enum_field(self, p):
"""enum_field : NAME
| NAME EQUALS expression"""
if len(p) == 2:
p[0] = ('ENUM_FIELD', p[1], None)
else:
p[0] = ('ENUM_FIELD', p[1], p[3])
### Expressions ###
def p_expression_object(self, p):
"""expression_object : expression_array
| LBRACE expression_object_elements RBRACE """
if len(p) < 3:
p[0] = p[1]
else:
p[0] = ('OBJECT', p[2])
def p_expression_object_elements(self, p):
"""expression_object_elements : expression_object
| expression_object COMMA expression_object_elements
| """
if len(p) == 2:
p[0] = _ListFromConcat(p[1])
elif len(p) > 3:
p[0] = _ListFromConcat(p[1], p[3])
def p_expression_array(self, p):
"""expression_array : expression
| LBRACKET expression_array_elements RBRACKET """
if len(p) < 3:
p[0] = p[1]
else:
p[0] = ('ARRAY', p[2])
def p_expression_array_elements(self, p):
"""expression_array_elements : expression_object
| expression_object COMMA expression_array_elements
| """
if len(p) == 2:
p[0] = _ListFromConcat(p[1])
elif len(p) > 3:
p[0] = _ListFromConcat(p[1], p[3])
# TODO(vtl): This is now largely redundant.
def p_expression(self, p):
"""expression : binary_expression"""
p[0] = ('EXPRESSION', p[1])
# PLY lets us specify precedence of operators, but since we don't actually
# evaluate them, we don't need that here.
# TODO(vtl): We're going to need to evaluate them.
def p_binary_expression(self, p):
"""binary_expression : unary_expression
| binary_expression binary_operator \
binary_expression"""
p[0] = _ListFromConcat(*p[1:])
def p_binary_operator(self, p):
"""binary_operator : TIMES
| DIVIDE
| MOD
| PLUS
| MINUS
| RSHIFT
| LSHIFT
| AND
| OR
| XOR"""
p[0] = p[1]
def p_unary_expression(self, p):
"""unary_expression : primary_expression
| unary_operator expression"""
p[0] = _ListFromConcat(*p[1:])
def p_unary_operator(self, p):
"""unary_operator : PLUS
| MINUS
| NOT"""
p[0] = p[1]
def p_primary_expression(self, p):
"""primary_expression : constant
| identifier
| LPAREN expression RPAREN"""
p[0] = _ListFromConcat(*p[1:])
def p_identifier(self, p):
"""identifier : NAME
| NAME DOT identifier"""
p[0] = ''.join(p[1:])
def p_constant(self, p):
"""constant : INT_CONST_DEC
| INT_CONST_OCT
| INT_CONST_HEX
| FLOAT_CONST
| CHAR_CONST
| STRING_LITERAL"""
p[0] = _ListFromConcat(*p[1:])
def p_error(self, e):
if e is None:
# Unexpected EOF.
# TODO(vtl): Can we figure out what's missing?
raise ParseError(self.filename, "Unexpected end of file")
raise ParseError(self.filename, "Unexpected %r:" % e.value, lineno=e.lineno,
snippet=self._GetSnippet(e.lineno))
def _GetSnippet(self, lineno):
return self.source.split('\n')[lineno - 1]
def Parse(source, filename):
lexer = Lexer(filename)
parser = Parser(lexer, source, filename)
lex.lex(object=lexer)
yacc.yacc(module=parser, debug=0, write_tables=0)
tree = yacc.parse(source)
return tree
def main(argv):
if len(argv) < 2:
print "usage: %s filename" % argv[0]
return 0
for filename in argv[1:]:
with open(filename) as f:
print "%s:" % filename
try:
print Parse(f.read(), filename)
except ParseError, e:
print e
return 1
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))