def test_model_with_modules(self): module1 = self.create_module_with_constants("module1") obj = self.create_object_with_properties() module2 = self.create_module_with_extension("module2", Nodes(), obj) self.model.modules['mod1'] = module1 self.model.modules['mod2'] = module2 self.assertModelEquals("module module1\n" + \ "const name0 : type0 = 0\n" + \ "const name1 : type1 = 1\n" + \ "module module2\n" + \ "extend nodes with " + dump(obj) + "\n")
def visit_module(self, tree): assert tree.text == "MODULE" children = tree.getChildren() id = self.visit(children[0]) module = Module(id) # by default a module contains the Nodes domain # more generic this could be triggered by a USE <domain> instruction module.domains['nodes'] = Nodes() self.model.modules[id.name] = module self.current_module = module if len(children) > 1: for index in range(1, len(children)): self.visit(children[index]) return module
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)
def create_model(): model = Model() for module in model.modules: if not module.domains['nodes']: module.domains['nodes'] = Nodes() return model
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)
def test_module_with_extensions(self): obj = self.create_object_with_properties() module = self.create_module_with_extension("moduleName", Nodes(), obj) self.assertEqual(dump(module), "module moduleName\n" + \ "extend nodes with " + dump(obj) + "\n")
def test_extension_with_object_extension(self): obj = self.create_object_with_properties() ext = Extension(Nodes(), obj) self.assertEqual(dump(ext), "extend nodes with " + dump(obj))
def test_every_strategy(self): function = self.create_function("name") strategy = Every(AllNodes(Nodes()), function, \ VariableExp(Identifier("interval"))) self.assertEqual(dump(strategy), "@every(interval)\n" + \ "with nodes do " + dump(function))
def test_domain_specific_scope(self): scope = Nodes().get_scope("self") self.assertEqual(dump(scope), "nodes.self")
def test_domain_scope(self): scope = Nodes().get_scope() self.assertEqual(dump(scope), "nodes")