tenfourfox/ipc/ipdl/ipdl/parser.py
Cameron Kaiser c9b2922b70 hello FPR
2017-04-19 00:56:45 -07:00

780 lines
21 KiB
Python

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import os, sys
from ply import lex, yacc
from ipdl.ast import *
def _getcallerpath():
'''Return the absolute path of the file containing the code that
**CALLED** this function.'''
return os.path.abspath(sys._getframe(1).f_code.co_filename)
##-----------------------------------------------------------------------------
class ParseError(Exception):
def __init__(self, loc, fmt, *args):
self.loc = loc
self.error = ('%s%s: error: %s'% (
Parser.includeStackString(), loc, fmt)) % args
def __str__(self):
return self.error
def _safeLinenoValue(t):
lineno, value = 0, '???'
if hasattr(t, 'lineno'): lineno = t.lineno
if hasattr(t, 'value'): value = t.value
return lineno, value
def _error(loc, fmt, *args):
raise ParseError(loc, fmt, *args)
class Parser:
# when we reach an |include [protocol] foo;| statement, we need to
# save the current parser state and create a new one. this "stack" is
# where that state is saved
#
# there is one Parser per file
current = None
parseStack = [ ]
parsed = { }
def __init__(self, type, name, debug=0):
assert type and name
self.type = type
self.debug = debug
self.filename = None
self.includedirs = None
self.loc = None # not always up to date
self.lexer = None
self.parser = None
self.tu = TranslationUnit(type, name)
self.direction = None
self.errout = None
def parse(self, input, filename, includedirs, errout):
assert os.path.isabs(filename)
if filename in Parser.parsed:
return Parser.parsed[filename].tu
self.lexer = lex.lex(debug=self.debug,
optimize=not self.debug,
lextab="ipdl_lextab")
self.parser = yacc.yacc(debug=self.debug,
optimize=not self.debug,
tabmodule="ipdl_yacctab")
self.filename = filename
self.includedirs = includedirs
self.tu.filename = filename
self.errout = errout
Parser.parsed[filename] = self
Parser.parseStack.append(Parser.current)
Parser.current = self
try:
ast = self.parser.parse(input=input, lexer=self.lexer,
debug=self.debug)
except ParseError, p:
print >>errout, p
return None
Parser.current = Parser.parseStack.pop()
return ast
def resolveIncludePath(self, filepath):
'''Return the absolute path from which the possibly partial
|filepath| should be read, or |None| if |filepath| cannot be located.'''
for incdir in self.includedirs +[ '' ]:
realpath = os.path.join(incdir, filepath)
if os.path.isfile(realpath):
return os.path.abspath(realpath)
return None
# returns a GCC-style string representation of the include stack.
# e.g.,
# in file included from 'foo.ipdl', line 120:
# in file included from 'bar.ipd', line 12:
# which can be printed above a proper error message or warning
@staticmethod
def includeStackString():
s = ''
for parse in Parser.parseStack[1:]:
s += " in file included from `%s', line %d:\n"% (
parse.loc.filename, parse.loc.lineno)
return s
def locFromTok(p, num):
return Loc(Parser.current.filename, p.lineno(num))
##-----------------------------------------------------------------------------
reserved = set((
'answer',
'as',
'async',
'both',
'bridges',
'call',
'child',
'class',
'compress',
'compressall',
'__delete__',
'delete', # reserve 'delete' to prevent its use
'from',
'goto',
'high',
'include',
'intr',
'manager',
'manages',
'namespace',
'normal',
'nullable',
'opens',
'or',
'parent',
'prio',
'protocol',
'recv',
'returns',
'send',
'spawns',
'start',
'state',
'struct',
'sync',
'union',
'upto',
'urgent',
'using'))
tokens = [
'COLONCOLON', 'ID', 'STRING',
] + [ r.upper() for r in reserved ]
t_COLONCOLON = '::'
literals = '(){}[]<>;:,~'
t_ignore = ' \f\t\v'
def t_linecomment(t):
r'//[^\n]*'
def t_multilinecomment(t):
r'/\*(\n|.)*?\*/'
t.lexer.lineno += t.value.count('\n')
def t_NL(t):
r'(?:\r\n|\n|\n)+'
t.lexer.lineno += len(t.value)
def t_ID(t):
r'[a-zA-Z_][a-zA-Z0-9_]*'
if t.value in reserved:
t.type = t.value.upper()
return t
def t_STRING(t):
r'"[^"\n]*"'
t.value = t.value[1:-1]
return t
def t_error(t):
_error(Loc(Parser.current.filename, t.lineno),
'lexically invalid characters `%s', t.value)
##-----------------------------------------------------------------------------
def p_TranslationUnit(p):
"""TranslationUnit : Preamble NamespacedStuff"""
tu = Parser.current.tu
tu.loc = Loc(tu.filename)
for stmt in p[1]:
if isinstance(stmt, CxxInclude):
tu.addCxxInclude(stmt)
elif isinstance(stmt, Include):
tu.addInclude(stmt)
elif isinstance(stmt, UsingStmt):
tu.addUsingStmt(stmt)
else:
assert 0
for thing in p[2]:
if isinstance(thing, StructDecl):
tu.addStructDecl(thing)
elif isinstance(thing, UnionDecl):
tu.addUnionDecl(thing)
elif isinstance(thing, Protocol):
if tu.protocol is not None:
_error(thing.loc, "only one protocol definition per file")
tu.protocol = thing
else:
assert(0)
# The "canonical" namespace of the tu, what it's considered to be
# in for the purposes of C++: |#include "foo/bar/TU.h"|
if tu.protocol:
assert tu.filetype == 'protocol'
tu.namespaces = tu.protocol.namespaces
tu.name = tu.protocol.name
else:
assert tu.filetype == 'header'
# There's not really a canonical "thing" in headers. So
# somewhat arbitrarily use the namespace of the last
# interesting thing that was declared.
for thing in reversed(tu.structsAndUnions):
tu.namespaces = thing.namespaces
break
p[0] = tu
##--------------------
## Preamble
def p_Preamble(p):
"""Preamble : Preamble PreambleStmt ';'
|"""
if 1 == len(p):
p[0] = [ ]
else:
p[1].append(p[2])
p[0] = p[1]
def p_PreambleStmt(p):
"""PreambleStmt : CxxIncludeStmt
| IncludeStmt
| UsingStmt"""
p[0] = p[1]
def p_CxxIncludeStmt(p):
"""CxxIncludeStmt : INCLUDE STRING"""
p[0] = CxxInclude(locFromTok(p, 1), p[2])
def p_IncludeStmt(p):
"""IncludeStmt : INCLUDE PROTOCOL ID
| INCLUDE ID"""
loc = locFromTok(p, 1)
Parser.current.loc = loc
if 4 == len(p):
id = p[3]
type = 'protocol'
else:
id = p[2]
type = 'header'
inc = Include(loc, type, id)
path = Parser.current.resolveIncludePath(inc.file)
if path is None:
raise ParseError(loc, "can't locate include file `%s'"% (
inc.file))
inc.tu = Parser(type, id).parse(open(path).read(), path, Parser.current.includedirs, Parser.current.errout)
p[0] = inc
def p_UsingStmt(p):
"""UsingStmt : USING CxxType FROM STRING
| USING CLASS CxxType FROM STRING
| USING STRUCT CxxType FROM STRING"""
if 6 == len(p):
header = p[5]
elif 5 == len(p):
header = p[4]
else:
header = None
if 6 == len(p):
kind = p[2]
else:
kind = None
if 6 == len(p):
cxxtype = p[3]
else:
cxxtype = p[2]
p[0] = UsingStmt(locFromTok(p, 1), cxxtype, header, kind)
##--------------------
## Namespaced stuff
def p_NamespacedStuff(p):
"""NamespacedStuff : NamespacedStuff NamespaceThing
| NamespaceThing"""
if 2 == len(p):
p[0] = p[1]
else:
p[1].extend(p[2])
p[0] = p[1]
def p_NamespaceThing(p):
"""NamespaceThing : NAMESPACE ID '{' NamespacedStuff '}'
| StructDecl
| UnionDecl
| ProtocolDefn"""
if 2 == len(p):
p[0] = [ p[1] ]
else:
for thing in p[4]:
thing.addOuterNamespace(Namespace(locFromTok(p, 1), p[2]))
p[0] = p[4]
def p_StructDecl(p):
"""StructDecl : STRUCT ID '{' StructFields '}' ';'
| STRUCT ID '{' '}' ';'"""
if 7 == len(p):
p[0] = StructDecl(locFromTok(p, 1), p[2], p[4])
else:
p[0] = StructDecl(locFromTok(p, 1), p[2], [ ])
def p_StructFields(p):
"""StructFields : StructFields StructField ';'
| StructField ';'"""
if 3 == len(p):
p[0] = [ p[1] ]
else:
p[1].append(p[2])
p[0] = p[1]
def p_StructField(p):
"""StructField : Type ID"""
p[0] = StructField(locFromTok(p, 1), p[1], p[2])
def p_UnionDecl(p):
"""UnionDecl : UNION ID '{' ComponentTypes '}' ';'"""
p[0] = UnionDecl(locFromTok(p, 1), p[2], p[4])
def p_ComponentTypes(p):
"""ComponentTypes : ComponentTypes Type ';'
| Type ';'"""
if 3 == len(p):
p[0] = [ p[1] ]
else:
p[1].append(p[2])
p[0] = p[1]
def p_ProtocolDefn(p):
"""ProtocolDefn : OptionalProtocolSendSemanticsQual PROTOCOL ID '{' ProtocolBody '}' ';'"""
protocol = p[5]
protocol.loc = locFromTok(p, 2)
protocol.name = p[3]
protocol.priorityRange = p[1][0]
protocol.sendSemantics = p[1][1]
p[0] = protocol
if Parser.current.type == 'header':
_error(protocol.loc, 'can\'t define a protocol in a header. Do it in a protocol spec instead.')
def p_ProtocolBody(p):
"""ProtocolBody : SpawnsStmtsOpt"""
p[0] = p[1]
##--------------------
## spawns/bridges/opens stmts
def p_SpawnsStmtsOpt(p):
"""SpawnsStmtsOpt : SpawnsStmt SpawnsStmtsOpt
| BridgesStmtsOpt"""
if 2 == len(p):
p[0] = p[1]
else:
p[2].spawnsStmts.insert(0, p[1])
p[0] = p[2]
def p_SpawnsStmt(p):
"""SpawnsStmt : PARENT SPAWNS ID AsOpt ';'
| CHILD SPAWNS ID AsOpt ';'"""
p[0] = SpawnsStmt(locFromTok(p, 1), p[1], p[3], p[4])
def p_AsOpt(p):
"""AsOpt : AS PARENT
| AS CHILD
| """
if 3 == len(p):
p[0] = p[2]
else:
p[0] = 'child'
def p_BridgesStmtsOpt(p):
"""BridgesStmtsOpt : BridgesStmt BridgesStmtsOpt
| OpensStmtsOpt"""
if 2 == len(p):
p[0] = p[1]
else:
p[2].bridgesStmts.insert(0, p[1])
p[0] = p[2]
def p_BridgesStmt(p):
"""BridgesStmt : BRIDGES ID ',' ID ';'"""
p[0] = BridgesStmt(locFromTok(p, 1), p[2], p[4])
def p_OpensStmtsOpt(p):
"""OpensStmtsOpt : OpensStmt OpensStmtsOpt
| ManagersStmtOpt"""
if 2 == len(p):
p[0] = p[1]
else:
p[2].opensStmts.insert(0, p[1])
p[0] = p[2]
def p_OpensStmt(p):
"""OpensStmt : PARENT OPENS ID ';'
| CHILD OPENS ID ';'"""
p[0] = OpensStmt(locFromTok(p, 1), p[1], p[3])
##--------------------
## manager/manages stmts
def p_ManagersStmtOpt(p):
"""ManagersStmtOpt : ManagersStmt ManagesStmtsOpt
| ManagesStmtsOpt"""
if 2 == len(p):
p[0] = p[1]
else:
p[2].managers = p[1]
p[0] = p[2]
def p_ManagersStmt(p):
"""ManagersStmt : MANAGER ManagerList ';'"""
if 1 == len(p):
p[0] = [ ]
else:
p[0] = p[2]
def p_ManagerList(p):
"""ManagerList : ID
| ManagerList OR ID"""
if 2 == len(p):
p[0] = [ Manager(locFromTok(p, 1), p[1]) ]
else:
p[1].append(Manager(locFromTok(p, 3), p[3]))
p[0] = p[1]
def p_ManagesStmtsOpt(p):
"""ManagesStmtsOpt : ManagesStmt ManagesStmtsOpt
| MessageDeclsOpt"""
if 2 == len(p):
p[0] = p[1]
else:
p[2].managesStmts.insert(0, p[1])
p[0] = p[2]
def p_ManagesStmt(p):
"""ManagesStmt : MANAGES ID ';'"""
p[0] = ManagesStmt(locFromTok(p, 1), p[2])
##--------------------
## Message decls
def p_MessageDeclsOpt(p):
"""MessageDeclsOpt : MessageDeclThing MessageDeclsOpt
| TransitionStmtsOpt"""
if 2 == len(p):
p[0] = p[1]
else:
p[2].messageDecls.insert(0, p[1])
p[0] = p[2]
def p_MessageDeclThing(p):
"""MessageDeclThing : MessageDirectionLabel ':' MessageDecl ';'
| MessageDecl ';'"""
if 3 == len(p):
p[0] = p[1]
else:
p[0] = p[3]
def p_MessageDirectionLabel(p):
"""MessageDirectionLabel : PARENT
| CHILD
| BOTH"""
if p[1] == 'parent':
Parser.current.direction = IN
elif p[1] == 'child':
Parser.current.direction = OUT
elif p[1] == 'both':
Parser.current.direction = INOUT
else:
assert 0
def p_MessageDecl(p):
"""MessageDecl : OptionalSendSemanticsQual MessageBody"""
msg = p[2]
msg.priority = p[1][0]
msg.sendSemantics = p[1][1]
if Parser.current.direction is None:
_error(msg.loc, 'missing message direction')
msg.direction = Parser.current.direction
p[0] = msg
def p_MessageBody(p):
"""MessageBody : MessageId MessageInParams MessageOutParams OptionalMessageCompress"""
# FIXME/cjones: need better loc info: use one of the quals
loc, name = p[1]
msg = MessageDecl(loc)
msg.name = name
msg.addInParams(p[2])
msg.addOutParams(p[3])
msg.compress = p[4]
p[0] = msg
def p_MessageId(p):
"""MessageId : ID
| __DELETE__
| DELETE
| '~' ID"""
loc = locFromTok(p, 1)
if 3 == len(p):
_error(loc, "sorry, `%s()' destructor syntax is a relic from a bygone era. Declare `__delete__()' in the `%s' protocol instead", p[1]+p[2], p[2])
elif 'delete' == p[1]:
_error(loc, "`delete' is a reserved identifier")
p[0] = [ loc, p[1] ]
def p_MessageInParams(p):
"""MessageInParams : '(' ParamList ')'"""
p[0] = p[2]
def p_MessageOutParams(p):
"""MessageOutParams : RETURNS '(' ParamList ')'
| """
if 1 == len(p):
p[0] = [ ]
else:
p[0] = p[3]
def p_OptionalMessageCompress(p):
"""OptionalMessageCompress : MessageCompress
| """
if 1 == len(p):
p[0] = ''
else:
p[0] = p[1]
def p_MessageCompress(p):
"""MessageCompress : COMPRESS
| COMPRESSALL"""
p[0] = p[1]
##--------------------
## State machine
def p_TransitionStmtsOpt(p):
"""TransitionStmtsOpt : TransitionStmt TransitionStmtsOpt
|"""
if 1 == len(p):
# we fill in |loc| in the Protocol rule
p[0] = Protocol(None)
else:
p[2].transitionStmts.insert(0, p[1])
p[0] = p[2]
def p_TransitionStmt(p):
"""TransitionStmt : OptionalStart STATE State ':' Transitions"""
p[3].start = p[1]
p[0] = TransitionStmt(locFromTok(p, 2), p[3], p[5])
def p_OptionalStart(p):
"""OptionalStart : START
| """
p[0] = (len(p) == 2) # True iff 'start' specified
def p_Transitions(p):
"""Transitions : Transitions Transition
| Transition"""
if 3 == len(p):
p[1].append(p[2])
p[0] = p[1]
else:
p[0] = [ p[1] ]
def p_Transition(p):
"""Transition : Trigger ID GOTO StateList ';'
| Trigger __DELETE__ ';'
| Trigger DELETE ';'"""
if 'delete' == p[2]:
_error(locFromTok(p, 1), "`delete' is a reserved identifier")
loc, trigger = p[1]
if 6 == len(p):
nextstates = p[4]
else:
nextstates = [ State.DEAD ]
p[0] = Transition(loc, trigger, p[2], nextstates)
def p_Trigger(p):
"""Trigger : SEND
| RECV
| CALL
| ANSWER"""
p[0] = [ locFromTok(p, 1), Transition.nameToTrigger(p[1]) ]
def p_StateList(p):
"""StateList : StateList OR State
| State"""
if 2 == len(p):
p[0] = [ p[1] ]
else:
p[1].append(p[3])
p[0] = p[1]
def p_State(p):
"""State : ID"""
p[0] = State(locFromTok(p, 1), p[1])
##--------------------
## Minor stuff
def p_Priority(p):
"""Priority : NORMAL
| HIGH
| URGENT"""
prios = {'normal': 1,
'high': 2,
'urgent': 3}
p[0] = prios[p[1]]
def p_OptionalSendSemanticsQual(p):
"""OptionalSendSemanticsQual : SendSemanticsQual
| """
if 2 == len(p): p[0] = p[1]
else: p[0] = [ NORMAL_PRIORITY, ASYNC ]
def p_SendSemanticsQual(p):
"""SendSemanticsQual : ASYNC
| SYNC
| PRIO '(' Priority ')' ASYNC
| PRIO '(' Priority ')' SYNC
| INTR"""
if p[1] == 'prio':
mtype = p[5]
prio = p[3]
else:
mtype = p[1]
prio = NORMAL_PRIORITY
if mtype == 'async': mtype = ASYNC
elif mtype == 'sync': mtype = SYNC
elif mtype == 'intr': mtype = INTR
else: assert 0
p[0] = [ prio, mtype ]
def p_OptionalProtocolSendSemanticsQual(p):
"""OptionalProtocolSendSemanticsQual : ProtocolSendSemanticsQual
| """
if 2 == len(p): p[0] = p[1]
else: p[0] = [ (NORMAL_PRIORITY, NORMAL_PRIORITY), ASYNC ]
def p_ProtocolSendSemanticsQual(p):
"""ProtocolSendSemanticsQual : ASYNC
| SYNC
| PRIO '(' Priority UPTO Priority ')' ASYNC
| PRIO '(' Priority UPTO Priority ')' SYNC
| PRIO '(' Priority UPTO Priority ')' INTR
| INTR"""
if p[1] == 'prio':
mtype = p[7]
prio = (p[3], p[5])
else:
mtype = p[1]
prio = (NORMAL_PRIORITY, NORMAL_PRIORITY)
if mtype == 'async': mtype = ASYNC
elif mtype == 'sync': mtype = SYNC
elif mtype == 'intr': mtype = INTR
else: assert 0
p[0] = [ prio, mtype ]
def p_ParamList(p):
"""ParamList : ParamList ',' Param
| Param
| """
if 1 == len(p):
p[0] = [ ]
elif 2 == len(p):
p[0] = [ p[1] ]
else:
p[1].append(p[3])
p[0] = p[1]
def p_Param(p):
"""Param : Type ID"""
p[0] = Param(locFromTok(p, 1), p[1], p[2])
def p_Type(p):
"""Type : MaybeNullable BasicType"""
# only actor types are nullable; we check this in the type checker
p[2].nullable = p[1]
p[0] = p[2]
def p_BasicType(p):
"""BasicType : ScalarType
| ScalarType '[' ']'"""
if 4 == len(p):
p[1].array = 1
p[0] = p[1]
def p_ScalarType(p):
"""ScalarType : ActorType
| CxxID""" # ID == CxxType; we forbid qnames here,
# in favor of the |using| declaration
if isinstance(p[1], TypeSpec):
p[0] = p[1]
else:
loc, id = p[1]
p[0] = TypeSpec(loc, QualifiedId(loc, id))
def p_ActorType(p):
"""ActorType : ID ':' State"""
loc = locFromTok(p, 1)
p[0] = TypeSpec(loc, QualifiedId(loc, p[1]), state=p[3])
def p_MaybeNullable(p):
"""MaybeNullable : NULLABLE
| """
p[0] = (2 == len(p))
##--------------------
## C++ stuff
def p_CxxType(p):
"""CxxType : QualifiedID
| CxxID"""
if isinstance(p[1], QualifiedId):
p[0] = TypeSpec(p[1].loc, p[1])
else:
loc, id = p[1]
p[0] = TypeSpec(loc, QualifiedId(loc, id))
def p_QualifiedID(p):
"""QualifiedID : QualifiedID COLONCOLON CxxID
| CxxID COLONCOLON CxxID"""
if isinstance(p[1], QualifiedId):
loc, id = p[3]
p[1].qualify(id)
p[0] = p[1]
else:
loc1, id1 = p[1]
_, id2 = p[3]
p[0] = QualifiedId(loc1, id2, [ id1 ])
def p_CxxID(p):
"""CxxID : ID
| CxxTemplateInst"""
if isinstance(p[1], tuple):
p[0] = p[1]
else:
p[0] = (locFromTok(p, 1), str(p[1]))
def p_CxxTemplateInst(p):
"""CxxTemplateInst : ID '<' ID '>'"""
p[0] = (locFromTok(p, 1), str(p[1]) +'<'+ str(p[3]) +'>')
def p_error(t):
lineno, value = _safeLinenoValue(t)
_error(Loc(Parser.current.filename, lineno),
"bad syntax near `%s'", value)