From dc5be22981bbcdaace2644206954b4f45d24b84f Mon Sep 17 00:00:00 2001 From: Benjamin Smedberg Date: Tue, 30 Jun 2009 15:38:59 -0400 Subject: [PATCH] Import IPDL from cjones' working repo, revision 282b4211d881. New IPDL work will take place in electrolysis. --- ipc/ipdl/ipdl/Makefile | 7 + ipc/ipdl/ipdl/__init__.py | 58 +++ ipc/ipdl/ipdl/ast.py | 280 +++++++++++++ ipc/ipdl/ipdl/builtin.py | 68 ++++ ipc/ipdl/ipdl/cgen.py | 129 ++++++ ipc/ipdl/ipdl/cxx/__init__.py | 34 ++ ipc/ipdl/ipdl/cxx/ast.py | 426 ++++++++++++++++++++ ipc/ipdl/ipdl/cxx/cgen.py | 337 ++++++++++++++++ ipc/ipdl/ipdl/lower.py | 729 ++++++++++++++++++++++++++++++++++ ipc/ipdl/ipdl/parser.py | 381 ++++++++++++++++++ ipc/ipdl/ipdl/type.py | 679 +++++++++++++++++++++++++++++++ ipc/ipdl/ipdlc | 141 +++++++ 12 files changed, 3269 insertions(+) create mode 100644 ipc/ipdl/ipdl/Makefile create mode 100644 ipc/ipdl/ipdl/__init__.py create mode 100644 ipc/ipdl/ipdl/ast.py create mode 100644 ipc/ipdl/ipdl/builtin.py create mode 100644 ipc/ipdl/ipdl/cgen.py create mode 100644 ipc/ipdl/ipdl/cxx/__init__.py create mode 100644 ipc/ipdl/ipdl/cxx/ast.py create mode 100644 ipc/ipdl/ipdl/cxx/cgen.py create mode 100644 ipc/ipdl/ipdl/lower.py create mode 100644 ipc/ipdl/ipdl/parser.py create mode 100644 ipc/ipdl/ipdl/type.py create mode 100755 ipc/ipdl/ipdlc diff --git a/ipc/ipdl/ipdl/Makefile b/ipc/ipdl/ipdl/Makefile new file mode 100644 index 00000000000..36ea5b2a555 --- /dev/null +++ b/ipc/ipdl/ipdl/Makefile @@ -0,0 +1,7 @@ +.PHONY: clean + +clean: + rm -f cxx/*~ *~ *.pyc cxx/*.pyc parser.out ipdl_lextab.py ipdl_yacctab.py + +check: + @echo "Implement me! With tests/!" diff --git a/ipc/ipdl/ipdl/__init__.py b/ipc/ipdl/ipdl/__init__.py new file mode 100644 index 00000000000..69f2520e126 --- /dev/null +++ b/ipc/ipdl/ipdl/__init__.py @@ -0,0 +1,58 @@ + # ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (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.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# Contributor(s): +# Chris Jones +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +__all__ = [ 'gencxx', 'genipdl', 'parse', 'typecheck' ] + +import os, sys + +from ipdl.cgen import IPDLCodeGen +from ipdl.lower import LowerToCxx +from ipdl.parser import Parser +from ipdl.type import TypeCheck + +from ipdl.cxx.cgen import CxxCodeGen + +def parse(specstring, filename=''): + return Parser().parse(specstring, filename) + +def typecheck(ast, errout=sys.stderr): + '''Returns True iff |ast| is well typed. Print errors to |errout| if + it is not.''' + return TypeCheck().check(ast, errout) + +def gencxx(ast, outdir): + for hdr in LowerToCxx().lower(ast): + path = os.path.join(outdir, hdr.filename) + CxxCodeGen(outf=open(path, 'w')).cgen(hdr) + +def genipdl(ast, outdir): + return IPDLCodeGen().cgen(ast) diff --git a/ipc/ipdl/ipdl/ast.py b/ipc/ipdl/ipdl/ast.py new file mode 100644 index 00000000000..2353200a6f6 --- /dev/null +++ b/ipc/ipdl/ipdl/ast.py @@ -0,0 +1,280 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (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.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# Contributor(s): +# Chris Jones +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +import sys + +class Visitor: + def defaultVisit(self, node): + raise Exception, "INTERNAL ERROR: no visitor for node type `%s'"% ( + node.__class__.__name__) + + def visitTranslationUnit(self, tu): + for cxxInc in tu.cxxIncludes: + cxxInc.accept(self) + for protoInc in tu.protocolIncludes: + protoInc.accept(self) + for using in tu.using: + using.accept(self) + tu.protocol.accept(self) + + def visitCxxInclude(self, inc): + pass + + def visitProtocolInclude(self, inc): + # Note: we don't visit the child AST here, because that needs delicate + # and pass-specific handling + pass + + def visitUsingStmt(self, using): + pass + + def visitProtocol(self, p): + for namespace in p.namespaces: + namespace.accept(self) + if p.manager is not None: + p.manager.accept(self) + for managed in p.managesStmts: + managed.accept(self) + for msgDecl in p.messageDecls: + msgDecl.accept(self) + for transitionStmt in p.transitionStmts: + transitionStmt.accept(self) + + def visitNamespace(self, ns): + pass + + def visitManagerStmt(self, mgr): + pass + + def visitManagesStmt(self, mgs): + pass + + def visitMessageDecl(self, md): + for inParam in md.inParams: + inParam.accept(self) + for outParam in md.outParams: + outParam.accept(self) + + def visitParam(self, decl): + pass + + def visitTypeSpec(self, ts): + pass + + def visitDecl(self, d): + pass + +class Loc: + def __init__(self, filename='', lineno=0): + assert filename + self.filename = filename + self.lineno = lineno + def __repr__(self): + return '%r:%r'% (self.filename, self.lineno) + def __str__(self): + return '%s:%s'% (self.filename, self.lineno) + +Loc.NONE = Loc(filename='', lineno=0) + +class _struct(): + pass + +class Node: + def __init__(self, loc=Loc.NONE): + self.loc = loc + + def accept(self, visitor): + visit = getattr(visitor, 'visit'+ self.__class__.__name__, None) + if visit is None: + return getattr(visitor, 'defaultVisit')(self) + return visit(self) + + def addAttrs(self, attrsName): + if not hasattr(self, attrsName): + setattr(self, attrsName, _struct()) + +class TranslationUnit(Node): + def __init__(self): + Node.__init__(self) + self.filename = None + self.cxxIncludes = [ ] + self.protocolIncludes = [ ] + self.using = [ ] + self.protocol = None + + def addCxxInclude(self, cxxInclude): self.cxxIncludes.append(cxxInclude) + def addProtocolInclude(self, pInc): self.protocolIncludes.append(pInc) + def addUsingStmt(self, using): self.using.append(using) + + def setProtocol(self, protocol): self.protocol = protocol + +class CxxInclude(Node): + def __init__(self, loc, cxxFile): + Node.__init__(self, loc) + self.file = cxxFile + +class ProtocolInclude(Node): + def __init__(self, loc, protocolFile): + Node.__init__(self, loc) + self.file = protocolFile + +class UsingStmt(Node): + def __init__(self, loc, cxxTypeSpec): + Node.__init__(self, loc) + self.type = cxxTypeSpec + +# "singletons" +class ASYNC: + pretty = 'Async' +class RPC: + pretty = 'Rpc' +class SYNC: + pretty = 'Sync' + +class INOUT: + pretty = 'InOut' +class IN: + @staticmethod + def pretty(ss): return _prettyTable['In'][ss.pretty] +class OUT: + @staticmethod + def pretty(ss): return _prettyTable['Out'][ss.pretty] + +_prettyTable = { + 'In' : { 'Async': 'AsyncRecv', + 'Sync': 'SyncRecv', + 'Rpc': 'RpcAnswer' }, + 'Out' : { 'Async': 'AsyncSend', + 'Sync': 'SyncSend', + 'Rpc': 'RpcCall' } + # inout doesn't make sense here +} + + +class Protocol(Node): + def __init__(self, loc): + Node.__init__(self, loc) + self.name = None + self.namespaces = [ ] + self.sendSemantics = ASYNC + self.managesStmts = [ ] + self.messageDecls = [ ] + self.transitionStmts = [ ] + + def addOuterNamespace(self, namespace): + self.namespaces.insert(0, namespace) + + def addManagesStmts(self, managesStmts): + self.managesStmts += managesStmts + + def addMessageDecls(self, messageDecls): + self.messageDecls += messageDecls + + def addTransitionStmts(self, transStmts): + self.transitionStmts += transStmts + +class Namespace(Node): + def __init__(self, loc, namespace): + Node.__init__(self, loc) + self.namespace = namespace + +class ManagerStmt(Node): + def __init__(self, loc, managerName): + Node.__init__(self, loc) + self.name = managerName + +class ManagesStmt(Node): + def __init__(self, loc, managedName): + Node.__init__(self, loc) + self.name = managedName + +class MessageDecl(Node): + def __init__(self, loc): + Node.__init__(self, loc) + self.name = None + self.sendSemantics = ASYNC + self.direction = None + self.inParams = [ ] + self.outParams = [ ] + + def addInParams(self, inParamsList): + self.inParams += inParamsList + + def addOutParams(self, outParamsList): + self.outParams += outParamsList + + def hasReply(self): + return self.sendSemantics is SYNC or self.sendSemantics is RPC + +class Param(Node): + def __init__(self, loc, typespec, name): + Node.__init__(self, loc) + self.name = name + self.typespec = typespec + +class TypeSpec(Node): + def __init__(self, loc, spec): + Node.__init__(self, loc) + self.spec = spec + + def basename(self): + return self.spec.baseid + + def __str__(self): return str(self.spec) + +class QualifiedId: # FIXME inherit from node? + def __init__(self, loc, baseid, quals=[ ]): + self.loc = loc + self.baseid = baseid + self.quals = quals + + def qualify(self, id): + self.quals.append(self.baseid) + self.baseid = id + + def __str__(self): + if 0 == len(self.quals): + return self.baseid + return '::'.join(self.quals) +'::'+ self.baseid + +# added by type checking passes +class Decl(Node): + def __init__(self, loc): + Node.__init__(self, loc) + self.progname = None # what the programmer typed, if relevant + self.shortname = None # shortest way to refer to this decl + self.fullname = None # full way to refer to this decl + self.loc = loc + self.type = None + self.scope = None + + diff --git a/ipc/ipdl/ipdl/builtin.py b/ipc/ipdl/ipdl/builtin.py new file mode 100644 index 00000000000..c5b2136e616 --- /dev/null +++ b/ipc/ipdl/ipdl/builtin.py @@ -0,0 +1,68 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (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.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# Contributor(s): +# Chris Jones +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +# WARNING: the syntax of the builtin types is not checked, so please +# don't add something syntactically invalid. It will not be fun to +# track down the bug. + +Types = ( + # C types + 'char', + 'short', + 'int', + 'long', + 'float', + 'double', + + # stdint types + 'int8_t', + 'uint8_t', + 'int16_t', + 'uint16_t', + 'int32_t', + 'uint32_t', + 'int64_t', + 'uint64_t', + 'intptr_t', + 'uintptr_t', + + # Mozilla types: "less" standard things we know how serialize/deserialize + 'mozilla::ipc::String', + 'mozilla::ipc::StringArray', +) + + +Includes = ( + 'nscore.h', + 'IPC/IPCMessageUtils.h', + 'mozilla/ipc/MessageTypes.h', +) diff --git a/ipc/ipdl/ipdl/cgen.py b/ipc/ipdl/ipdl/cgen.py new file mode 100644 index 00000000000..53421246998 --- /dev/null +++ b/ipc/ipdl/ipdl/cgen.py @@ -0,0 +1,129 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (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.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# Contributor(s): +# Chris Jones +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +import os, sys + +from ipdl.ast import Visitor +from ipdl.ast import IN, OUT, INOUT, ASYNC, SYNC, RPC + +class CodePrinter: + def __init__(self, outf=sys.stdout, indentCols=4): + self.outf = outf + self.col = 0 + self.indentCols = indentCols + + def write(self, str): + self.outf.write(str) + + def printdent(self, str=''): + self.write((' '* self.col) + str) + + def println(self, str=''): + self.write(str +'\n') + + def printdentln(self, str): + self.write((' '* self.col) + str +'\n') + + def indent(self): self.col += self.indentCols + def dedent(self): self.col -= self.indentCols + + +##----------------------------------------------------------------------------- +class IPDLCodeGen(CodePrinter, Visitor): + '''Spits back out equivalent IPDL to the code that generated this. +Also known as pretty-printing.''' + + def __init__(self, outf=sys.stdout, indentCols=4, printed=set()): + CodePrinter.__init__(self, outf, indentCols) + self.printed = printed + + def visitTranslationUnit(self, tu): + self.printed.add(tu.filename) + self.println('//\n// Automatically generated by ipdlc\n//') + CodeGen.visitTranslationUnit(self, tu) + + def visitCxxInclude(self, inc): + self.println('include "'+ inc.file +'";') + + def visitProtocolInclude(self, inc): + self.println('include protocol "'+ inc.file +'";') + if inc.tu.filename not in self.printed: + self.println('/* Included file:') + IPDLCodeGen(outf=self.outf, indentCols=self.indentCols, + printed=self.printed).visitTranslationUnit(inc.tu) + + self.println('*/') + + def visitProtocol(self, p): + self.println() + for namespace in p.namespaces: namespace.accept(self) + + self.println('%s protocol %s\n{'% (p.sendSemantics[0], p.name)) + self.indent() + + for mgs in p.managesStmts: + mgs.accept(self) + if len(p.managesStmts): self.println() + + for msgDecl in p.messageDecls: msgDecl.accept(self) + self.println() + + for transStmt in p.transitionStmts: transStmt.accept(self) + + self.dedent() + self.println('}') + self.write('}\n'* len(p.namespaces)) + + def visitManagerStmt(self, mgr): + self.printdentln('manager '+ mgr.name +';') + + def visitManagesStmt(self, mgs): + self.printdentln('manages '+ mgs.name +';') + + def visitMessageDecl(self, msg): + self.printdent('%s %s %s('% (msg.sendSemantics[0], msg.direction[0], msg.name)) + for i, inp in enumerate(msg.inParams): + inp.accept(self) + if i != (len(msg.inParams) - 1): self.write(', ') + self.write(')') + if 0 == len(msg.outParams): + self.println(';') + return + + self.println() + self.indent() + self.printdent('returns (') + for i, outp in enumerate(msg.outParams): + outp.accept(self) + if i != (len(msg.outParams) - 1): self.write(', ') + self.println(');') + self.dedent() diff --git a/ipc/ipdl/ipdl/cxx/__init__.py b/ipc/ipdl/ipdl/cxx/__init__.py new file mode 100644 index 00000000000..aa37eae5122 --- /dev/null +++ b/ipc/ipdl/ipdl/cxx/__init__.py @@ -0,0 +1,34 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (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.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# Contributor(s): +# Chris Jones +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +import ipdl.cxx.ast +import ipdl.cxx.cgen diff --git a/ipc/ipdl/ipdl/cxx/ast.py b/ipc/ipdl/ipdl/cxx/ast.py new file mode 100644 index 00000000000..f80b6b6c3cf --- /dev/null +++ b/ipc/ipdl/ipdl/cxx/ast.py @@ -0,0 +1,426 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (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.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# Contributor(s): +# Chris Jones +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +import copy, sys + +class Visitor: + def defaultVisit(self, node): + raise Exception, "INTERNAL ERROR: no visitor for node type `%s'"% ( + node.__class__.__name__) + + def visitWhitespace(self, ws): + pass + + def visitFile(self, f): + for thing in f.stuff: + thing.accept(self) + + def visitCppDirective(self, ppd): + pass + + def visitBlock(self, block): + for stmt in block.stmts: + stmt.accept(self) + + def visitNamespace(self, ns): + self.visitBlock(ns) + + def visitType(self, type): + pass + + def visitTypeEnum(self, enum): + pass + + def visitTypedef(self, tdef): + tdef.fromtype.accept(self) + tdef.tottype.accept(self) + + def visitDecl(self, decl): + decl.type.accept(self) + + def visitClass(self, cls): + for viz, parent in cls.inherits: + parent.accept(self) + self.visitBlock(cls) + + def visitInherit(self, inh): + pass + + def visitMethodDecl(self, meth): + for param in meth.params: + param.accept(self) + if meth.ret is not None: + meth.ret.accept(self) + + def visitMethodDefn(self, meth): + meth.decl.accept(self) + self.visitBlock(meth) + + def visitConstructorDecl(self, ctor): + self.visitMethodDecl(ctor) + + def visitConstructorDefn(self, cd): + cd.decl.accept(self) + for init in cd.memberinits: + init.accept(self) + self.visitBlock(cd) + + def visitDestructorDecl(self, dtor): + self.visitMethodDecl(dtor) + + def visitDestructorDefn(self, dd): + dd.decl.accept(self) + self.visitBlock(dd) + + def visitExprVar(self, v): + pass + + def visitExprPrefixUnop(self, e): + e.expr.accept(self) + + def visitExprAddrOf(self, eao): + self.visitExprPrefixUnop(eao) + + def visitExprDeref(self, ed): + self.visitExprPrefixUnop(ed) + + def visitExprSelect(self, es): + es.obj.accept(self) + + def visitExprAssn(self, ea): + ea.lhs.accept(self) + ea.rhs.accept(self) + + def visitExprCall(self, ec): + ec.func.accept(self) + for arg in ec.args: + arg.accept(self) + + def visitExprNew(self, en): + self.visitExprCall(en) + + def visitExprDelete(self, ed): + ed.obj.accept(self) + + def visitExprMemberInit(self, minit): + self.visitExprCall(minit) + + def visitStmtBlock(self, sb): + self.visitBlock(sb) + + def visitStmtDecl(self, sd): + sd.decl.accept(self) + + def visitLabel(self, label): + pass + + def visitCaseLabel(self, case): + pass + + def visitDefaultLabel(self, dl): + pass + + def visitStmtSwitch(self, ss): + ss.expr.accept(self) + self.visitBlock(ss) + + def visitStmtExpr(self, se): + se.expr.accept(self) + + def visitStmtReturn(self, sr): + if sr.expr is not None: + sr.expr.accept(self) + +##------------------------------ +class Node: + def __init__(self): + pass + + def accept(self, visitor): + visit = getattr(visitor, 'visit'+ self.__class__.__name__, None) + if visit is None: + return getattr(visitor, 'defaultVisit')(self) + return visit(self) + +class Whitespace(Node): + # yes, this is silly. but we need to stick comments in the + # generated code without resorting to more serious hacks + def __init__(self, ws): + Node.__init__(self) + self.ws = ws +Whitespace.NL = Whitespace('\n') + +class File(Node): + def __init__(self, filename): + Node.__init__(self) + self.filename = filename + # array of stuff in the file --- stmts and preprocessor thingies + self.stuff = [ ] + + def addthing(self, thing): + self.stuff.append(thing) + + # "look like" a Block so code doesn't have to care whether they're + # in global scope or not + def addstmt(self, stmt): + self.stuff.append(stmt) + +class CppDirective(Node): + '''represents |#[directive] [rest]|, where |rest| is any string''' + def __init__(self, directive, rest): + Node.__init__(self) + self.directive = directive + self.rest = rest + +class Block(Node): + def __init__(self): + Node.__init__(self) + self.stmts = [ ] + + def addstmt(self, stmt): + self.stmts.append(stmt) + +##------------------------------ +# type and decl thingies +class Namespace(Block): + def __init__(self, name): + Block.__init__(self) + self.name = name + +class Type(Node): + def __init__(self, name, const=False, ptr=False, ref=False): + Node.__init__(self) + self.name = name + self.const = const + self.ptr = ptr + self.ref = ref + # XXX could get serious here with recursive types, but shouldn't + # need that for this codegen + def __deepcopy__(self, memo): + return Type(self.name, self.const, self.ptr, self.ref) + +class TypeEnum(Node): + def __init__(self, name=None): + '''name can be None''' + Node.__init__(self) + self.name = name + self.idnums = [ ] # pairs of ('Foo', [num]) or ('Foo', None) + + def addId(self, id, num=None): + self.idnums.append((id, num)) + +class Typedef(Node): + def __init__(self, fromtype, totype): + Node.__init__(self) + self.fromtype = fromtype + self.totype = totype + +class Decl(Node): + '''represents |Foo bar|, e.g. in a function signature''' + def __init__(self, type, name): + Node.__init__(self) + self.type = type + self.name = name + def __deepcopy__(self, memo): + return Decl(copy.deepcopy(self.type, memo), self.name) + +##------------------------------ +# class stuff +class Class(Block): + def __init__(self, name, inherits=[ ], + interface=False, final=False): + assert not (interface and final) + + Block.__init__(self) + self.name = name + self.inherits = inherits # array of (viz, Type) pairs + self.interface = interface + self.final = final + +class Inherit(Node): + def __init__(self, name, viz='public'): + Node.__init__(self) + self.name = name + self.viz = viz + +class MethodDecl(Node): + def __init__(self, name, params=[ ], ret=Type('void'), + virtual=False, const=False, pure=False, static=False): + assert not (virtual and static) + assert not pure or virtual # pure => virtual + + Node.__init__(self) + self.name = name + self.params = params + self.ret = ret + self.virtual = virtual + self.const = const + self.pure = pure + self.static = static + def __deepcopy__(self, memo): + return MethodDecl(self.name, + copy.deepcopy(self.params, memo), + copy.deepcopy(self.ret, memo), + self.virtual, + self.const, + self.pure) + +class MethodDefn(Block): + def __init__(self, decl): + Block.__init__(self) + self.decl = decl + +class ConstructorDecl(MethodDecl): + def __init__(self, name, params=[ ]): + MethodDecl.__init__(self, name, params=params, ret=None) + +class ConstructorDefn(MethodDefn): + def __init__(self, decl, memberinits=[ ]): + MethodDefn.__init__(self, decl) + self.memberinits = memberinits + +class DestructorDecl(MethodDecl): + def __init__(self, name, virtual=False): + MethodDecl.__init__(self, name, params=[ ], ret=None, + virtual=virtual) +class DestructorDefn(MethodDefn): + def __init__(self, decl): MethodDefn.__init__(self, decl) + +##------------------------------ +# expressions +class ExprVar(Node): + def __init__(self, name): + Node.__init__(self) + self.name = name + +class ExprPrefixUnop(Node): + def __init__(self, expr, op): + self.expr = expr + self.op = op + +class ExprAddrOf(ExprPrefixUnop): + def __init__(self, expr): + ExprPrefixUnop.__init__(self, expr, '&') + +class ExprDeref(ExprPrefixUnop): + def __init__(self, expr): + ExprPrefixUnop.__init__(self, expr, '*') + +class ExprSelect(Node): + def __init__(self, obj, op, field): + Node.__init__(self) + self.obj = obj + self.op = op + self.field = field + +class ExprAssn(Node): + def __init__(self, lhs, rhs): + Node.__init__(self) + self.lhs = lhs + self.rhs = rhs + +class ExprCall(Node): + def __init__(self, func, args=[ ]): + Node.__init__(self) + self.func = func + self.args = args + +class ExprNew(ExprCall): + # XXX taking some poetic license ... + def __init__(self, type, args=[ ]): + ExprCall.__init__(self, ExprVar(type.name), args) + +class ExprDelete(Node): + def __init__(self, obj): + Node.__init__(self) + self.obj = obj + +class ExprMemberInit(ExprCall): + def __init__(self, member, args=[ ]): + ExprCall.__init__(self, member, args) + +##------------------------------ +# statements etc. +class StmtBlock(Block): + def __init__(self): + Block.__init__(self) + +class StmtDecl(Node): + def __init__(self, decl): + Node.__init__(self) + self.decl = decl + +class Label(Node): + def __init__(self, name): + Node.__init__(self) + self.name = name + +class CaseLabel(Node): + def __init__(self, name): + Node.__init__(self) + self.name = name + +class DefaultLabel(Node): + def __init__(self): + Node.__init__(self) + +class StmtIf(Node): + def __init__(self, cond): + Node.__init__(self) + self.cond = cond + self.ifb = Block() + self.elseb = None + def addifstmt(self, stmt): + self.ifb.addstmt(stmt) + def addelsestmt(self, stmt): + if self.elseb is None: self.elseb = Block() + self.elseb.addstmt(stmt) + +class StmtSwitch(Block): + def __init__(self, expr): + Block.__init__(self) + self.expr = expr + + def addcase(self, case, block): + '''NOTE: |case| is not checked for uniqueness''' + self.addstmt(case) + self.addstmt(block) + +class StmtExpr(Node): + def __init__(self, expr): + Node.__init__(self) + self.expr = expr + +class StmtReturn(Node): + def __init__(self, expr=None): + Node.__init__(self) + self.expr = expr diff --git a/ipc/ipdl/ipdl/cxx/cgen.py b/ipc/ipdl/ipdl/cxx/cgen.py new file mode 100644 index 00000000000..e0da8b6f9f0 --- /dev/null +++ b/ipc/ipdl/ipdl/cxx/cgen.py @@ -0,0 +1,337 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (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.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# Contributor(s): +# Chris Jones +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +import sys + +from ipdl.cgen import CodePrinter +from ipdl.cxx.ast import Visitor + +class CxxCodeGen(CodePrinter, Visitor): + def __init__(self, outf=sys.stdout, indentCols=4): + CodePrinter.__init__(self, outf, indentCols) + + def cgen(self, cxxfile): + cxxfile.accept(self) + + def visitWhitespace(self, ws): + self.write(ws.ws) + + def visitCppDirective(self, cd): + self.println('#%s %s'% (cd.directive, cd.rest)) + + def visitNamespace(self, ns): + self.println('namespace '+ ns.name +' {') + self.visitBlock(ns) + self.println('} // namespace '+ ns.name) + + def visitType(self, t): + ts = '' + if t.const: ts += 'const ' + ts += t.name + if t.ptr: ts += '*' + if t.ref: ts += '&' + self.write(ts) + + def visitTypeEnum(self, te): + self.write('enum') + if te.name: + self.write(' '+ te.name) + self.println(' {') + + self.indent() + nids = len(te.idnums) + for i, (id, num) in enumerate(te.idnums): + self.printdent(id) + if num: + self.write(' = '+ str(num)) + if i != (nids-1): + self.write(',') + self.println() + self.dedent() + self.printdent('}') + + + def visitTypedef(self, td): + self.printdent('typedef ') + td.fromtype.accept(self) + self.write(' ') + td.totype.accept(self) + self.println(';') + + def visitDecl(self, d): + d.type.accept(self) + if d.name: + self.write(' '+ d.name) + + def visitClass(self, c): + self.printdent('class') + if c.interface: + # FIXME/cjones: turn this "on" when we get the analysis + self.write(' /*NS_INTERFACE_CLASS*/') + if c.final: + self.write(' /*NS_FINAL_CLASS*/') + self.write(' '+ c.name) + + ninh = len(c.inherits) + if 0 < ninh: + self.println(' :') + self.indent() + for i, inherit in enumerate(c.inherits): + self.printdent() + inherit.accept(self) + if i != (ninh - 1): + self.println(',') + self.dedent() + self.println() + + self.printdentln('{') + self.indent() + + self.visitBlock(c) + + self.dedent() + self.printdentln('};') + + def visitInherit(self, inh): + self.write(inh.viz +' '+ inh.name) + + + def visitMethodDecl(self, md): + assert not (md.static and md.virtual) + if md.static: + self.write('static ') + if md.virtual: + self.write('virtual ') + if md.ret: + md.ret.accept(self) + self.write(' ') + self.write(md.name +'(') + self.writeDeclList(md.params) + self.write(')') + if md.const: + self.write(' const') + if md.pure: + self.write(' = 0') + + def visitMethodDefn(self, md): + self.printdent() + md.decl.accept(self) + self.println() + + self.printdentln('{') + self.indent() + self.visitBlock(md) + self.dedent() + self.printdentln('}') + + + def visitConstructorDecl(self, cd): + # FIXME/cjones: explicit when possible + self.visitMethodDecl(cd) + + def visitConstructorDefn(self, cd): + self.printdent() + cd.decl.accept(self) + if len(cd.memberinits): + self.println(' :') + self.indent() + ninits = len(cd.memberinits) + for i, init in enumerate(cd.memberinits): + self.printdent() + init.accept(self) + if i != (ninits-1): + self.println(',') + self.dedent() + self.println() + + self.printdentln('{') + self.indent() + + self.visitBlock(cd) + + self.dedent() + self.printdentln('}') + + + def visitDestructorDecl(self, dd): + if dd.virtual: + self.write('virtual ') + self.write('~'+ dd.name +'()') + + def visitDestructorDefn(self, dd): + self.printdent() + dd.decl.accept(self) + self.println() + + self.printdentln('{') + self.indent() + + self.visitBlock(dd) + + self.dedent() + self.printdentln('}') + + + def visitExprVar(self, ev): + self.write(ev.name) + + def visitExprPrefixUnop(self, e): + self.write(e.op) + self.write('(') + e.expr.accept(self) + self.write(')') + + def visitExprSelect(self, es): + es.obj.accept(self) + self.write(es.op + es.field) + + def visitExprAssn(self, ea): + ea.lhs.accept(self) + self.write(' = ') + ea.rhs.accept(self) + + def visitExprCall(self, ec): + ec.func.accept(self) + self.write('(') + self.writeExprList(ec.args) + self.write(')') + + def visitExprNew(self, en): + self.write('new ') + self.visitExprCall(en) + + def visitExprDelete(self, ed): + self.write('delete ') + ed.accept(self) + + + def visitStmtBlock(self, b): + self.printdentln('{') + self.indent() + self.visitBlock(b) + self.dedent() + self.printdentln('}') + + def visitLabel(self, label): + self.dedent() # better not be at global scope ... + self.printdentln(label.name +':') + self.indent() + + def visitCaseLabel(self, cl): + self.dedent() + self.printdentln('case '+ cl.name +':') + self.indent() + + def visitDefaultLabel(self, dl): + self.dedent() + self.printdentln('default:') + self.indent() + + + def visitStmtIf(self, si): + self.printdent('if (') + si.cond.accept(self) + self.println(') {') + self.indent() + si.ifb.accept(self) + self.dedent() + self.printdentln('}') + + if si.elseb is not None: + self.printdentln('else {') + self.indent() + si.elseb.accept(self) + self.dedent() + self.println('}') + + + def visitStmtSwitch(self, sw): + self.printdent('switch (') + sw.expr.accept(self) + self.println(') {') + self.indent() + self.visitBlock(sw) + self.dedent() + self.printdentln('}') + + + def visitStmtDecl(self, sd): + self.printdent() + sd.decl.accept(self) + self.println(';') + + + def visitStmtExpr(self, se): + self.printdent() + se.expr.accept(self) + self.println(';') + + + def visitStmtReturn(self, sr): + self.printdent('return') + if sr.expr: + self.write (' ') + sr.expr.accept(self) + self.println(';') + + + def writeDeclList(self, decls): + # FIXME/cjones: try to do nice formatting of these guys + + ndecls = len(decls) + if 0 == ndecls: + return + elif 1 == ndecls: + decls[0].accept(self) + return + + self.indent() + self.indent() + self.indent() + for i, decl in enumerate(decls): + self.println() + self.printdent() + decl.accept(self) + if i != (ndecls-1): + self.write(',') + self.dedent() + self.dedent() + self.dedent() + + def writeExprList(self, exprs): + # FIXME/cjones: try to do nice formatting and share code with + # writeDeclList() + nexprs = len(exprs) + for i, expr in enumerate(exprs): + expr.accept(self) + if i != (nexprs-1): + self.write(', ') diff --git a/ipc/ipdl/ipdl/lower.py b/ipc/ipdl/ipdl/lower.py new file mode 100644 index 00000000000..8ee6193b5b4 --- /dev/null +++ b/ipc/ipdl/ipdl/lower.py @@ -0,0 +1,729 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (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.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# Contributor(s): +# Chris Jones +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +import os +from copy import deepcopy + +from ipdl.ast import Visitor, ASYNC, SYNC, RPC, IN, OUT, INOUT +import ipdl.cxx.ast as cxx + + +def _joinProtocolNamespacesName(sep, p, pname): + return sep.join([ ns.namespace for ns in p.namespaces ] + [ pname ]) + +def _protocolIncludeGuard(p, pname): + return _joinProtocolNamespacesName('_', p, pname) +'_h' + +def _protocolHeaderFilename(p, pname): + return _joinProtocolNamespacesName(os.path.sep, p, pname)+ '.h' + +def _protocolHeaderName(pname): + return pname +'Protocol' + + +class _struct: pass + +class LowerToCxx: + def lower(self, tu): + '''returns a list of cxx.File representing the lowered form of |tu|''' + pname = _protocolHeaderName(tu.protocol.name) + pheader = cxx.File(pname +'.h') + GenerateProtocolHeader().lower(tu, pname, pheader) + + parentname = pname +'Parent' + parentheader = cxx.File(parentname +'.h') + GenerateProtocolParentHeader().lower( + tu, pname, parentname, parentheader) + + childname = pname +'Child' + childheader = cxx.File(childname +'.h') + GenerateProtocolChildHeader().lower( + tu, pname, childname, childheader) + + return pheader, parentheader, childheader + + +##----------------------------------------------------------------------------- +class GenerateProtocolHeader(Visitor): + '''creates a "generic" protocol header from an IPDL AST''' + def __init__(self): + self.pname = None + self.file = None + self.ns = None # the namespace we toss all this stuff into + # list of typedefs for the protocol namespace. these are + # produced from various places in the AST and collected here + self.typedefs = [ ] + + def lower(self, tu, protocol, outcxxfile): + self.pname = protocol + self.file = outcxxfile + tu.accept(self) + + + def typedef(self, t, name): + self.typedefs.append(cxx.Typedef(cxx.Type(t), cxx.Type(name))) + + def injectTypedefs(self, scope): + for tdef in self.typedefs: + scope.addstmt(tdef) + + + def visitTranslationUnit(self, tu): + f = self.file + + f.addthing(cxx.Whitespace('''// +// Automatically generated by ipdlc. +// Edit at your own risk +// + +''')) + includeguard = _protocolIncludeGuard(tu.protocol, self.pname) + f.addthing(cxx.CppDirective('ifndef', includeguard)) + f.addthing(cxx.CppDirective('define', includeguard)) + f.addthing(cxx.Whitespace.NL) + + Visitor.visitTranslationUnit(self, tu) + + f.addthing(cxx.Whitespace.NL) + f.addthing(cxx.CppDirective('endif', '// ifndef '+ includeguard)) + + + def visitCxxInclude(self, inc): + self.file.addthing(cxx.CppDirective('include', '"'+ inc.file +'"')) + + + def visitProtocolInclude(self, inc): + p = inc.tu.protocol + self.file.addthing( + cxx.CppDirective( + 'include', + '"'+ _protocolHeaderFilename(p, _protocolHeaderName(p.name)) +'"')) + # FIXME/cjones: not clear what types we need from other header +# if p.decl.fullname is not None: +# self.typedef(p.decl.fullname, p.decl.shortname) + + + def visitUsingStmt(self, using): + if using.decl.fullname is not None: + self.typedef(using.decl.fullname, using.decl.shortname) + + + def visitProtocol(self, p): + self.file.addthing(cxx.Whitespace.NL) + + # construct the namespace into which we'll stick all our decls + if 0 == len(p.namespaces): + self.ns = self.file + else: + innernamespaces = p.namespaces[1:] + self.ns = cxx.Namespace(p.namespaces[0].namespace) + self.file.addthing(self.ns) + + for ns in innernamespaces: + innerns = cxx.Namespace(ns.namespace) + self.ns.addstmt(innerns) + self.ns = innerns + + ns = cxx.Namespace(self.pname) + self.ns.addstmt(ns) + self.ns = ns + ns.addstmt(cxx.Whitespace.NL) + ns.addstmt(cxx.Whitespace.NL) + + # previsit the messages and stash away some common info used + # several times later + for md in p.messageDecls: + md.accept(self) + + # generate parent and child interfaces + iparent = cxx.Class('IParent', interface=True) + iparent.addstmt(cxx.Label('protected')) + self.injectTypedefs(iparent) + iparent.addstmt(cxx.Whitespace.NL) + + p.accept(GenerateParentInterface(iparent)) + ns.addstmt(iparent) + ns.addstmt(cxx.Whitespace.NL) + + ns.addstmt(cxx.Whitespace.NL) + ichild = cxx.Class('IChild', interface=True) + ichild.addstmt(cxx.Label('protected')) + self.injectTypedefs(ichild) + ichild.addstmt(cxx.Whitespace.NL) + + p.accept(GenerateChildInterface(ichild)) + ns.addstmt(ichild) + ns.addstmt(cxx.Whitespace.NL) + + # TODO + for ts in p.transitionStmts: + ts.accept(self) + ns.addstmt(cxx.StmtDecl(cxx.Decl(cxx.TypeEnum('State'), ''))) + ns.addstmt(cxx.Whitespace.NL) + + # spit out message type enum and classes + msgstart = self.pname +'MsgStart << 12' + msgenum = cxx.TypeEnum(self.pname +'MsgType') + msgenum.addId(self.pname +'Start', msgstart) + msgenum.addId(self.pname +'PreStart', '('+ msgstart +') - 1') + + for md in p.messageDecls: + msgenum.addId(md._cxx.id +'__ID') + if md.decl.type.hasReply(): + msgenum.addId(md._cxx.replyid +'__ID') + + msgenum.addId(self.pname +'End') + ns.addstmt(cxx.StmtDecl(cxx.Decl(msgenum, ''))) + + for md in p.messageDecls: + ns.addstmt(generateMessageClass(md, self.injectTypedefs)) + if md.decl.type.hasReply(): + ns.addstmt(generateReplyClass(md, self.injectTypedefs)) + + ns.addstmt(cxx.Whitespace.NL) + ns.addstmt(cxx.Whitespace.NL) + + + def visitMessageDecl(self, md): + # where we squirrel away some common information + setattr(md, '_cxx', _struct()) + + md._cxx.params = [ ] + for param in md.inParams: + md._cxx.params.append(cxx.Decl(cxx.Type(param.type.name()), + param.progname)) + md._cxx.returns = [ ] + for param in md.outParams: + md._cxx.returns.append(cxx.Decl(cxx.Type(param.type.name()), + param.progname)) + + # generate C++ interface to message sending/handling + method = cxx.MethodDecl( + name=md.decl.progname, + params=[ ], + ret=cxx.Type('nsresult'), + virtual=True) + for param in md._cxx.params: + pcopy = deepcopy(param) + pcopy.type.const = True + pcopy.type.ref = True + method.params.append(pcopy) + for ret in md._cxx.returns: + rcopy = deepcopy(ret) + rcopy.type.ptr = True + method.params.append(rcopy) + md._cxx.method = method + + # the ID is used by the IPC layer only + md._cxx.id = 'Msg_%s'% (md.decl.progname) + md._cxx.nsid = '%s::%s'% (self.pname, md._cxx.id) + if md.decl.type.hasReply(): + md._cxx.replyid = 'Reply_%s'% (md.decl.progname) + md._cxx.nsreplyid = '%s::%s'% (self.pname, md._cxx.replyid) + + +class GenerateInterface(Visitor): + def __init__(self, iface, name): + self.iface = iface + self.name = name + + def visitProtocol(self, p): + ifc = self.iface + n = self.name + + ifc.addstmt(cxx.Label('public')) + nmsgs = len(p.messageDecls) + for i, msgdecl in enumerate(p.messageDecls): + msgdecl.accept(self) + + ifc.addstmt(cxx.Whitespace.NL) + ifc.addstmt(cxx.Label('protected')) + ifc.addstmt(cxx.ConstructorDefn( + cxx.ConstructorDecl(n))) + ifc.addstmt(cxx.DestructorDefn( + cxx.DestructorDecl(n, virtual=True))) + + # disable unwanted ctors/operators + ifc.addstmt(cxx.Whitespace.NL) + ifc.addstmt(cxx.Label('private')) + ref = cxx.Type(n, ref=True) + constref = cxx.Type(n, const=True, ref=True) + ifc.addstmt(cxx.StmtDecl( + cxx.ConstructorDecl(n, + params=[ cxx.Decl(constref, '')]))) + ifc.addstmt(cxx.StmtDecl( + cxx.MethodDecl('operator=', + params= [ cxx.Decl(constref, '') ], + ret=ref))) + + def visitMessageDecl(self, md): + if self.msgComesIn(md.decl.type): + method = deepcopy(md._cxx.method) + method.pure = True + self.iface.addstmt(cxx.StmtDecl(method)) + self.iface.addstmt(cxx.Whitespace.NL) + + +class GenerateParentInterface(GenerateInterface): + def __init__(self, iparent): + GenerateInterface.__init__(self, iparent, iparent.name) + def msgComesIn(self, mtype): return mtype.isIn() or mtype.isInout() + +class GenerateChildInterface(GenerateInterface): + def __init__(self, iparent): + GenerateInterface.__init__(self, iparent, iparent.name) + def msgComesIn(self, mtype): return mtype.isOut() or mtype.isInout() + + +def generateMsgClass(clsname, params, typedefInjector): + cls = cxx.Class(name=clsname, + inherits=[ cxx.Inherit('IPC::Message') ]) + cls.addstmt(cxx.Label('private')) + typedefInjector(cls) + cls.addstmt(cxx.Whitespace.NL) + + cls.addstmt(cxx.Label('public')) + + idenum = cxx.TypeEnum() + idenum.addId('ID', clsname +'__ID') + cls.addstmt(cxx.StmtDecl(cxx.Decl(idenum, ''))) + + # FIXME/cjones: need to handle "managed" messages + + constparams = deepcopy(params) + writestmts = [ ] + for cparam in constparams: + cparam.type.const = True + cparam.type.ref = True + writestmts.append( + cxx.StmtExpr(cxx.ExprCall(cxx.ExprVar('IPC::WriteParam'), + [ cxx.ExprVar('this'), + cxx.ExprVar(cparam.name) ]))) + + # make the message constructor (serializer) + ctordecl = cxx.ConstructorDecl(clsname, params=constparams) + + superinit = cxx.ExprMemberInit( + cxx.ExprVar('IPC::Message'), + [ cxx.ExprVar('MSG_ROUTING_CONTROL'), + cxx.ExprVar('ID'), + cxx.ExprVar('PRIORITY_NORMAL') ]) + + ctor = cxx.ConstructorDefn(ctordecl, [ superinit ]) + for writestmt in writestmts: + ctor.addstmt(writestmt) + cls.addstmt(ctor) + + cls.addstmt(cxx.Whitespace.NL) + + # make the message deserializer + outparams = deepcopy(params) + for oparam in outparams: + oparam.type.ptr = True + + reader = cxx.MethodDefn( + cxx.MethodDecl( + 'Read', + params=([ cxx.Decl(cxx.Type('Message', ptr=True, const=True), + 'msg') ] + + outparams), + ret=cxx.Type('bool'), + static=True)) + + # avoid generating an unused variable when we don't deserialize + # anything. why generate the method anyway? it keeps other code + # consistent, and we might do some checking in here eventually + if len(outparams): + # hack + reader.addstmt( + cxx.StmtDecl(cxx.Decl(cxx.Type('void', ptr=True), 'iter = 0'))) + reader.addstmt(cxx.Whitespace.NL) + + for oparam in outparams: + cond = cxx.ExprPrefixUnop( + cxx.ExprCall(cxx.ExprVar('IPC::ReadParam'), + [ cxx.ExprVar('msg'), + cxx.ExprAddrOf(cxx.ExprVar('iter')), + cxx.ExprVar(oparam.name) ]), + '!') + ifstmt = cxx.StmtIf(cond) + # false isn't a var + ifstmt.addifstmt(cxx.StmtReturn(cxx.ExprVar('false'))) + reader.addstmt(ifstmt) + reader.addstmt(cxx.Whitespace.NL) + + # false isn't a var + reader.addstmt(cxx.StmtReturn(cxx.ExprVar('true'))) + + cls.addstmt(reader) + + return cls + +def generateMessageClass(md, typedefInjector): + return generateMsgClass(md._cxx.id, md._cxx.params, typedefInjector) + +def generateReplyClass(md, typedefInjector): + return generateMsgClass(md._cxx.replyid, md._cxx.returns, typedefInjector) + + +##----------------------------------------------------------------------------- +_channelTable = { + 'Async': [ 'mozilla', 'ipc', 'AsyncChannel' ], + 'Sync': [ 'mozilla', 'ipc', 'SyncChannel' ], + 'Rpc': [ 'mozilla', 'ipc', 'RPCChannel' ] +} + + +class GenerateProtocolActorHeader(Visitor): + def __init__(self, thisiface, thatiface): + self.thisiface = thisiface + self.thatiface = thatiface + self.clsname = None + self.pname = None + self.file = None + self.ns = None + + def lower(self, tu, pname, clsname, cxxHeaderFile): + self.pname = pname + self.clsname = clsname + self.file = cxxHeaderFile + tu.accept(self) + + def visitTranslationUnit(self, tu): + f = self.file + + f.addthing(cxx.Whitespace('''// +// Automatically generated by ipdlc. +// Edit at your own risk +// + +''')) + includeguard = _protocolIncludeGuard(tu.protocol, self.clsname) + f.addthing(cxx.CppDirective('ifndef', includeguard)) + f.addthing(cxx.CppDirective('define', includeguard)) + f.addthing(cxx.Whitespace.NL) + + mainheader = _protocolHeaderFilename(tu.protocol, self.pname) + f.addthing(cxx.CppDirective('include', '"'+ mainheader +'"')) + + tu.protocol.accept(self) + + f.addthing(cxx.Whitespace.NL) + f.addthing(cxx.CppDirective('endif', '// ifndef '+ includeguard)) + + + def visitProtocol(self, p): + channel = _channelTable[p.decl.type.sendSemantics.pretty] + channelname = '::'.join(channel) + channelfile = '/'.join(channel) +'.h' + if p.decl.type.isToplevel(): + self.channelsel = '.' + else: + self.channelsel = '->' + + self.file.addthing(cxx.CppDirective('include', '"'+ channelfile +'"')) + self.file.addthing(cxx.Whitespace.NL) + + # construct the namespace into which we'll stick all our decls + if 0 == len(p.namespaces): + self.ns = self.file + else: + innernamespaces = p.namespaces[1:] + self.ns = cxx.Namespace(p.namespaces[0].namespace) + self.file.addthing(self.ns) + + for ns in innernamespaces: + innerns = cxx.Namespace(ns.namespace) + self.ns.addstmt(innerns) + self.ns = innerns + + self.ns.addstmt(cxx.Whitespace.NL) + self.ns.addstmt(cxx.Whitespace.NL) + + iface = p.decl.fullname +'Protocol::'+ self.thatiface + cls = cxx.Class(self.clsname, + inherits=[ cxx.Inherit(iface), + cxx.Inherit(channelname +'::Listener') ], + final=True) + + cls.addstmt(cxx.Label('private')) + impliface = p.decl.fullname +'Protocol::'+ self.thisiface + cls.addstmt(cxx.Typedef(cxx.Type('IPC::Message'), + cxx.Type('Message'))) + cls.addstmt(cxx.Typedef(cxx.Type(channelname), + cxx.Type('Channel'))) + cls.addstmt(cxx.Typedef(cxx.Type(impliface), + cxx.Type(self.thisiface))) + cls.addstmt(cxx.Whitespace.NL) + + # TODO manager param to constructor, when protocol is managed + + cls.addstmt(cxx.Label('public')) + ctor = cxx.ConstructorDefn( + cxx.ConstructorDecl( + self.clsname, + [ cxx.Decl(cxx.Type(self.thisiface, ptr=True), 'aImpl') ]), + [ cxx.ExprMemberInit(cxx.ExprVar('mImpl'), + [ cxx.ExprVar('aImpl') ]) ]) + if p.decl.type.isToplevel(): + ctor.memberinits.append( + cxx.ExprMemberInit(cxx.ExprVar('mChannel'), + [ cxx.ExprVar('this') ])) + cls.addstmt(ctor) + cls.addstmt(cxx.Whitespace.NL) + + dtor = cxx.DestructorDefn( + cxx.DestructorDecl(self.clsname, virtual=True)) + cls.addstmt(dtor) + cls.addstmt(cxx.Whitespace.NL) + + if p.decl.type.isToplevel(): + # open + openmeth = cxx.MethodDefn( + cxx.MethodDecl( + 'Open', + params=[ cxx.Decl(cxx.Type('IPC::Channel', ptr=True), + 'aChannel'), + cxx.Decl(cxx.Type('MessageLoop', ptr=True), + 'aThread = 0') ], + ret=cxx.Type('bool'))) + openmeth.addstmt(cxx.StmtReturn( + cxx.ExprCall( + cxx.ExprSelect(cxx.ExprVar('mChannel'), '.', 'Open'), + [ cxx.ExprVar('aChannel'), cxx.ExprVar('aThread') ]))) + cls.addstmt(openmeth) + cls.addstmt(cxx.Whitespace.NL) + + # close + closemeth = cxx.MethodDefn(cxx.MethodDecl('Close')) + closemeth.addstmt(cxx.StmtExpr( + cxx.ExprCall( + cxx.ExprSelect(cxx.ExprVar('mChannel'), '.', 'Close')))) + cls.addstmt(closemeth) + cls.addstmt(cxx.Whitespace.NL) + + # incoming message dispatchers + self.asyncswitch = cxx.StmtSwitch( + cxx.ExprCall(cxx.ExprSelect(cxx.ExprVar('msg'), '.', 'type'), [ ])) + if p.decl.type.talksSync(): + self.syncswitch = deepcopy(self.asyncswitch) + if p.decl.type.talksRpc(): + self.rpcswitch = deepcopy(self.syncswitch) + + # implement child iface and add handlers to message switches + self.cls = cls + for md in p.messageDecls: + self.visitMessageDecl(md) + + # add default cases + default = cxx.StmtBlock() + default.addstmt(cxx.StmtReturn(cxx.ExprVar('MsgNotKnown'))) + + self.asyncswitch.addcase(cxx.DefaultLabel(), default) + if p.decl.type.talksSync(): + self.syncswitch.addcase(cxx.DefaultLabel(), default) + if p.decl.type.talksRpc(): + self.rpcswitch.addcase(cxx.DefaultLabel(), default) + + asynchandler = cxx.MethodDefn( + cxx.MethodDecl( + 'OnMessageReceived', virtual=True, + params=[ cxx.Decl(cxx.Type('Message', const=1, ref=1),'msg') ], + ret=cxx.Type('Result'))) + + if p.decl.type.talksSync(): + synchandler = deepcopy(asynchandler) + synchandler.decl.params.append(cxx.Decl( + cxx.Type('Message', ref=1, ptr=1), 'reply')) + + if p.decl.type.talksRpc(): + rpchandler = deepcopy(synchandler) + rpchandler.decl.name = 'OnCallReceived' + + asynchandler.addstmt(self.asyncswitch) + cls.addstmt(asynchandler) + cls.addstmt(cxx.Whitespace.NL) + + if p.decl.type.talksSync(): + synchandler.addstmt(self.syncswitch) + cls.addstmt(synchandler) + cls.addstmt(cxx.Whitespace.NL) + + if p.decl.type.talksRpc(): + rpchandler.addstmt(self.rpcswitch) + cls.addstmt(rpchandler) + cls.addstmt(cxx.Whitespace.NL) + + # private members and methods + + # TODO handle manager stuff: lookups, routing + + cls.addstmt(cxx.Label('private')) + cls.addstmt(cxx.StmtDecl(cxx.Decl(cxx.Type(self.thisiface, ptr=True), + 'mImpl'))) + channeltype = cxx.Type('Channel') + if p.decl.type.isManaged(): + channeltype.ptr = True # subprotocols inherit this + cls.addstmt(cxx.StmtDecl(cxx.Decl(channeltype, 'mChannel'))) + + self.ns.addstmt(cls) + self.ns.addstmt(cxx.Whitespace.NL) + self.ns.addstmt(cxx.Whitespace.NL) + + + def visitMessageDecl(self, md): + # TODO special handling of constructor messages + + # create method for "that" interface + if self.sendsMessage(md): + impl = cxx.MethodDefn(md._cxx.method) + + impl.addstmt(cxx.StmtDecl(cxx.Decl(cxx.Type('nsresult'), '__rv'))) + rv = cxx.ExprVar('__rv') + failif = cxx.StmtIf(rv) + failif.ifb.addstmt(cxx.StmtReturn(rv)) + + hasreply = md.decl.type.hasReply() + if hasreply: + impl.addstmt(cxx.StmtDecl( + cxx.Decl(cxx.Type('Message'), 'reply'))) + reply = cxx.ExprVar('reply') + impl.addstmt(cxx.Whitespace.NL) + + sendcall = cxx.ExprCall( + cxx.ExprSelect( + cxx.ExprVar('mChannel'), self.channelsel, 'Call'), + [ cxx.ExprNew(cxx.Type(md._cxx.nsid), + [ cxx.ExprVar(p.name) + for p in md._cxx.params ]) ]) + if hasreply: + sendcall.args.append(cxx.ExprAddrOf(reply)) + + # TODO special handling of actor handles + + impl.addstmt(cxx.StmtExpr(cxx.ExprAssn(rv, sendcall))) + if not hasreply: + impl.addstmt(cxx.StmtReturn(rv)) + self.cls.addstmt(impl) + self.cls.addstmt(cxx.Whitespace.NL) + else: + impl.addstmt(failif) + + unpack = cxx.ExprCall(cxx.ExprVar(md._cxx.nsreplyid +'::Read'), + [ cxx.ExprAddrOf(cxx.ExprVar('reply')) ] + + [ cxx.ExprVar(r.name) + for r in md._cxx.returns ]) + errhandle = cxx.StmtIf(cxx.ExprPrefixUnop(unpack, '!')) + errhandle.ifb.addstmt(cxx.StmtReturn( + cxx.ExprVar('MsgPayloadError'))) + impl.addstmt(errhandle) + + # TODO special handling of actor handles + + impl.addstmt(cxx.StmtReturn(cxx.ExprVar('NS_OK'))) + + self.cls.addstmt(impl) + self.cls.addstmt(cxx.Whitespace.NL) + + + # create case for this message in the big handler switch statement + if self.receivesMessage(md): + case = cxx.CaseLabel(md._cxx.nsid +'__ID') + block = cxx.StmtBlock() + + rv = cxx.ExprVar('__rv') + for param in md._cxx.params: + block.addstmt(cxx.StmtDecl(param)) + for ret in md._cxx.returns: + block.addstmt(cxx.StmtDecl(ret)) + block.addstmt(cxx.Whitespace.NL) + + unpack = cxx.ExprCall(cxx.ExprVar(md._cxx.nsid +'::Read'), + [ cxx.ExprAddrOf(cxx.ExprVar('msg')) ] + + [ cxx.ExprAddrOf(cxx.ExprVar(p.name)) + for p in md._cxx.params ]) + errhandle = cxx.StmtIf(cxx.ExprPrefixUnop(unpack, '!')) + errhandle.ifb.addstmt(cxx.StmtReturn( + cxx.ExprVar('MsgPayloadError'))) + block.addstmt(errhandle) + + # TODO special handling of actor handles + + callimpl = cxx.ExprCall( + cxx.ExprSelect(cxx.ExprVar('mImpl'), '->', + md.decl.progname), [ ]) + callimpl.args += [ cxx.ExprVar(p.name) for p in md._cxx.params ] + callimpl.args += [ cxx.ExprAddrOf(cxx.ExprVar(r.name)) + for r in md._cxx.returns ] + errhandle = cxx.StmtIf(callimpl) + errhandle.ifb.addstmt(cxx.StmtReturn( + cxx.ExprVar('MsgValueError'))) + block.addstmt(errhandle) + + # TODO special handling of actor handles + + if md.decl.type.hasReply(): + replymsg = cxx.ExprNew( + cxx.Type(md._cxx.nsreplyid), + [ cxx.ExprVar(r.name) for r in md._cxx.returns ]) + block.addstmt(cxx.StmtExpr(cxx.ExprAssn(cxx.ExprVar('reply'), + replymsg))) + + block.addstmt(cxx.StmtReturn(cxx.ExprVar('MsgProcessed'))) + + if md.decl.type.isAsync(): + self.asyncswitch.addcase(case, block) + elif md.decl.type.isSync(): + self.syncswitch.addcase(case, block) + else: + self.rpcswitch.addcase(case, block) + + +class GenerateProtocolParentHeader(GenerateProtocolActorHeader): + def __init__(self): + GenerateProtocolActorHeader.__init__(self, 'IParent', 'IChild') + + def sendsMessage(self, md): + return not md.decl.type.isIn() + + def receivesMessage(self, md): + return md.decl.type.isInout() or md.decl.type.isIn() + +class GenerateProtocolChildHeader(GenerateProtocolActorHeader): + def __init__(self): + GenerateProtocolActorHeader.__init__(self, 'IChild', 'IParent') + + def sendsMessage(self, md): + return not md.decl.type.isOut() + + def receivesMessage(self, md): + return md.decl.type.isInout() or md.decl.type.isOut() diff --git a/ipc/ipdl/ipdl/parser.py b/ipc/ipdl/ipdl/parser.py new file mode 100644 index 00000000000..01fd8b5a0e8 --- /dev/null +++ b/ipc/ipdl/ipdl/parser.py @@ -0,0 +1,381 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (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.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# Contributor(s): +# Chris Jones +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +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) + +# we want PLY to generate its output in the module directory, not wherever +# the user chooses to run ipdlc from +_thisdir, _ = os.path.split(_getcallerpath()) + +##----------------------------------------------------------------------------- + +class Parser: + # when we reach an |include protocol "foo.ipdl";| 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, debug=0): + self.debug = debug + self.filename = None + self.loc = None # not always up to date + self.lexer = None + self.parser = None + self.tu = TranslationUnit() + + def parse(self, input, filename): + realpath = os.path.abspath(filename) + if realpath in Parser.parsed: + return Parser.parsed[realpath].tu + + self.lexer = lex.lex(debug=self.debug, + optimize=not self.debug, + lextab="ipdl_lextab", + outputdir=_thisdir) + self.parser = yacc.yacc(debug=self.debug, + optimize=not self.debug, + tabmodule="ipdl_yacctab", + outputdir=_thisdir) + self.filename = filename + self.tu.filename = realpath + + Parser.parsed[realpath] = self + Parser.parseStack.append(Parser.current) + Parser.current = self + + ast = self.parser.parse(input=input, lexer=self.lexer, + debug=self.debug) + + Parser.current = Parser.parseStack.pop() + return ast + + # 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', + 'async', + 'call', + 'goto', + 'in', + 'include', + 'inout', + 'manager', + 'manages', + 'namespace', + 'out', + 'protocol', + 'recv', + 'returns', + 'rpc', + 'send', + 'share', + 'sync', + 'transfer', + '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'\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): + includeStackStr = Parser.includeStackString() + raise Exception, '%s%s: lexically invalid characters %s'% ( + includeStackStr, Loc(Parser.current.filename, t.lineno), str(t)) + +##----------------------------------------------------------------------------- + +def p_TranslationUnit(p): + """TranslationUnit : Preamble NamespacedProtocolDefn""" + tu = Parser.current.tu + for stmt in p[1]: + if isinstance(stmt, CxxInclude): tu.addCxxInclude(stmt) + elif isinstance(stmt, ProtocolInclude): tu.addProtocolInclude(stmt) + elif isinstance(stmt, UsingStmt): tu.addUsingStmt(stmt) + else: + assert 0 + tu.protocol = p[2] + 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 + | ProtocolIncludeStmt + | UsingStmt""" + p[0] = p[1] + +def p_CxxIncludeStmt(p): + """CxxIncludeStmt : INCLUDE STRING""" + p[0] = CxxInclude(locFromTok(p, 1), p[2]) + +def p_ProtocolIncludeStmt(p): + """ProtocolIncludeStmt : INCLUDE PROTOCOL STRING""" + loc = locFromTok(p, 1) + Parser.current.loc = loc + + inc = ProtocolInclude(loc, p[3]) + inc.tu = Parser().parse(open(inc.file).read(), inc.file) + p[0] = inc + +def p_UsingStmt(p): + """UsingStmt : USING CxxType""" + p[0] = UsingStmt(locFromTok(p, 1), p[2]) + +##-------------------- +## Protocol definition +def p_NamespacedProtocolDefn(p): + """NamespacedProtocolDefn : NAMESPACE ID '{' NamespacedProtocolDefn '}' + | ProtocolDefn""" + if 2 == len(p): + p[0] = p[1] + else: + protocol = p[4] + protocol.addOuterNamespace(Namespace(locFromTok(p, 1), p[2])) + p[0] = protocol + +def p_ProtocolDefn(p): + """ProtocolDefn : SendSemanticsQual PROTOCOL ID '{' ManagerStmtOpt ManagesStmts MessageDecls TransitionStmts '}' ';'""" + protocol = Protocol(locFromTok(p, 2)) + protocol.name = p[3] + protocol.sendSemantics = p[1] + protocol.manager = p[5] + protocol.addManagesStmts(p[6]) + protocol.addMessageDecls(p[7]) + protocol.addTransitionStmts(p[8]) + p[0] = protocol + +def p_ManagesStmts(p): + """ManagesStmts : ManagesStmts ManagesStmt + | """ + if 1 == len(p): + p[0] = [ ] + else: + p[1].append(p[2]) + p[0] = p[1] + +def p_ManagerStmtOpt(p): + """ManagerStmtOpt : MANAGER ID ';' + | """ + if 1 == len(p): + p[0] = None + else: + p[0] = ManagerStmt(locFromTok(p, 1), p[2]) + +def p_ManagesStmt(p): + """ManagesStmt : MANAGES ID ';'""" + p[0] = ManagesStmt(locFromTok(p, 1), p[2]) + +def p_MessageDecls(p): + """MessageDecls : MessageDecls MessageDecl ';' + | MessageDecl ';'""" + if 3 == len(p): + p[0] = [ p[1] ] + else: + p[1].append(p[2]) + p[0] = p[1] + +def p_MessageDecl(p): + """MessageDecl : SendSemanticsQual DirectionQual MessageBody""" + msg = p[3] + msg.sendSemantics = p[1] + msg.direction = p[2] + p[0] = msg + +def p_MessageBody(p): + """MessageBody : MessageId MessageInParams MessageOutParams""" + # FIXME/cjones: need better loc info: use one of the quals + msg = MessageDecl(locFromTok(p, 1)) + msg.name = p[1] + msg.addInParams(p[2]) + msg.addOutParams(p[3]) + p[0] = msg + +def p_MessageId(p): + """MessageId : ID + | '~' ID""" + if 3 == len(p): + p[1] += p[2] # munge dtor name to "~Foo". handled later + p[0] = 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_TransitionStmts(p): + """TransitionStmts : """ + # FIXME/cjones: impl + p[0] = [ ] + +##-------------------- +## Minor stuff +def p_SendSemanticsQual(p): + """SendSemanticsQual : ASYNC + | RPC + | SYNC + | """ + if 1 == len(p): + p[0] = ASYNC + return + + s = p[1] + if 'async' == s: p[0] = ASYNC + elif 'rpc' == s: p[0] = RPC + elif 'sync'== s: p[0] = SYNC + else: + assert 0 + +def p_DirectionQual(p): + """DirectionQual : IN + | INOUT + | OUT""" + s = p[1] + if 'in' == s: p[0] = IN + elif 'inout' == s: p[0] = INOUT + elif 'out' == s: p[0] = OUT + else: + assert 0 + +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 : ID ID""" + loc = locFromTok(p, 1) + p[0] = Param(loc, + TypeSpec(loc, QualifiedId(loc, p[1])), + p[2]) + +##-------------------- +## C++ stuff +def p_CxxType(p): + """CxxType : QualifiedID + | ID""" + if isinstance(p[0], QualifiedId): + p[0] = TypeSpec(p[1].loc, p[1]) + else: + loc = locFromTok(p, 1) + p[0] = TypeSpec(loc, QualifiedId(loc, p[1])) + +def p_QualifiedID(p): + """QualifiedID : QualifiedID COLONCOLON ID + | ID COLONCOLON ID""" + if isinstance(p[1], QualifiedId): + p[1].qualify(p[3]) + p[0] = p[1] + else: + p[0] = QualifiedId(locFromTok(p, 1), p[3]) + +def p_error(t): + includeStackStr = Parser.includeStackString() + raise Exception, '%s%s: syntax error near "%s"'% ( + includeStackStr, Loc(Parser.current.filename, t.lineno), t.value) diff --git a/ipc/ipdl/ipdl/type.py b/ipc/ipdl/ipdl/type.py new file mode 100644 index 00000000000..23ccb965024 --- /dev/null +++ b/ipc/ipdl/ipdl/type.py @@ -0,0 +1,679 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (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.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# Contributor(s): +# Chris Jones +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +import sys + +from ipdl.ast import CxxInclude, Decl, Loc, QualifiedId, TypeSpec, UsingStmt, Visitor, ASYNC, SYNC, RPC, IN, OUT, INOUT +import ipdl.builtin as builtin + +class Type: + # Is this a C++ type? + def isCxx(): + return False + # Is this an IPDL type? + def isIPDL(): + return False + # Can this type appear in IPDL programs? + def isVisible(): + return False + def isVoid(self): + return False + def typename(self): + return self.__class__.__name__ + + def name(self): raise Exception, 'NYI' + def fullname(self): raise Exception, 'NYI' + +class VoidType(Type): + # the following are not a type-o's (hah): void is both a Cxx and IPDL type + def isCxx(): + return True + def isIPDL(): + return True + def isVisible(self): + return True + def isVoid(self): + return True + + def name(self): return 'void' + def fullname(self): return 'void' + +VOID = VoidType() + +##-------------------- +class CxxType(Type): + def isCxx(self): + return True + def isBuiltin(self): + return False + def isImported(self): + return False + def isGenerated(self): + return False + def isVisible(self): + return True + +class BuiltinCxxType(CxxType): + def __init__(self, qname): + assert isinstance(qname, QualifiedId) + self.loc = qname.loc + self.qname = qname + def isBuiltin(self): return True + + def name(self): + return self.qname.baseid + def fullname(self): + return str(self.qname) + +class ImportedCxxType(CxxType): + def __init__(self, qname): + assert isinstance(qname, QualifiedId) + self.loc = qname.loc + self.qname = qname + def isImported(self): return True + + def name(self): + return self.qname.baseid + def fullname(self): + return str(self.qname) + +class GeneratedCxxType(CxxType): + def isGenerated(self): return True + def isVisible(self): return False + +##-------------------- +class IPDLType(Type): + def isIPDL(self): return True + def isVisible(self): return True + + def isAsync(self): return self.sendSemantics is ASYNC + def isSync(self): return self.sendSemantics is SYNC + def isRpc(self): return self.sendSemantics is RPC + + def talksAsync(self): return True + def talksSync(self): return self.isSync() or self.isRpc() + def talksRpc(self): return self.isRpc() + + def hasReply(self): return self.isSync() or self.isRpc() + + def needsMoreJuiceThan(self, o): + return (o.isAsync() and not self.isAsync() + or o.isSync() and self.isRpc()) + +class MessageType(IPDLType): + def __init__(self, sendSemantics, direction, + ctor=False, dtor=False, cdtype=None): + assert not (ctor and dtor) + assert not (ctor or dtor) or type is not None + + self.sendSemantics = sendSemantics + self.direction = direction + self.params = [ ] + self.returns = [ ] + self.ctor = ctor + self.dtor = dtor + self.cdtype = cdtype + def isMessage(self): return True + + def isCtor(self): return self.ctor + def isDtor(self): return self.dtor + def constructedType(self): return self.cdtype + + def isIn(self): return self.direction is IN + def isOut(self): return self.direction is OUT + def isInout(self): return self.direction is INOUT + +class ProtocolType(IPDLType): + def __init__(self, qname, sendSemantics): + self.qname = qname + self.sendSemantics = sendSemantics + self.manager = None + self.manages = [ ] + def isProtocol(self): return True + + def name(self): + return self.qname.baseid + def fullname(self): + return str(self.qname) + + def managedBy(self, mgr): + self.manager = mgr + + def isManager(self, pt): + for managed in self.manages: + if pt is managed: + return True + return False + + def isManaged(self): + return self.manager is not None + def isToplevel(self): + return not self.isManaged() + +##-------------------- +_builtinloc = Loc('', 0) +def makeBuiltinUsing(tname): + quals = tname.split('::') + base = quals.pop() + quals = quals[0:] + return UsingStmt(_builtinloc, + TypeSpec(_builtinloc, + QualifiedId(_builtinloc, base, quals))) + +builtinUsing = [ makeBuiltinUsing(t) for t in builtin.Types ] +builtinIncludes = [ CxxInclude(_builtinloc, f) for f in builtin.Includes ] + +##-------------------- +def errormsg(loc, fmt, *args): + while not isinstance(loc, Loc): + if loc is None: loc = Loc.NONE + else: loc = loc.loc + return '%s: error: %s'% (str(loc), fmt % args) + + +class SymbolTable: + def __init__(self, errors): + self.errors = errors + self.scopes = [ { } ] # stack({}) + self.globalScope = self.scopes[0] + self.currentScope = self.globalScope + + def enterScope(self, node): + assert (isinstance(self.scopes[0], dict) + and self.globalScope is self.scopes[0]) + assert (isinstance(self.currentScope, dict)) + + if not hasattr(node, 'symtab'): + node.symtab = { } + + self.scopes.append(node.symtab) + self.currentScope = self.scopes[-1] + + def exitScope(self, node): + symtab = self.scopes.pop() + assert self.currentScope is symtab + + self.currentScope = self.scopes[-1] + + assert (isinstance(self.scopes[0], dict) + and self.globalScope is self.scopes[0]) + assert isinstance(self.currentScope, dict) + + def lookup(self, sym): + # NB: since IPDL doesn't allow any aliased names of different types, + # it doesn't matter in which order we walk the scope chain to resolve + # |sym| + for scope in self.scopes: + decl = scope.get(sym, None) + if decl is not None: return decl + return None + + def declare(self, decl): + assert decl.progname or decl.shortname or decl.fullname + assert decl.loc + assert decl.type + + def tryadd(name): + olddecl = self.lookup(name) + if olddecl is not None: + self.errors.append(errormsg( + decl.loc, + "redeclaration of symbol `%s', first declared at %s", + name, olddecl.loc)) + return + self.currentScope[name] = decl + decl.scope = self.currentScope + + if decl.progname: tryadd(decl.progname) + if decl.shortname: tryadd(decl.shortname) + if decl.fullname: tryadd(decl.fullname) + + +class TypeCheck(Visitor): + '''This pass sets the .type attribute of every AST node. For some +nodes, the type is meaningless and it is set to "VOID." This pass +also sets the .decl attribute of AST nodes for which that is relevant; +a decl says where, with what type, and under what name(s) a node was +declared. + +With this information, it finally type checks the AST.''' + + def __init__(self): + # NB: no IPDL compile will EVER print a warning. A program has + # one of two attributes: it is either well typed, or not well typed. + self.errors = [ ] # [ string ] + self.symtab = SymbolTable(self.errors) + + def check(self, tu, errout=sys.stderr): + tu.cxxIncludes = builtinIncludes + tu.cxxIncludes + + # tag each relevant node with "decl" information, giving type, name, + # and location of declaration + tu.accept(GatherDecls(builtinUsing, self.symtab, self.errors)) + if len(self.errors): + # no point in checking types if we couldn't even resolve symbols + self.reportErrors(errout) + return False + + # now that the nodes have decls, type checking is much easier. + tu.accept(CheckTypes(self.symtab, self.errors)) + if (len(self.errors)): + # no point in later passes if type checking fails + self.reportErrors(errout) + return False + + return True + + def reportErrors(self, errout): + for error in self.errors: + print >>errout, error + + +class GatherDecls(Visitor): + def __init__(self, builtinUsing, symtab, errors): + self.builtinUsing = builtinUsing + self.symtab = symtab + self.errors = errors + self.visited = set() # set(filename) + self.depth = 0 + + def visitTranslationUnit(self, tu): + # all TranslationUnits declare symbols in global scope + if tu.filename in self.visited: + return + self.visited.add(tu.filename) + self.depth += 1 + + # bit of a hack here --- we want the builtin |using| + # statements to be added to the symbol table before anything + # else, but we also want them in the top-level translation + # unit's list of using stmts so that we can use them later + # down the pipe. so we add them to the symbol table before + # anything else, and prepend them to the top-level TU after + # it's visited all its |using| decls + if 1 == self.depth: + for using in self.builtinUsing: + udecl = Decl(using.loc) + udecl.shortname = using.type.basename() + fullname = str(using.type) + if udecl.shortname != fullname: + udecl.fullname = fullname + udecl.type = BuiltinCxxType(using.type.spec) + self.symtab.declare(udecl) + using.decl = udecl + + p = tu.protocol + + # FIXME/cjones: it's a little weird and counterintuitive to put + # both the namespace and non-namespaced name in the global scope. + # try to figure out something better; maybe a type-neutral |using| + # that works for C++ and protocol types? + pdecl = Decl(p.loc) + pdecl.shortname = p.name + + fullname = QualifiedId(p.loc, p.name, + [ ns.namespace for ns in p.namespaces ]) + if len(fullname.quals): + pdecl.fullname = str(fullname) + + pdecl.type = ProtocolType(fullname, p.sendSemantics) + pdecl.body = p + self.symtab.declare(pdecl) + + p.decl = pdecl + p.type = pdecl.type + + # make sure we have decls for all dependent protocols + for pinc in tu.protocolIncludes: + pinc.accept(self) + + # each protocol has its own scope for types brought in through |using| + self.symtab.enterScope(tu) + + # and for all imported C++ types + for using in tu.using: + using.accept(self) + + # (see long comment above) + if 1 == self.depth: + tu.using = self.builtinUsing + tu.using + + # grab symbols in the protocol itself + p.accept(self) + + self.symtab.exitScope(tu) + + tu.type = VOID + self.depth -= 1 + + def visitProtocolInclude(self, pi): + pi.tu.accept(self) + + def visitUsingStmt(self, using): + decl = Decl(using.loc) + decl.shortname = using.type.basename() + fullname = str(using.type) + if decl.shortname != fullname: + decl.fullname = fullname + decl.type = ImportedCxxType(using.type.spec) + self.symtab.declare(decl) + using.decl = decl + + + def visitProtocol(self, p): + # protocol scope + self.symtab.enterScope(p) + + if p.manager is not None: + p.manager.of = p + p.manager.accept(self) + + for managed in p.managesStmts: + managed.manager = p + managed.accept(self) + + setattr(self, 'currentProtocolDecl', p.decl) + for msg in p.messageDecls: + msg.accept(self) + del self.currentProtocolDecl + + for trans in p.transitionStmts: + trans.accept(self) + + # declare all the little C++ thingies that will be generated. + + # they're not relevant to IPDL itself, but those ("invisible") + # symbols can clash with others in the IPDL spec, and we'd like + # to catch those before C++ compilers are allowed to obfuscate + # the error + + self.symtab.exitScope(p) + + + def visitManagerStmt(self, mgr): + mgrdecl = self.symtab.lookup(mgr.name) + pdecl = mgr.of.decl + assert pdecl + + pname, mgrname = pdecl.shortname, mgr.name + loc = mgr.loc + + if mgrdecl is None: + self.errors.append( + errmsg( + loc, + "protocol `%s' referenced as |manager| of `%s' has not been declared", + mgrname, pname)) + elif not isinstance(mgrdecl.type, ProtocolType): + self.errors.append( + errmsg( + loc, + "entity `%s' referenced as |manager| of `%s' is not of `protocol' type; instead it is of type `%s'", + mgrname, pname, mgrdecl.type.typename())) + else: + assert pdecl.type.manager is None + mgr.decl = mgrdecl + pdecl.type.manager = mgrdecl.type + + + def visitManagesStmt(self, mgs): + mgsdecl = self.symtab.lookup(mgs.name) + pdecl = mgs.manager.decl + assert pdecl + + pname, mgsname = pdecl.shortname, mgs.name + loc = mgs.loc + + if mgsdecl is None: + self.errors.append( + errormsg( + loc, + "protocol `%s', managed by `%s', has not been declared", + mgsname, pdeclname)) + elif not isinstance(mgsdecl.type, ProtocolType): + self.errors.append( + errormsg( + loc, + "%s declares itself managing a non-`protocol' entity `%s' of type `%s'", + pdeclname, mgsname, mgsdecl.type.typename())) + else: + mgs.decl = mgsdecl + pdecl.type.manages.append(mgsdecl.type) + + + def visitMessageDecl(self, md): + msgname = md.name + loc = md.loc + + isctor = False + isdtor = False + cdtype = None + + if '~' == msgname[0]: + # it's a destructor. look up the constructed type + msgname = msgname[1:] + + decl = self.symtab.lookup(msgname) + if decl is None: + self.errors.append( + errormsg( + loc, + "type `%s' has not been declared", + msgname)) + elif not decl.type.isProtocol(): + self.errors.append( + errormsg( + loc, + "destructor for non-protocol type `%s'", + msgname)) + else: + msgname += 'Destructor' + isdtor = True + cdtype = decl.type + + decl = self.symtab.lookup(msgname) + + if decl is not None and decl.type.isProtocol(): + # probably a ctor. we'll check validity later. + msgname += 'Constructor' + isctor = True + cdtype = decl.type + elif decl is not None: + self.errors.append( + errormsg( + loc, + "message name `%s' already declared as `%s'", + msgname, decl.type.typename())) + # if we error here, no big deal; move on to find more + decl = None + + # enter message scope + self.symtab.enterScope(md) + + msgtype = MessageType(md.sendSemantics, md.direction, + ctor=isctor, dtor=isdtor, cdtype=cdtype) + + # replace inparam Param nodes with proper Decls + for i, inparam in enumerate(md.inParams): + inptname = inparam.typespec.basename() + inploc = inparam.typespec.loc + + inptdecl = self.symtab.lookup(inptname) + + if inptdecl is None: + self.errors.append( + errormsg( + inploc, + "inparam typename `%s' of message `%s' has not been declared", + inptname, msgname)) + else: + inpdecl = Decl(inploc) + inpdecl.progname = inparam.name + inpdecl.type = inptdecl.type + + self.symtab.declare(inpdecl) + + msgtype.params.append(inpdecl.type) + md.inParams[i] = inpdecl + + # replace outparam Param with proper Decls + for i, outparam in enumerate(md.outParams): + outptname = outparam.typespec.basename() + outploc = outparam.typespec.loc + + outptdecl = self.symtab.lookup(outptname) + + if outptdecl is None: + self.errors.append( + errormsg( + outploc, + "outparam typename `%s' of message `%s' has not been declared", + outptname, msgname)) + else: + outpdecl = Decl(outploc) + outpdecl.progname = outparam.name + outpdecl.type = outptdecl.type + + self.symtab.declare(outpdecl) + + msgtype.returns.append(outpdecl.type) + md.outParams[i] = outpdecl + + self.symtab.exitScope(md) + + decl = Decl(loc) + decl.progname = msgname + decl.type = msgtype + + self.symtab.declare(decl) + md.decl = decl + md.protocolDecl = self.currentProtocolDecl + + +class CheckTypes(Visitor): + def __init__(self, symtab, errors): + self.symtab = symtab + self.errors = errors + self.visited = set() + + def visitProtocolInclude(self, inc): + if inc.tu.filename in self.visited: + return + self.visited.add(inc.tu.filename) + inc.tu.protocol.accept(self) + + + def visitProtocol(self, p): + # check that we require no more "power" than our manager protocol + ptype, pname = p.decl.type, p.decl.shortname + mgrtype = ptype.manager + if mgrtype is not None and ptype.needsMoreJuiceThan(mgrtype): + mgrname = p.manager.decl.shortname + self.errors.append(errormsg( + p.decl.loc, + "protocol `%s' requires more powerful send semantics than its manager `%s' provides", + pname, + mgrname)) + + return Visitor.visitProtocol(self, p) + + + def visitManagesStmt(self, mgs): + pdecl = mgs.manager.decl + ptype, pname = pdecl.type, pdecl.shortname + + mgsdecl = mgs.decl + mgstype, mgsname = mgsdecl.type, mgsdecl.shortname + + loc = mgs.loc + + # we added this information; sanity check it + for managed in ptype.manages: + if managed is mgstype: + break + else: + assert False + + # check that the "managed" protocol agrees + if mgstype.manager is not ptype: + self.errors.append(errormsg( + loc, + "|manages| declaration in protocol `%s' does not match any |manager| declaration in protocol `%s'", + pname, mgsname)) + + + def visitManagerStmt(self, mgr): + pdecl = mgr.of.decl + ptype, pname = pdecl.type, pdecl.shortname + + mgrdecl = mgr.decl + mgrtype, mgrname = mgrdecl.type, mgrdecl.shortname + + # we added this information; sanity check it + assert ptype.manager is mgrtype + + loc = mgr.loc + + # check that the "manager" protocol agrees + if not mgrtype.isManager(ptype): + self.errors.append(errormsg( + loc, + "|manager| declaration in protocol `%s' does not match any |manages| declaration in protocol `%s'", + pname, mgrname)) + + + def visitMessageDecl(self, md): + mtype, mname = md.decl.type, md.decl.progname + ptype, pname = md.protocolDecl.type, md.protocolDecl.shortname + + loc = md.decl.loc + + if mtype.needsMoreJuiceThan(ptype): + self.errors.append(errormsg( + loc, + "message `%s' requires more powerful send semantics than its protocol `%s' provides", + mname, + pname)) + + if mtype.isAsync() and len(mtype.returns): + # XXX/cjones could modify grammar to disallow this ... + self.errors.append(errormsg( + loc, + "asynchronous message `%s' requests returned values", + mname)) + + if (mtype.isCtor() or mtype.isDtor()) and not ptype.isManager(mtype.constructedType()): + self.errors.append(errormsg( + loc, + "ctor/dtor for protocol `%s', which is not managed by protocol `%s'", + mname[:-len('constructor')], + pname)) diff --git a/ipc/ipdl/ipdlc b/ipc/ipdl/ipdlc new file mode 100755 index 00000000000..cec6884c3ef --- /dev/null +++ b/ipc/ipdl/ipdlc @@ -0,0 +1,141 @@ +#!/usr/bin/env python + +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (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.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is mozilla.org code. +# +# Contributor(s): +# Chris Jones +# +# Alternatively, the contents of this file may be used under the terms of +# either of the GNU General Public License Version 2 or later (the "GPL"), +# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +import getopt, os, re, sys + +_verbosity = 1 +def log(minv, fmt, *args): + if _verbosity >= minv: + print >>sys.stderr, fmt % args + +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) + +# auto-add the compiler modules to the pythonpath +installdir, _ = os.path.split(getcallerpath()) +sys.path.append(installdir) + +import ipdl + +# process command line + +def usage(err): + if err: + out, rv = (sys.stderr, 1) + else: + out, rv = (sys.stdout, 0) + print >>out, ''' +Usage: ipdlc [OPTIONS...] SPECIFICATION + +where OPTIONS are zero or more of + -h, --help + : print this message and exit + -d DIR, --output-dir=DIR + : directory in which to put generated code. + Created if it doesn't exist. + default: use current working directory. + -v N, --verbosity=N + : be as verbose as N. set to 0 for no output, and higher than 1 + for more output. + default: -v 1 + +and SPECIFICATION is a single IPDL specification file. '-' = read from stdin. + default : read specification from stdin +''' + sys.exit(rv) + +try: + opts, args = getopt.gnu_getopt( + sys.argv[1:], + 'd:hv:', + ['help', + 'output-dir=', + 'verbosity=']) +except getopt.GetoptError, err: + print >>sys.stderr, str(err), '\n' + usage(err=True) + +codedir = os.getcwd() +spec = '-' + +for o, a in opts: + if o in ('-h', '--help'): + usage(err=False) + elif o in ('-d', '--output-dir'): + codedir = a + elif o in ('-v', '--verbosity'): + _verbosity = int(a) + +if 1 < len(args): + usage(err=True) +if 1 == len(args): + spec = args[0] + +specstring = None +specfilename = None +if spec != '-': + log (1, 'using specification %s', spec) + specstring = open(spec, 'r').read() + specfilename = spec +else: + log(1, 'reading specification from stdin') + specstring = sys.stdin.read() + specfilename = '' + +log(3, ' specification:\n%s', specstring) + +log(1, 'parsing specification') +ast = ipdl.parse(specstring, specfilename) + +log(1, 'checking types') +if not ipdl.typecheck(ast): + print >>sys.stderr, 'Specification is not well typed.' + sys.exit(1) + +if _verbosity >= 3: + log(3, ' pretty printed code:') + ipdl.genipdl(ast, codedir) + +log(1, 'generating code to "%s"', codedir) +ipdl.gencxx(ast, codedir) + +log(1, '''\nIMPORTANT: remember to add the new enum value + + %sMsgStart, + +to the |IPCMessageStart| enum in "ipc/glue/IPCMessageUtils.h". +Your code will not compile until this value is added. +'''% (ast.protocol.name))