def __init__(self, args): try: self.verbose = args.verbose except: self.verbose = False # small limitation for now ;-) assert args.language == "c", "Only C language is currently implemented." try: self.output = args.output except: self.output = None self.platform = self.get_platform(args.platform) # create top-level compilation unit self.unit = Unit() # SM->CM translator self.translator = Translator() # prepare code emitter self.language = C.Emitter(platform=self.platform).output_to( self.output) self.domain_generators = {}
def prepare(self): """ Prepares the definition of the node type. """ # this is a generator for the Nodes domain, let's get it ;-) self.domain = SemanticNodes() # we need a translator self.translator = Translator() node_type = code.StructuredType("nodes").tag("node_type_def") node_type.append(code.Comment("domain properties"), code.Property("id", code.ByteType()), code.Property("address", code.LongType())) # the node type module = self.generator.unit.append(structure.Module("node_t")) module.select("def").append(code.Import("moose/bool")) module.select("def").append(code.Comment("THE node type"), node_type).tag("node_t-start") # provide init_node to initialise module-specific properties module.select("dec").append( code.Import("node_t"), code.Function("init_node", params=[ code.Parameter("node", type=code.ObjectType("node")) ]).tag("init_node") # filled when creating node_t ) # TODO: remove this redundant header file module = self.generator.unit.append(structure.Module("nodes")) module.select("def").append(code.Import("includes")) # add more imports to includes anchor = self.generator.unit.select("includes").select("def").find( "foo-lib-start") code.Import("nodes").insert_before(anchor).tag("requires-tuples") code.Import("node_t").insert_before(anchor) code.Import("foo-lib/nodes").insert_before(anchor) code.Import("foo-lib/payload").insert_before(anchor) # initialiase nodes and add handling of receive packets self.generator.unit.find("init").append( code.FunctionCall("nodes_init"), code.FunctionCall("mesh_on_receive", [code.SimpleVariable("payload_parser_parse")]))
def prepare(self): """ Prepares the definition of the node type. """ # this is a generator for the Nodes domain, let's get it ;-) self.domain = SemanticNodes() # we need a translator self.translator = Translator() node_type = code.StructuredType("nodes").tag("node_type_def") node_type.append(code.Comment("domain properties"), code.Property("id", code.ByteType()), code.Property("address", code.LongType()) ) # the node type module = self.generator.unit.append(structure.Module("node_t")) module.select("def").append( code.Import("moose/bool") ) module.select("def").append( code.Comment("THE node type"), node_type ).tag("node_t-start") # provide init_node to initialise module-specific properties module.select("dec").append( code.Import("node_t"), code.Function("init_node", params=[ code.Parameter("node", type=code.ObjectType("node")) ]).tag("init_node") # filled when creating node_t ) # TODO: remove this redundant header file module = self.generator.unit.append(structure.Module("nodes")) module.select("def").append( code.Import("includes") ) # add more imports to includes anchor = self.generator.unit.select("includes").select("def").find("foo-lib-start") code.Import("nodes").insert_before(anchor).tag("requires-tuples") code.Import("node_t").insert_before(anchor) code.Import("foo-lib/nodes").insert_before(anchor) code.Import("foo-lib/payload").insert_before(anchor) # initialiase nodes and add handling of receive packets self.generator.unit.find("init").append( code.FunctionCall("nodes_init"), code.FunctionCall("mesh_on_receive", [code.SimpleVariable("payload_parser_parse")]) )
def __init__(self, args): try: self.verbose = args.verbose except: self.verbose = False # small limitation for now ;-) assert args.language == "c", "Only C language is currently implemented." try: self.output = args.output except: self.output = None self.platform = self.get_platform(args.platform) # create top-level compilation unit self.unit = Unit() # SM->CM translator self.translator = Translator() # prepare code emitter self.language = C.Emitter(platform=self.platform).output_to(self.output) self.domain_generators = {}
def __init__(self): # TODO: use a backlink to generator ? super(Transformer, self).__init__() self.translator = Translator() # this is a Transformer for the Nodes domain, let's get it ;-) self.domain = SemanticNodes()
class Transformer(language.Visitor): """ Visitor for CodeCanvas-based ASTs to add/remove code automagically. """ def __init__(self): # TODO: use a backlink to generator ? super(Transformer, self).__init__() self.translator = Translator() # this is a Transformer for the Nodes domain, let's get it ;-) self.domain = SemanticNodes() def translate(self, tree): return self.translator.translate(tree) @stacked def visit_MethodCall(self, call): """ Methodcalls to the nodes domain are rewritten to function-calls. """ if isinstance(call.obj, code.Object) and call.obj.name == "nodes": # FIXME: this is a bit too local, would nicer at C-level, based on the # definition of the parameters of these functions. not for now ;-) # these function call take vararg bytes. we need to convert each non-byte # argument to a ListLiteral of corresponding bytes. # create function-call # strategy: # AtomLiteral will be expanded later, so we skip them here # ObjectProperty check/convert property type # SimpleVariable # FunctionCall extract from arguments, assign to variable, convert assert isinstance(call.arguments[0], code.ListLiteral) # TODO: mark this somehow and move logic to emitter # the UnionType solution is to C-specific - no more time to do it # right :-( # rebuild the arguments args = code.ListLiteral() temp = 0 for index, arg in enumerate(call.arguments[0]): # TODO: generalize a bit more - this only support our minimal needs if isinstance(arg, code.FunctionCall): code.Assign(code.VariableDecl("temp" + str(temp), arg.type), arg) \ .insert_before(call) if isinstance(arg.type, code.AmountType) and \ isinstance(arg.type.type, code.ByteType): for i in range(arg.type.size): args.append( code.ListVariable("temp" + str(temp), i)) temp += 1 elif isinstance(arg, code.SimpleVariable): if str(arg.info) == "TimestampType": code.VariableDecl( "temp" + str(temp), code.UnionType("temp" + str(temp)) \ .contains( code.Property("value", code.NamedType("timestamp")), code.Property( "b", code.AmountType(code.ByteType(), 4) # platf? ) ) ).insert_before(call) code.Assign(code.StructProperty("temp" + str(temp), "value"), arg) \ .insert_before(call) for i in range(4): args.append( code.ListVariable( code.StructProperty( "temp" + str(temp), "b"), i)) temp += 1 elif str(arg.info) == "ObjectType(nodes)": # a node is identified in a distributed manner by its nw address # TODO: maybe not always correct, but split it out as 2 bytes args.append( code.ShiftLeft( code.ObjectProperty(arg.name, "address"), 8), code.ObjectProperty(arg.name, "address")) elif isinstance(arg, code.ObjectProperty): if isinstance(arg.type, code.ByteType): args.append(arg) elif isinstance(arg.type, code.FloatType): code.VariableDecl( "temp" + str(temp), code.UnionType("temp" + str(temp)) \ .contains( code.Property("value", code.FloatType() ), code.Property( "b", code.AmountType(code.ByteType(), 4) ) ) ).insert_before(call) code.Assign(code.StructProperty("temp" + str(temp), "value"), arg) \ .insert_before(call) for i in range(4): args.append( code.ListVariable( code.StructProperty( "temp" + str(temp), "b"), i)) temp += 1 else: args.append(arg) # replace by functioncall return code.FunctionCall("nodes_" + call.method.name, [args]) processors = 0 @stacked def visit_CaseStatement(self, stmt): """ CaseStatements may be used to handle incoming payloads. These should be centralized in the processing of incoming payloads. Payload references are found in the case.expression.type == semantic.Nodes.payload_t. We now support the typical case with a contains() function. For each of these we generate a function based on the consequence, accepting a payload, positioned where it contains information following the case condition. The case condition's literals are added as to-find literals and a reference to the handling function is also registered with a general purpose byte- stream parser/state machine. """ # TODO: take into account execution strategy # TODO: only supported now: SimpleVariable if isinstance(stmt.expression, code.SimpleVariable): if stmt.expression.info == SemanticNodes.payload_t: # move handling to centralized processing of incoming data for case, consequence in zip(stmt.cases, stmt.consequences): # create a function that processes matched payload handler = code.Function( "nodes_process_incoming_case_" + str(Transformer.processors), params=[ code.Parameter("me", code.ObjectType("node")), code.Parameter("sender", code.ObjectType("node")), code.Parameter("from", code.ObjectType("node")), code.Parameter("hop", code.ObjectType("node")), code.Parameter("to", code.ObjectType("node")), code.Parameter( "payload", self.translate( self.domain.get_type("payload"))) ]) Transformer.processors += 1 handler.append( code.Comment("extract variables from payload")) # declare matching local variables from case # NOTE: these are inside a ListLiteral for arg in case.arguments[0]: if isinstance(arg, code.Variable): code_type = self.translate(arg.info) # TODO: generalize this more code_type_name = { "NamedType {'name': 'timestamp'}": lambda: code_type.name, "ByteType": lambda: "byte", # TODO: amount has type, should be recursively extracted # TODO: size "AmountType {}": lambda: "bytes", "ObjectType {'name': 'nodes'}": lambda: code_type.name[:-1], "FloatType": lambda: "float" }[str(code_type)]() # TODO: size should be generalized args = [code.IntegerLiteral(code_type.size)] \ if code_type_name == "bytes" else [] if isinstance(code_type, code.AmountType): code_type = code.ManyType(code.ByteType()) handler.append( code.Assign( code.VariableDecl(arg.name, code_type), code.FunctionCall( "payload_parser_consume_" + code_type_name, type=code_type, arguments=args))) # add consequence handler.append(code.Comment("perform handling actions")) for statement in consequence: handler.append(statement) self.stack[0].find("nodes_main").select("dec").append( handler) # register the handle for the literals in the case arguments = code.ListLiteral() for arg in case.arguments[0]: if isinstance(arg, code.Literal): arguments.append(arg) registration = code.FunctionCall( "payload_parser_register", [code.SimpleVariable(handler.name), arguments]) self.stack[0].find("init").append(registration) # if there is an else case, also generate it and register it if not stmt.case_else == None: handler = code.Function( "nodes_process_incoming_else", params=[ code.Parameter("sender", code.ObjectType("node")), code.Parameter("from", code.ObjectType("node")), code.Parameter("hop", code.ObjectType("node")), code.Parameter("to", code.ObjectType("node")), code.Parameter( "payload", self.translate( self.domain.get_type("payload"))) ]) handler.append(self.translate(stmt.case_else)) self.stack[0].find("nodes_main").select("dec").append( handler) registration = code.FunctionCall( "payload_parser_register_else", [code.SimpleVariable(handler.name)]) self.stack[0].find("init").append(registration) # remove the case self.stack[-2].remove_child(self.child)
class Nodes(Domain): def prepare(self): """ Prepares the definition of the node type. """ # this is a generator for the Nodes domain, let's get it ;-) self.domain = SemanticNodes() # we need a translator self.translator = Translator() node_type = code.StructuredType("nodes").tag("node_type_def") node_type.append(code.Comment("domain properties"), code.Property("id", code.ByteType()), code.Property("address", code.LongType())) # the node type module = self.generator.unit.append(structure.Module("node_t")) module.select("def").append(code.Import("moose/bool")) module.select("def").append(code.Comment("THE node type"), node_type).tag("node_t-start") # provide init_node to initialise module-specific properties module.select("dec").append( code.Import("node_t"), code.Function("init_node", params=[ code.Parameter("node", type=code.ObjectType("node")) ]).tag("init_node") # filled when creating node_t ) # TODO: remove this redundant header file module = self.generator.unit.append(structure.Module("nodes")) module.select("def").append(code.Import("includes")) # add more imports to includes anchor = self.generator.unit.select("includes").select("def").find( "foo-lib-start") code.Import("nodes").insert_before(anchor).tag("requires-tuples") code.Import("node_t").insert_before(anchor) code.Import("foo-lib/nodes").insert_before(anchor) code.Import("foo-lib/payload").insert_before(anchor) # initialiase nodes and add handling of receive packets self.generator.unit.find("init").append( code.FunctionCall("nodes_init"), code.FunctionCall("mesh_on_receive", [code.SimpleVariable("payload_parser_parse")])) def _translate(self, tree): return self.translator.translate(tree) def extend(self, module): """ Add domain specific extensions (e.g. includes, functions, ...) """ {"main": self.extend_main}[module.name](module) def extend_main(self, module): """ Transforms the main module by adding nodes functionality to the event_loop. """ self.add_import_nodes(module) if not module.find("nodes_main") is None: return module.tag("nodes_main") # get pointers into the code dec = module.select("dec") event_loop = module.find("event_loop") # prepare top-level actions in event_loop event_loop.append(code.Comment("nodes logic execution hook")) event_loop.append(code.FunctionCall("nodes_process")) # prepare a hook to setup scheduling scheduler_init = dec.append( code.Function("nodes_scheduler_init")).tag("nodes_scheduler_init") module.find("main_function").append( code.FunctionCall(scheduler_init.name).stick_top()) def construct(self, code_module, module): """ Constructs a code_module from a module. """ self.add_import_nodes(code_module) code_module.select("dec").append(code.Import(code_module.data)) code_module.select("def").append(code.Import("includes")) # add extensions to node_t definition node_type = self.generator.unit.find("node_type_def") node_type.append(code.Comment("extended properties for " + module.name)) # add initializations to init_node function init = self.generator.unit.find("init_node") for ext in module.domains["nodes"].extensions: for prop in ext.extension.properties: if isinstance(prop.type, model.ManyType) and \ isinstance(prop.type.subtype, model.TupleType): code.Import("tuples").insert_before(node_type) node_type.append( code.Property(prop.name, self._translate(prop.type))) init.append( code.Assign(code.ObjectProperty("node", prop.name), self.translator.translate(prop.value))) # TODO: this should go in the emission of a functioncal :-/ # and lookup the function in the externals (no time now) if isinstance(prop.value, model.FunctionCallExp) and \ prop.value.name == "now": self.generator.unit.select("node_t", "def").append( code.Import("foo-lib/time")) # create all functions for function in module.functions: code_module.select("dec").append(self._translate(function)) def add_import_nodes(self, module): """ Make sure that nodes functionality is imported (once) """ if not module.find("import_nodes") is None: return module.select("def").append(code.Import("nodes")).tag("import_nodes") def transform(self, unit): unit.accept(Transformer()) def link_execution(self, execution): { "Every": self.create_every_execution, "When": self.create_when_execution }[execution.__class__.__name__](execution) def create_every_execution(self, execution): if isinstance(execution.scope, AllNodes): name = "nodes_schedule_all" else: name = "nodes_schedule_own" self.generator.unit.find("nodes_scheduler_init").append( code.FunctionCall(name, arguments=[ self._translate(execution.interval), code.SimpleVariable(execution.executed.name) ])) def create_when_execution(self, execution): if execution.event.name == "receive": f = self.generator.unit.find(execution.executed.name + "_decl") if not f is None and len(f.children) > 0: # if children are single CaseStatement, it will be handled by Transform if not isinstance(f.children[0], code.CaseStatement): # else we need to register a catch-all handlers self.generator.unit.find("init").append( code.FunctionCall( "payload_parser_register_all", [code.SimpleVariable(execution.executed.name)])) return # Case-based receiving is handled by Transform # TODO: generalize: only supported = after transmit for all nodes assert execution.timing == "after" assert execution.event.name == "transmit" assert isinstance(execution.scope, AllNodes) # wire transmit handler self.generator.unit.find("init").append( code.FunctionCall("mesh_on_transmit", [code.SimpleVariable("handle_transmit")])) # setup transforming handle_transmit self.generator.unit.find("nodes_main").select("dec").append( code.Function( "handle_transmit", params=[ code.Parameter("from", code.IntegerType()), code.Parameter("hop", code.IntegerType()), code.Parameter("to", code.IntegerType()), code.Parameter("size", code.ByteType()), code.Parameter("payload", code.ManyType(code.ByteType())) ]).contains( code.FunctionCall( execution.executed.name, arguments=[ code.FunctionCall( "nodes_lookup", type=code.ObjectType("node"), arguments=[code.SimpleVariable("from")]), code.FunctionCall( "nodes_lookup", type=code.ObjectType("node"), arguments=[code.SimpleVariable("hop")]), code.FunctionCall( "nodes_lookup", type=code.ObjectType("node"), arguments=[code.SimpleVariable("to")]), code.FunctionCall( "make_payload", type=code.ObjectType("payload"), arguments=[ code.SimpleVariable("payload"), code.SimpleVariable("size") ]), ])))
class Generator(): def __init__(self, args): try: self.verbose = args.verbose except: self.verbose = False # small limitation for now ;-) assert args.language == "c", "Only C language is currently implemented." try: self.output = args.output except: self.output = None self.platform = self.get_platform(args.platform) # create top-level compilation unit self.unit = Unit() # SM->CM translator self.translator = Translator() # prepare code emitter self.language = C.Emitter(platform=self.platform).output_to( self.output) self.domain_generators = {} def __str__(self): return "generating to " + self.output + " using " + str(self.language) + \ " on " + str(self.platform) def log(self, msg): if self.verbose: print "--- " + msg def generate(self, model): self.construct_code_model(model) return self.emit_source() def construct_code_model(self, model): # construct basic model self.construct(model) # appy domain transformations to entire unit for domain_generator in self.domain_generators.values(): domain_generator.transform(self.unit) return self.unit def emit_source(self, unit=None): if unit is None: unit = self.unit # emit to language self.log("starting language emission") return self.language.emit(unit) def translate(self, part): return self.translator.translate(part) def construct(self, model): """ Constructs a CodeModel given a SemanticModel. """ self.log("constructing basic code model from semantic model") self.create_main_module(model) # basic setup is ready, allow platform to add stuff self.platform.setup(self.unit) self.create_constants(model) self.create_modules(model) self.create_executions(model) def create_constants(self, model): defines = None for module in model.modules.values(): for constant in module.constants: if defines is None: defines = self.unit.append( Module("constants")).select("def") defines.append( code.Constant(constant.name, self.translate(constant.value), self.translate(constant.type))) def create_modules(self, model): """ Creates a module for each domain/module pair. """ for module_name, module in model.modules.items(): for domain_name, domain in module.domains.items(): domain_generator = self.generator_for_domain(domain_name) name = domain_name + "-" + module_name self.log("creating " + name) # construct section domain_generator.construct(self.unit.append(Module(name)), module) def create_main_module(self, model): """ Creates the top-level main and includes modules. """ module = self.unit.append(Module("includes").tag("includes")) # add basic set of includes that apply to all generations, without causing # compilation problems module.select("def").append(code.Import("<stdint.h>")) for mod in model.modules.values(): if len(mod.constants.items()) > 0: module.select("def").append(code.Import("constants")) break module.select("def").append( code.Import("foo-lib/crypto")).tag("foo-lib-start") module.select("def").append(code.Import("foo-lib/time")) module.select("def").append(code.Import("../lib/network")) # MAIN module module = self.unit.append(Module("main")) module.select("def").append(code.Import("includes")) module.select("dec").append(code.Import("main")).tag("main_h") for domain_module_name, domain_module in model.modules.items(): for domain_name, domain in domain_module.domains.items(): name = domain_name + "-" + domain_module_name module.select("def").append(code.Import(name)) # init init = code.Function("init").tag("init") \ .contains(code.Comment("add framework init here")) # app app = code.Function("application_step") \ .contains(code.Comment("add application specific code here")) \ .tag("step") # main main = code.Function("main", code.NamedType("int")).tag("main_function") main.append(code.FunctionCall("init").stick_top()) main.append(code.Return(code.IntegerLiteral(1))).stick_bottom() module.select("dec").append( code.Comment("""init and application_step can be implemented using application specific needs."""), init, app, code.Comment("""starting point please don't change anything beyond this point."""), main) # construct an event_loop builder and hook it into the main function event_loop = code.WhileDo(code.BooleanLiteral(True)) main.append(event_loop).tag("event_loop") \ .append(code.Comment("your application gets its share"), code.FunctionCall("application_step")) # allow each domain generator to extend the main section for mod in model.modules.values(): for domain_name, domain in mod.domains.items(): self.generator_for_domain(domain_name).extend(module) def create_executions(self, model): # executions for module_name, module in model.modules.items(): for execution in module.executions: self.generator_for_domain("nodes").link_execution(execution) def generator_for_domain(self, domain_name): """ Lazy-Dynamic-Loading of Domain Generators, based on the Semantic Domain name """ if domain_name not in self.domain_generators: clazz = self.get_class("foo_lang.generator.domains." + domain_name, domain_name.capitalize()) self.domain_generators[domain_name] = clazz(self) return self.domain_generators[domain_name] def get_platform(self, platform_name): clazz = self.get_class("foo_lang.generator.platforms." + platform_name, platform_name.capitalize()) return clazz() def get_class(self, module_name, class_name): module = __import__(module_name, fromlist=[class_name]) return getattr(module, class_name)
class Transformer(language.Visitor): """ Visitor for CodeCanvas-based ASTs to add/remove code automagically. """ def __init__(self): # TODO: use a backlink to generator ? super(Transformer, self).__init__() self.translator = Translator() # this is a Transformer for the Nodes domain, let's get it ;-) self.domain = SemanticNodes() def translate(self, tree): return self.translator.translate(tree) @stacked def visit_MethodCall(self, call): """ Methodcalls to the nodes domain are rewritten to function-calls. """ if isinstance(call.obj, code.Object) and call.obj.name == "nodes": # FIXME: this is a bit too local, would nicer at C-level, based on the # definition of the parameters of these functions. not for now ;-) # these function call take vararg bytes. we need to convert each non-byte # argument to a ListLiteral of corresponding bytes. # create function-call # strategy: # AtomLiteral will be expanded later, so we skip them here # ObjectProperty check/convert property type # SimpleVariable # FunctionCall extract from arguments, assign to variable, convert assert isinstance(call.arguments[0], code.ListLiteral) # TODO: mark this somehow and move logic to emitter # the UnionType solution is to C-specific - no more time to do it # right :-( # rebuild the arguments args = code.ListLiteral() temp = 0; for index, arg in enumerate(call.arguments[0]): # TODO: generalize a bit more - this only support our minimal needs if isinstance(arg, code.FunctionCall): code.Assign(code.VariableDecl("temp" + str(temp), arg.type), arg) \ .insert_before(call) if isinstance(arg.type, code.AmountType) and \ isinstance(arg.type.type, code.ByteType): for i in range(arg.type.size): args.append(code.ListVariable("temp" +str(temp), i)) temp += 1 elif isinstance(arg, code.SimpleVariable): if str(arg.info) == "TimestampType": code.VariableDecl( "temp" + str(temp), code.UnionType("temp" + str(temp)) \ .contains( code.Property("value", code.NamedType("timestamp")), code.Property( "b", code.AmountType(code.ByteType(), 4) # platf? ) ) ).insert_before(call) code.Assign(code.StructProperty("temp" + str(temp), "value"), arg) \ .insert_before(call) for i in range(4): args.append( code.ListVariable( code.StructProperty("temp" + str(temp), "b"), i ) ) temp += 1 elif str(arg.info) == "ObjectType(nodes)": # a node is identified in a distributed manner by its nw address # TODO: maybe not always correct, but split it out as 2 bytes args.append( code.ShiftLeft(code.ObjectProperty(arg.name, "address"), 8), code.ObjectProperty(arg.name, "address") ) elif isinstance(arg, code.ObjectProperty): if isinstance(arg.type, code.ByteType): args.append(arg) elif isinstance(arg.type, code.FloatType): code.VariableDecl( "temp" + str(temp), code.UnionType("temp" + str(temp)) \ .contains( code.Property("value", code.FloatType() ), code.Property( "b", code.AmountType(code.ByteType(), 4) ) ) ).insert_before(call) code.Assign(code.StructProperty("temp" + str(temp), "value"), arg) \ .insert_before(call) for i in range(4): args.append( code.ListVariable( code.StructProperty("temp" + str(temp), "b"), i ) ) temp += 1 else: args.append(arg) # replace by functioncall return code.FunctionCall("nodes_" + call.method.name, [args]) processors = 0 @stacked def visit_CaseStatement(self, stmt): """ CaseStatements may be used to handle incoming payloads. These should be centralized in the processing of incoming payloads. Payload references are found in the case.expression.type == semantic.Nodes.payload_t. We now support the typical case with a contains() function. For each of these we generate a function based on the consequence, accepting a payload, positioned where it contains information following the case condition. The case condition's literals are added as to-find literals and a reference to the handling function is also registered with a general purpose byte- stream parser/state machine. """ # TODO: take into account execution strategy # TODO: only supported now: SimpleVariable if isinstance(stmt.expression, code.SimpleVariable): if stmt.expression.info == SemanticNodes.payload_t: # move handling to centralized processing of incoming data for case, consequence in zip(stmt.cases, stmt.consequences): # create a function that processes matched payload handler = code.Function( "nodes_process_incoming_case_" + str(Transformer.processors), params=[ code.Parameter("me", code.ObjectType("node")), code.Parameter("sender", code.ObjectType("node")), code.Parameter("from", code.ObjectType("node")), code.Parameter("hop", code.ObjectType("node")), code.Parameter("to", code.ObjectType("node")), code.Parameter("payload", self.translate(self.domain.get_type("payload"))) ] ) Transformer.processors += 1 handler.append(code.Comment("extract variables from payload")) # declare matching local variables from case # NOTE: these are inside a ListLiteral for arg in case.arguments[0]: if isinstance(arg, code.Variable): code_type = self.translate(arg.info) # TODO: generalize this more code_type_name = { "NamedType {'name': 'timestamp'}": lambda: code_type.name, "ByteType" : lambda: "byte", # TODO: amount has type, should be recursively extracted # TODO: size "AmountType {}" : lambda: "bytes", "ObjectType {'name': 'nodes'}" : lambda: code_type.name[:-1], "FloatType" : lambda: "float" }[str(code_type)]() # TODO: size should be generalized args = [code.IntegerLiteral(code_type.size)] \ if code_type_name == "bytes" else [] if isinstance(code_type, code.AmountType): code_type = code.ManyType(code.ByteType()) handler.append( code.Assign( code.VariableDecl(arg.name, code_type), code.FunctionCall("payload_parser_consume_" + code_type_name, type=code_type, arguments=args) ) ) # add consequence handler.append(code.Comment("perform handling actions")) for statement in consequence: handler.append(statement) self.stack[0].find("nodes_main").select("dec").append(handler) # register the handle for the literals in the case arguments = code.ListLiteral() for arg in case.arguments[0]: if isinstance(arg, code.Literal): arguments.append(arg) registration = code.FunctionCall("payload_parser_register", [ code.SimpleVariable(handler.name), arguments ] ) self.stack[0].find("init").append(registration) # if there is an else case, also generate it and register it if not stmt.case_else == None: handler = code.Function( "nodes_process_incoming_else", params=[ code.Parameter("sender", code.ObjectType("node")), code.Parameter("from", code.ObjectType("node")), code.Parameter("hop", code.ObjectType("node")), code.Parameter("to", code.ObjectType("node")), code.Parameter("payload", self.translate(self.domain.get_type("payload"))) ] ) handler.append(self.translate(stmt.case_else)) self.stack[0].find("nodes_main").select("dec").append(handler) registration = code.FunctionCall("payload_parser_register_else", [ code.SimpleVariable(handler.name) ] ) self.stack[0].find("init").append(registration) # remove the case self.stack[-2].remove_child(self.child)
class Nodes(Domain): def prepare(self): """ Prepares the definition of the node type. """ # this is a generator for the Nodes domain, let's get it ;-) self.domain = SemanticNodes() # we need a translator self.translator = Translator() node_type = code.StructuredType("nodes").tag("node_type_def") node_type.append(code.Comment("domain properties"), code.Property("id", code.ByteType()), code.Property("address", code.LongType()) ) # the node type module = self.generator.unit.append(structure.Module("node_t")) module.select("def").append( code.Import("moose/bool") ) module.select("def").append( code.Comment("THE node type"), node_type ).tag("node_t-start") # provide init_node to initialise module-specific properties module.select("dec").append( code.Import("node_t"), code.Function("init_node", params=[ code.Parameter("node", type=code.ObjectType("node")) ]).tag("init_node") # filled when creating node_t ) # TODO: remove this redundant header file module = self.generator.unit.append(structure.Module("nodes")) module.select("def").append( code.Import("includes") ) # add more imports to includes anchor = self.generator.unit.select("includes").select("def").find("foo-lib-start") code.Import("nodes").insert_before(anchor).tag("requires-tuples") code.Import("node_t").insert_before(anchor) code.Import("foo-lib/nodes").insert_before(anchor) code.Import("foo-lib/payload").insert_before(anchor) # initialiase nodes and add handling of receive packets self.generator.unit.find("init").append( code.FunctionCall("nodes_init"), code.FunctionCall("mesh_on_receive", [code.SimpleVariable("payload_parser_parse")]) ) def _translate(self, tree): return self.translator.translate(tree) def extend(self, module): """ Add domain specific extensions (e.g. includes, functions, ...) """ { "main": self.extend_main }[module.name](module) def extend_main(self, module): """ Transforms the main module by adding nodes functionality to the event_loop. """ self.add_import_nodes(module) if not module.find("nodes_main") is None: return module.tag("nodes_main") # get pointers into the code dec = module.select("dec") event_loop = module.find("event_loop") # prepare top-level actions in event_loop event_loop.append(code.Comment("nodes logic execution hook")) event_loop.append(code.FunctionCall("nodes_process")) # prepare a hook to setup scheduling scheduler_init = dec.append( code.Function("nodes_scheduler_init") ).tag("nodes_scheduler_init") module.find("main_function").append( code.FunctionCall(scheduler_init.name).stick_top() ) def construct(self, code_module, module): """ Constructs a code_module from a module. """ self.add_import_nodes(code_module) code_module.select("dec").append(code.Import(code_module.data)) code_module.select("def").append(code.Import("includes")) # add extensions to node_t definition node_type = self.generator.unit.find("node_type_def") node_type.append(code.Comment("extended properties for " + module.name)) # add initializations to init_node function init = self.generator.unit.find("init_node") for ext in module.domains["nodes"].extensions: for prop in ext.extension.properties: if isinstance(prop.type, model.ManyType) and \ isinstance(prop.type.subtype, model.TupleType): code.Import("tuples").insert_before(node_type) node_type.append(code.Property(prop.name, self._translate(prop.type))) init.append(code.Assign( code.ObjectProperty("node", prop.name), self.translator.translate(prop.value) )) # TODO: this should go in the emission of a functioncal :-/ # and lookup the function in the externals (no time now) if isinstance(prop.value, model.FunctionCallExp) and \ prop.value.name == "now": self.generator.unit.select("node_t", "def").append( code.Import("foo-lib/time") ) # create all functions for function in module.functions: code_module.select("dec").append(self._translate(function)) def add_import_nodes(self, module): """ Make sure that nodes functionality is imported (once) """ if not module.find("import_nodes") is None: return module.select("def").append(code.Import("nodes")).tag("import_nodes") def transform(self, unit): unit.accept(Transformer()) def link_execution(self, execution): { "Every": self.create_every_execution, "When" : self.create_when_execution }[execution.__class__.__name__](execution) def create_every_execution(self, execution): if isinstance(execution.scope, AllNodes): name = "nodes_schedule_all" else: name = "nodes_schedule_own" self.generator.unit.find("nodes_scheduler_init").append( code.FunctionCall(name, arguments=[ self._translate(execution.interval), code.SimpleVariable(execution.executed.name) ]) ) def create_when_execution(self, execution): if execution.event.name == "receive": f = self.generator.unit.find(execution.executed.name+"_decl") if not f is None and len(f.children) > 0: # if children are single CaseStatement, it will be handled by Transform if not isinstance(f.children[0], code.CaseStatement): # else we need to register a catch-all handlers self.generator.unit.find("init").append( code.FunctionCall("payload_parser_register_all", [ code.SimpleVariable(execution.executed.name) ]) ) return # Case-based receiving is handled by Transform # TODO: generalize: only supported = after transmit for all nodes assert execution.timing == "after" assert execution.event.name == "transmit" assert isinstance(execution.scope, AllNodes) # wire transmit handler self.generator.unit.find("init").append( code.FunctionCall("mesh_on_transmit", [ code.SimpleVariable("handle_transmit") ]) ) # setup transforming handle_transmit self.generator.unit.find("nodes_main").select("dec").append( code.Function( "handle_transmit", params=[ code.Parameter("from", code.IntegerType()), code.Parameter("hop", code.IntegerType()), code.Parameter("to", code.IntegerType()), code.Parameter("size", code.ByteType()), code.Parameter("payload", code.ManyType(code.ByteType())) ] ).contains( code.FunctionCall( execution.executed.name, arguments=[ code.FunctionCall("nodes_lookup", type=code.ObjectType("node"), arguments=[code.SimpleVariable("from")]), code.FunctionCall("nodes_lookup", type=code.ObjectType("node"), arguments=[code.SimpleVariable("hop")]), code.FunctionCall("nodes_lookup", type=code.ObjectType("node"), arguments=[code.SimpleVariable("to")]), code.FunctionCall("make_payload", type=code.ObjectType("payload"), arguments=[ code.SimpleVariable("payload"), code.SimpleVariable("size") ]), ] ) ) )
def setUp(self): self.translator = Translator()
class TestTranslate(unittest.TestCase): def setUp(self): self.translator = Translator() def translate(self, model): return self.translator.translate(model) def test_simple_types(self): self.assertIsInstance(self.translate(model.VoidType()), code.VoidType) self.assertIsInstance(self.translate(model.IntegerType()), code.IntegerType) self.assertIsInstance(self.translate(model.FloatType()), code.FloatType) self.assertIsInstance(self.translate(model.LongType()), code.LongType) self.assertIsInstance(self.translate(model.ByteType()), code.ByteType) self.assertIsInstance(self.translate(model.BooleanType()), code.BooleanType) def test_known_named_types(self): result = self.translate(model.TimestampType()) self.assertIsInstance(result, code.NamedType) self.assertEqual(result.name, "timestamp") def test_manytype(self): result = self.translate(model.ManyType(model.IntegerType())) self.assertIsInstance(result, code.ManyType) self.assertIsInstance(result.type, code.IntegerType) def test_nested_manytypes(self): result = self.translate( model.ManyType(model.ManyType(model.IntegerType()))) self.assertIsInstance(result, code.ManyType) self.assertIsInstance(result.type, code.ManyType) self.assertIsInstance(result.type.type, code.IntegerType) def test_tupletype(self): result = self.translate( model.TupleType([model.IntegerType(), model.FloatType()])) self.assertIsInstance(result, code.TupleType) self.assertIsInstance(result.types[0], code.IntegerType) self.assertIsInstance(result.types[1], code.FloatType) def test_literals(self): result = self.translate(model.BooleanLiteralExp(True)) self.assertIsInstance(result, code.BooleanLiteral) self.assertTrue(result.value) result = self.translate(model.BooleanLiteralExp(False)) self.assertIsInstance(result, code.BooleanLiteral) self.assertFalse(result.value) result = self.translate(model.IntegerLiteralExp(123)) self.assertIsInstance(result, code.IntegerLiteral) self.assertEqual(result.value, 123) result = self.translate(model.FloatLiteralExp(12.3)) self.assertIsInstance(result, code.FloatLiteral) self.assertEqual(result.value, 12.3) def test_variable(self): result = self.translate( model.VariableExp(model.Identifier("var_name"), model.IntegerType())) self.assertIsInstance(result, code.SimpleVariable) self.assertIsInstance(result.id, code.Identifier) self.assertEqual(result.id.name, "var_name") def test_assign_stmt(self): result = self.translate( model.AssignStmt( model.VariableExp(model.Identifier("var_name"), model.IntegerType()), model.IntegerLiteralExp(456))) self.assertIsInstance(result, code.Assign) self.assertIsInstance(result.operand, code.VariableDecl) self.assertIsInstance(result.expression, code.IntegerLiteral) def test_empty_block_stmt(self): self.assertIsNone(self.translate(model.BlockStmt([]))) def test_filled_block_stmt(self): result = self.translate( model.BlockStmt([ model.AssignStmt( model.VariableExp(model.Identifier("var_name"), model.IntegerType()), model.IntegerLiteralExp(456)) ])) self.assertEqual(len(result), 1) self.assertIsInstance(result[0], code.Assign) def test_function_without_params_or_body(self): result = self.translate( model.FunctionDecl(model.BlockStmt(), type=model.VoidType())) self.assertIsInstance(result, code.Function) self.assertIsInstance(result.type, code.VoidType) self.assertEqual(result.children, []) self.assertEqual(list(result.params), []) def test_function_with_params_and_body(self): result = self.translate( model.FunctionDecl(model.BlockStmt([ model.AssignStmt( model.VariableExp(model.Identifier("var_name"), model.IntegerType()), model.IntegerLiteralExp(456)) ]), type=model.VoidType(), parameters=[ model.Parameter( model.Identifier("var_name"), model.IntegerType()) ])) self.assertIsInstance(result, code.Function) self.assertIsInstance(result.type, code.VoidType) self.assertEqual(len(result), 1) self.assertEqual(len(result.params), 1) self.assertIsInstance(result.children[0], code.Assign) self.assertEqual(result.params[0].name, "var_name") self.assertIsInstance(result.params[0].type, code.IntegerType)
class Generator(): def __init__(self, args): try: self.verbose = args.verbose except: self.verbose = False # small limitation for now ;-) assert args.language == "c", "Only C language is currently implemented." try: self.output = args.output except: self.output = None self.platform = self.get_platform(args.platform) # create top-level compilation unit self.unit = Unit() # SM->CM translator self.translator = Translator() # prepare code emitter self.language = C.Emitter(platform=self.platform).output_to(self.output) self.domain_generators = {} def __str__(self): return "generating to " + self.output + " using " + str(self.language) + \ " on " + str(self.platform) def log(self, msg): if self.verbose: print "--- " + msg def generate(self, model): self.construct_code_model(model) return self.emit_source() def construct_code_model(self, model): # construct basic model self.construct(model) # appy domain transformations to entire unit for domain_generator in self.domain_generators.values(): domain_generator.transform(self.unit) return self.unit def emit_source(self, unit=None): if unit is None: unit = self.unit # emit to language self.log("starting language emission") return self.language.emit(unit) def translate(self, part): return self.translator.translate(part) def construct(self, model): """ Constructs a CodeModel given a SemanticModel. """ self.log("constructing basic code model from semantic model") self.create_main_module(model) # basic setup is ready, allow platform to add stuff self.platform.setup(self.unit) self.create_constants(model) self.create_modules(model) self.create_executions(model) def create_constants(self, model): defines = None for module in model.modules.values(): for constant in module.constants: if defines is None: defines = self.unit.append(Module("constants")).select("def") defines.append(code.Constant(constant.name, self.translate(constant.value), self.translate(constant.type))) def create_modules(self, model): """ Creates a module for each domain/module pair. """ for module_name, module in model.modules.items(): for domain_name, domain in module.domains.items(): domain_generator = self.generator_for_domain(domain_name) name = domain_name + "-" + module_name self.log("creating " + name) # construct section domain_generator.construct(self.unit.append(Module(name)), module) def create_main_module(self, model): """ Creates the top-level main and includes modules. """ module = self.unit.append(Module("includes").tag("includes")) # add basic set of includes that apply to all generations, without causing # compilation problems module.select("def").append(code.Import("<stdint.h>")) for mod in model.modules.values(): if len(mod.constants.items()) > 0: module.select("def").append(code.Import("constants")) break module.select("def").append(code.Import("foo-lib/crypto")).tag("foo-lib-start") module.select("def").append(code.Import("foo-lib/time")) module.select("def").append(code.Import("../lib/network")) # MAIN module module = self.unit.append(Module("main")) module.select("def").append(code.Import("includes")) module.select("dec").append(code.Import("main")).tag("main_h") for domain_module_name, domain_module in model.modules.items(): for domain_name, domain in domain_module.domains.items(): name = domain_name + "-" + domain_module_name module.select("def").append(code.Import(name)) # init init = code.Function("init").tag("init") \ .contains(code.Comment("add framework init here")) # app app = code.Function("application_step") \ .contains(code.Comment("add application specific code here")) \ .tag("step") # main main = code.Function("main", code.NamedType("int")).tag("main_function") main.append(code.FunctionCall("init").stick_top()) main.append(code.Return(code.IntegerLiteral(1))).stick_bottom() module.select("dec").append(code.Comment("""init and application_step can be implemented using application specific needs."""), init, app, code.Comment("""starting point please don't change anything beyond this point."""), main) # construct an event_loop builder and hook it into the main function event_loop = code.WhileDo(code.BooleanLiteral(True)) main.append(event_loop).tag("event_loop") \ .append(code.Comment("your application gets its share"), code.FunctionCall("application_step")) # allow each domain generator to extend the main section for mod in model.modules.values(): for domain_name, domain in mod.domains.items(): self.generator_for_domain(domain_name).extend(module) def create_executions(self, model): # executions for module_name, module in model.modules.items(): for execution in module.executions: self.generator_for_domain("nodes").link_execution(execution) def generator_for_domain(self, domain_name): """ Lazy-Dynamic-Loading of Domain Generators, based on the Semantic Domain name """ if domain_name not in self.domain_generators: clazz = self.get_class("foo_lang.generator.domains." + domain_name, domain_name.capitalize()) self.domain_generators[domain_name] = clazz(self) return self.domain_generators[domain_name] def get_platform(self, platform_name): clazz = self.get_class("foo_lang.generator.platforms." + platform_name, platform_name.capitalize()) return clazz() def get_class(self, module_name, class_name): module = __import__( module_name, fromlist=[class_name]) return getattr(module, class_name)