def setUp(self): self.sut_proto = ProtoRenderer('ns').set_comment_ignored( True).set_ignore_no_export(False) self.sut_cpp = CppRenderer( 'ns', 'dfproto', 'DFProto').set_comment_ignored(True).set_ignore_no_export(False) self.maxDiff = None
def render_h(self): rdr = CppRenderer(self.ns, self.proto_ns, 'DFProto') out = '/* THIS FILE WAS GENERATED. DO NOT EDIT. */\n' out += '#include "DataDefs.h"\n' out += '#include "Export.h"\n' out += '#include <stdint.h>\n' out += '#include \"df/%s.h\"\n' % (self.get_type_name()) out += '#include \"%s.pb.h\"\n' % (self.get_type_name()) out += '\nnamespace DFProto {\n' out += ' %s\n' % (rdr.render_prototype(self.xml)) out += '}\n' return out
class TestCppRenderer(unittest.TestCase): output = '' @classmethod def setUpClass(cls): pass def setUp(self): self.sut = CppRenderer( 'ns', 'dfproto', 'DFProto').set_comment_ignored(True).set_ignore_no_export(False) self.maxDiff = None @classmethod def tearDownClass(cls): if not cls.output: return with open(OUTPUT_FNAME, 'a') as fil: fil.write(cls.output) subprocess.check_call( ['protoc -I. -o%s.pb %s' % (OUTPUT_FNAME, OUTPUT_FNAME)], shell=True) os.remove(OUTPUT_FNAME) os.remove(OUTPUT_FNAME + '.pb') def assertStructEqual(self, str1, str2): self.assertEqual(''.join(str1.split()), ''.join(str2.split()), str1 + '/' + str2) # # test exceptions # def test_struct_index_field(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="struct-type" ld:level="0" type-name="interaction"> <ld:field ld:meta="container" ld:level="1" ld:subtype="stl-vector" name="targets" pointer-type="interaction_target" ld:is-container="true"> <ld:item ld:meta="pointer" ld:is-container="true" ld:level="2" type-name="interaction_target"> <ld:item ld:level="3" ld:meta="global" type-name="interaction_target"/> </ld:item> </ld:field> </ld:global-type> </ld:data-definition> """ root = etree.fromstring(XML) self.sut.add_exception_index('interaction_target', 'index') out = self.sut.render_type(root[0]) self.assertStructEqual( out, """ void DFProto::describe_interaction(dfproto::interaction* proto, df::interaction* dfhack) { for (size_t i=0; i<dfhack->targets.size(); i++) { proto->add_targets_index(dfhack->targets[i]->index); } } """) # # prototype # def test_render_prototype(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="bitfield-type" ld:level="0" type-name="announcement_flags"> </ld:global-type> </ld:data-definition> """ root = etree.fromstring(XML) out = self.sut.render_prototype(root[0]) self.assertEqual(len(self.sut.imports), 0) self.assertStructEqual( out, """ void describe_announcement_flags(dfproto::announcement_flags* proto, df::announcement_flags* dfhack); """) self.output += out + '\n' def _test_debug(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="class-type" ld:level="0" type-name="viewscreen_selectitemst" inherits-from="viewscreen"> <ld:field ld:level="1" ld:meta="pointer" since="v0.47.02" ld:is-container="true"/> </ld:global-type> </ld:data-definition> """ root = etree.fromstring(XML) out = self.sut.render_type(root[0]) print(self.sut.imports) print(out) self.assertEqual(len(self.sut.imports), 0) self.assertStructEqual( out, """ void describe_announcement_flags(dfproto::announcement_flags* proto, df::announcement_flags* dfhack); """) self.output += out + '\n' def _test_render_global_types(self): tree = etree.parse('codegen/codegen.out.xml') root = tree.getroot() ns = re.match(r'{.*}', root.tag).group(0) sut = ProtoRenderer(ns) for e in root: print('line ' + str(e.sourceline) + ':', e.get(f'{ns}meta'), e.get(f'type-name')) out = sut.render_type(e) self.output += out + '\n'
class TestRenderType(unittest.TestCase): proto_output = '' cpp_output = '' @classmethod def setUpClass(cls): with open(PROTO_FNAME, 'w') as fil: fil.write('syntax = "proto2";\n') with open(CPP_FNAME, 'w') as fil: fil.write('') def setUp(self): self.sut_proto = ProtoRenderer('ns').set_comment_ignored( True).set_ignore_no_export(False) self.sut_cpp = CppRenderer( 'ns', 'dfproto', 'DFProto').set_comment_ignored(True).set_ignore_no_export(False) self.maxDiff = None def tearDown(self): with open(PROTO_FNAME, 'a') as fil: fil.write(self.proto_output) with open(CPP_FNAME, 'a') as fil: fil.write(self.cpp_output) # @classmethod # def tearDownClass(cls): # os.remove(PROTO_FNAME) # os.remove(CPP_FNAME) def assertStructEqual(self, str1, str2): self.assertEqual(''.join(str1.split()), ''.join(str2.split()), str1 + '/' + str2) def check_rendering(self, XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS): xml = etree.fromstring(XML)[0] out = self.sut_proto.render_type(xml) if len(PROTO): self.assertStructEqual(out, PROTO) self.assertEqual(sorted(list(self.sut_proto.imports)), sorted(IMPORTS)) self.proto_output += out if CPP: out = self.sut_cpp.render_type(xml) self.assertStructEqual(out, CPP) self.assertEqual(sorted(list(self.sut_cpp.dfproto_imports)), sorted(DFPROTO_IMPORTS)) self.cpp_output += out # # enum # def test_render_type_enum(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="enum-type" ld:level="0" type-name="ui_advmode_menu" base-type="int16_t"> <enum-item name="Default" value="0"/> <enum-item name="Look"/> </ld:global-type> </ld:data-definition> """ PROTO = """ enum ui_advmode_menu { ui_advmode_menu_Default = 0; ui_advmode_menu_Look = 1; } """ CPP = """ void DFProto::describe_ui_advmode_menu(dfproto::ui_advmode_menu* proto, df::ui_advmode_menu* dfhack) { *proto = static_cast<dfproto::ui_advmode_menu>(*dfhack); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_type_enum_with_values(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="enum-type" ld:level="0" type-name="conflict_level"> <enum-item name="None" value="-1"/> <enum-item name="Encounter"/> <enum-item name="Horseplay"/> <enum-item value="-3"/> </ld:global-type> </ld:data-definition> """ PROTO = """ enum conflict_level { conflict_level_Encounter = 0; conflict_level_Horseplay = 1; conflict_level_None = -1; conflict_level_anon_1 = -3; } """ CPP = """ void DFProto::describe_conflict_level(dfproto::conflict_level* proto, df::conflict_level* dfhack) { *proto = static_cast<dfproto::conflict_level>(*dfhack); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) # # bitfield # def test_render_type_bitfield(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="bitfield-type" ld:level="0" type-name="announcement_flags"> <ld:field name="DO_MEGA" comment="BOX" ld:level="1" ld:meta="number" ld:subtype="flag-bit" ld:bits="1"/> <ld:field name="PAUSE" comment="P" ld:level="1" ld:meta="number" ld:subtype="flag-bit" ld:bits="1"/> <ld:field name="RECENTER" comment="R" ld:level="1" ld:meta="number" ld:subtype="flag-bit" ld:bits="1"/> </ld:global-type> </ld:data-definition> """ PROTO = """ message announcement_flags { enum mask { do_mega = 0x0; /* BOX */ pause = 0x1; /* P */ recenter = 0x2; /* R */ } required fixed32 flags = 1; } """ CPP = """ void DFProto::describe_announcement_flags(dfproto::announcement_flags* proto, df::announcement_flags* dfhack) { proto->set_flags(dfhack->whole); } """ CPP = None IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) # # struct/class # def test_render_type_struct_with_primitive_fields(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="struct-type" ld:level="0" type-name="conversation1"> <ld:field name="conv_title" ld:level="1" ld:meta="primitive" ld:subtype="stl-string"/> <ld:field name="num_30" ref-target="unit" ld:level="1" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:global-type> </ld:data-definition> """ PROTO = """ message conversation1 { required string conv_title = 1; required int32 num_30 = 2; } """ CPP = """ void DFProto::describe_conversation1(dfproto::conversation1* proto, df::conversation1* dfhack) { proto->set_conv_title(dfhack->conv_title); proto->set_num_30(dfhack->num_30); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_type_struct(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="struct-type" ld:level="0" type-name="campfire"> <ld:field type-name="coord" name="pos" ld:level="1" ld:meta="global"/> <ld:field name="timer" ld:level="1" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:global-type> </ld:data-definition> """ PROTO = """ message campfire { required coord pos = 1; required int32 timer = 2; } """ CPP = """ void DFProto::describe_campfire(dfproto::campfire* proto, df::campfire* dfhack) { describe_coord(proto->mutable_pos(), &dfhack->pos); proto->set_timer(dfhack->timer); } """ IMPORTS = ['coord'] DFPROTO_IMPORTS = ['coord'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_type_struct_with_anon_fields(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="struct-type" ld:level="0" type-name="entity_site_link"> <ld:field name="target" ref-target="world_site" comment="world.world_data.sites vector" ld:level="1" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> <ld:field name="entity_id" ref-target="historical_entity" ld:level="1" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> <ld:field ld:level="1" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> <ld:field ld:level="1" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:global-type> </ld:data-definition> """ PROTO = """ message entity_site_link { required int32 target = 1; /* world.world_data.sites vector */ required int32 entity_id = 2; required int32 anon_1 = 3; required int32 anon_2 = 4; } """ CPP = """ void DFProto::describe_entity_site_link(dfproto::entity_site_link* proto, df::entity_site_link* dfhack) { proto->set_target(dfhack->target); proto->set_entity_id(dfhack->entity_id); proto->set_anon_1(dfhack->anon_1); proto->set_anon_2(dfhack->anon_2); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_type_struct_with_local_bitfield_and_anon_flags(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="struct-type" ld:level="0" type-name="entity_site_link"> <ld:field ld:level="1" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> <ld:field ld:subtype="bitfield" name="flags" base-type="uint32_t" ld:level="1" ld:meta="compound"> <ld:field name="residence" comment="site is residence" ld:level="2" ld:meta="number" ld:subtype="flag-bit" ld:bits="1"/> <ld:field ld:level="2" ld:meta="number" ld:subtype="flag-bit" ld:bits="1"/> </ld:field> <ld:field ld:level="1" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:global-type> </ld:data-definition> """ PROTO = """ message entity_site_link { required int32 anon_1 = 1; message T_flags { enum mask { residence = 0x0; /* site is residence */ anon_1 = 0x1; } required fixed32 flags = 1; } required T_flags flags = 2; required int32 anon_2 = 3; } """ CPP = """ void DFProto::describe_entity_site_link(dfproto::entity_site_link* proto, df::entity_site_link* dfhack) { proto->set_anon_1(dfhack->anon_1); auto describe_T_flags = [](dfproto::entity_site_link_T_flags* proto, df::entity_site_link::T_flags* dfhack) { proto->set_flags(dfhack->whole); }; describe_T_flags(proto->mutable_flags(), &dfhack->flags); proto->set_anon_2(dfhack->anon_2); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_type_struct_with_enum_and_union(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="struct-type" ld:level="0" type-name="history_event_reason_info"> <ld:field ld:subtype="enum" name="type" type-name="history_event_reason" base-type="int32_t" ld:level="1" ld:meta="global"/> <ld:field name="data" is-union="true" init-value="-1" ld:level="1" ld:meta="compound" ld:typedef-name="T_data" ld:in-union="true"> <ld:field name="glorify_hf" ref-target="historical_figure" ld:level="2" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> <ld:field name="artifact_is_heirloom_of_family_hfid" ref-target="historical_figure" ld:level="2" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/>"historical_entity" ld:level="2" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:field> </ld:global-type> </ld:data-definition> """ PROTO = """ message history_event_reason_info { required history_event_reason type = 1; oneof data { int32 glorify_hf = 2; int32 artifact_is_heirloom_of_family_hfid = 3; } } """ CPP = """ void DFProto::describe_history_event_reason_info(dfproto::history_event_reason_info* proto, df::history_event_reason_info* dfhack) { dfproto::history_event_reason type; describe_history_event_reason(&type, &dfhack->type); proto->set_type(type); switch (dfhack->type) { case ::df::enums::history_event_reason::glorify_hf: proto->set_glorify_hf(dfhack->data.glorify_hf); break; case ::df::enums::history_event_reason::artifact_is_heirloom_of_family_hfid: proto->set_artifact_is_heirloom_of_family_hfid(dfhack->data.artifact_is_heirloom_of_family_hfid); break; default: proto->clear_data(); } } """ IMPORTS = ['history_event_reason'] DFPROTO_IMPORTS = ['history_event_reason'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_type_struct_with_container_of_pointers(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="struct-type" ld:level="0" type-name="conversation"> <ld:field ld:meta="container" ld:level="1" ld:subtype="stl-vector" name="nem_54" pointer-type="nemesis_record" ld:is-container="true"> <ld:item ld:meta="pointer" ld:is-container="true" ld:level="2" type-name="nemesis_record"> <ld:item ld:level="3" ld:meta="global" type-name="nemesis_record"/> </ld:item></ld:field> </ld:global-type> </ld:data-definition> """ PROTO = """ message conversation { repeated nemesis_record nem_54 = 1; } """ CPP = """ void DFProto::describe_conversation(dfproto::conversation* proto, df::conversation* dfhack) { for (size_t i=0; i<dfhack->nem_54.size(); i++) { if (dfhack->nem_54[i] != NULL) { describe_nemesis_record(proto->add_nem_54(), dfhack->nem_54[i]); } } } """ IMPORTS = ['nemesis_record'] DFPROTO_IMPORTS = ['nemesis_record'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_type_struct_with_inheritance(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="class-type" ld:level="0" type-name="adventure_item" inherits-from="adventure_item_interact_choicest"> <ld:field ld:level="1" ld:meta="pointer" type-name="item" ld:is-container="true"> <ld:item ld:level="2" ld:meta="global" type-name="item"/> </ld:field> </ld:global-type> </ld:data-definition> """ PROTO = """ message adventure_item { /* parent type */ required adventure_item_interact_choicest parent = 1; optional item anon_1 = 2; } """ CPP = """ void DFProto::describe_adventure_item(dfproto::adventure_item* proto, df::adventure_item* dfhack) { describe_adventure_item_interact_choicest(proto->mutable_parent(), dfhack); if (dfhack->anon_1 != NULL) { describe_item(proto->mutable_anon_1(), dfhack->anon_1); } } """ IMPORTS = ['adventure_item_interact_choicest', 'item'] DFPROTO_IMPORTS = ['adventure_item_interact_choicest', 'item'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_type_with_pointer_to_anon_compound(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="struct-type" ld:level="0" type-name="entity_claim_mask"> <ld:field ld:level="1" ld:meta="pointer" name="map" is-array="true" ld:is-container="true"> <ld:item ld:level="2" ld:meta="pointer" is-array="true" ld:is-container="true"> <ld:item ld:meta="compound" ld:level="2"> <ld:field ld:meta="container" ld:level="3" ld:subtype="stl-vector" name="entities" type-name="int32_t" ref-target="historical_entity" ld:is-container="true"> <ld:item ref-target="historical_entity" ld:level="4" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:field> </ld:item> </ld:item> </ld:field> </ld:global-type> </ld:data-definition> """ PROTO = """ message entity_claim_mask { message T_map { repeated int32 entities = 1; } optional T_map map = 1; } """ CPP = """ void DFProto::describe_entity_claim_mask(dfproto::entity_claim_mask* proto, df::entity_claim_mask* dfhack) { auto describe_T_map = [](dfproto::entity_claim_mask_T_map* proto, df::entity_claim_mask::T_map* dfhack) { for (size_t i=0; i<dfhack->entities.size(); i++) { proto->add_entities(dfhack->entities[i]); } }; if (dfhack->map != NULL) { describe_T_map(proto->mutable_map(), dfhack->map); } } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_type_with_recursive_compounds(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="struct-type" ld:level="0" type-name="historical_entity" key-field="id" instance-vector="$global.world.entities.all"> <ld:field name="resources" ld:level="1" ld:meta="compound"> <ld:field name="metal" ld:level="2" ld:meta="compound"> <ld:field name="pick" type-name="material_vec_ref" ld:level="3" ld:meta="global"/> <ld:field name="weapon" type-name="material_vec_ref" ld:level="3" ld:meta="global"/> </ld:field> </ld:field> </ld:global-type> </ld:data-definition> """ PROTO = """ message historical_entity { message T_resources { message T_metal { required material_vec_ref pick = 1; required material_vec_ref weapon = 2; } required T_metal metal = 1; } required T_resources resources = 1; } """ CPP = """ void DFProto::describe_historical_entity(dfproto::historical_entity* proto, df::historical_entity* dfhack) { auto describe_T_resources = [](dfproto::historical_entity_T_resources* proto, df::historical_entity::T_resources* dfhack) { auto describe_T_metal = [](dfproto::historical_entity_T_resources_T_metal* proto, df::historical_entity::T_resources::T_metal* dfhack) { describe_material_vec_ref(proto->mutable_pick(), &dfhack->pick); describe_material_vec_ref(proto->mutable_weapon(), &dfhack->weapon); }; describe_T_metal(proto->mutable_metal(), &dfhack->metal); }; describe_T_resources(proto->mutable_resources(), &dfhack->resources); } """ IMPORTS = ['material_vec_ref'] DFPROTO_IMPORTS = ['material_vec_ref'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_type_class(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="class-type" ld:level="0" type-name="adventure_movement_optionst" comment="comment"> <ld:field name="dest" type-name="coord" ld:level="1" ld:meta="global"/> <ld:field name="source" type-name="coord" ld:level="1" ld:meta="global"/> <virtual-methods> <vmethod ld:level="1"><ld:field ld:level="2" ld:meta="pointer" ld:is-container="true"/></vmethod> <vmethod ld:level="1"/> </virtual-methods> </ld:global-type> </ld:data-definition> """ PROTO = """ /* comment */ message adventure_movement_optionst { required coord dest = 1; required coord source = 2; } """ CPP = """ void DFProto::describe_adventure_movement_optionst(dfproto::adventure_movement_optionst* proto, df::adventure_movement_optionst* dfhack) { describe_coord(proto->mutable_dest(), &dfhack->dest); describe_coord(proto->mutable_source(), &dfhack->source); } """ IMPORTS = ['coord'] DFPROTO_IMPORTS = ['coord'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_type_class_with_methods(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="class-type" ld:level="0" type-name="item"> <ld:field name="id" ld:level="1" ld:meta="number" ld:subtype="int16_t"/> <virtual-methods> <vmethod ld:level="1" ret-type="item_type" name="getType" export="true"><ret-type ld:level="2" ld:meta="global" type-name="item_type"/></vmethod> <vmethod ld:level="1" ret-type="int16_t" name="getSubtype" export="true"><ret-type ld:level="2" ld:meta="number" ld:subtype="int16_t" ld:bits="16"/></vmethod> </virtual-methods> </ld:global-type> </ld:data-definition> """ PROTO = """ message item { required int32 id = 1; required item_type type = 2; required int32 subtype = 3; } """ CPP = """ void DFProto::describe_item(dfproto::item* proto, df::item* dfhack) { proto->set_id(dfhack->id); df::item_type df_type = dfhack->getType(); dfproto::item_type type; describe_item_type(&type, &df_type); proto->set_type(type); proto->set_subtype(dfhack->getSubtype()); } """ IMPORTS = ['item_type'] DFPROTO_IMPORTS = ['item_type'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) # # exceptions # def test_ignore_field(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="class-type" ld:level="0" type-name="general_ref" original-name="general_refst"> <ld:field ld:meta="container" ld:level="1" ld:subtype="stl-vector" name="general_refs" pointer-type="general_ref" ld:is-container="true"> <ld:item ld:meta="pointer" ld:is-container="true" ld:level="2" type-name="general_ref"> <ld:item ld:level="3" ld:meta="global" type-name="general_ref"/> </ld:item> </ld:field> </ld:global-type> </ld:data-definition> """ PROTO = """ message general_ref { /* ignored field general_refs */ } """ CPP = """ void DFProto::describe_general_ref(dfproto::general_ref* proto, df::general_ref* dfhack) { } """ IMPORTS = [] DFPROTO_IMPORTS = [] FILTER = 'ld:global-type[@type-name="general_ref"]/ld:field[@name="general_refs"]' self.sut_proto.add_exception_ignore(FILTER) self.sut_cpp.add_exception_ignore(FILTER) self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_ignore_regex(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="class-type" ld:level="0" type-name="mytype"> <ld:field name="unk_1" ld:level="1" ld:meta="number" ld:subtype="int16_t" ld:bits="16"/> <ld:field name="id" ld:level="1" ld:meta="number" ld:subtype="int16_t" ld:bits="16"/> <ld:field name="unk_2" ld:level="1" ld:meta="number" ld:subtype="int16_t" ld:bits="16"/> </ld:global-type> </ld:data-definition> """ PROTO = """ message mytype { /* ignored field unk_1 */ required int32 id = 2; /* ignored field unk_2 */ } """ CPP = """ void DFProto::describe_mytype(dfproto::mytype* proto, df::mytype* dfhack) { proto->set_id(dfhack->id); } """ IMPORTS = [] DFPROTO_IMPORTS = [] FILTER = "ld:global-type[@type-name='mytype']/ld:field[re:match(@name, 'unk_[0-9]+')]" self.sut_proto.add_exception_ignore(FILTER) self.sut_cpp.add_exception_ignore(FILTER) self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_rename_field(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="struct-type" ld:level="0" type-name="entity_position_raw"> <ld:field name="squad_size" ld:level="1" ld:meta="number" ld:subtype="int16_t" ld:bits="16"/> </ld:global-type> </ld:data-definition> """ PROTO = """ message entity_position_raw { required int32 squad_sz = 1; } """ CPP = """ void DFProto::describe_entity_position_raw(dfproto::entity_position_raw* proto, df::entity_position_raw* dfhack) { proto->set_squad_sz(dfhack->squad_size); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.sut_cpp.add_exception_rename( 'ld:global-type[@type-name="entity_position_raw"]/ld:field[@name="squad_size"]', 'squad_sz') self.sut_proto.add_exception_rename( 'ld:global-type[@type-name="entity_position_raw"]/ld:field[@name="squad_size"]', 'squad_sz') self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_index_field(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="struct-type" ld:subtype="df-linked-list-type" ld:level="0" type-name="job_list_link" item-type="job"> <ld:field name="next" type-name="job_list_link" ld:level="1" ld:meta="pointer" ld:is-container="true"> <ld:item ld:level="2" ld:meta="global" type-name="job_list_link"/> </ld:field> </ld:global-type> </ld:data-definition> """ PROTO = """ message job_list_link { optional int32 next_id = 1; } """ CPP = """ void DFProto::describe_job_list_link(dfproto::job_list_link* proto, df::job_list_link* dfhack) { proto->set_next_id(dfhack->next->id); } """ IMPORTS = [] DFPROTO_IMPORTS = ['job_list_link'] self.sut_cpp.add_exception_index('job_list_link', 'id') self.sut_proto.add_exception_index('job_list_link', 'id') self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) # # non-regression tests from bug fixing # def test_bugfix_pointer_with_comment(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="class-type" ld:level="0" type-name="job_handler" original-name="job_handlerst" custom-methods="true"> <ld:field ld:level="2" ld:meta="pointer" type-name="unit" comment="List" ld:is-container="true"> <ld:item ld:level="3" ld:meta="global" type-name="unit"/> </ld:field> <ld:field ld:level="2" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:global-type> </ld:data-definition> """ PROTO = """ message job_handler { /* List */ optional unit anon_1 = 1; required int32 anon_2 = 2; } """ CPP = """ void DFProto::describe_job_handler(dfproto::job_handler* proto, df::job_handler* dfhack) { if (dfhack->anon_1 != NULL) { describe_unit(proto->mutable_anon_1(), dfhack->anon_1); } proto->set_anon_2(dfhack->anon_2); } """ IMPORTS = ['unit'] DFPROTO_IMPORTS = ['unit'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_bugfix_multiple_anon_compounds_and_fields(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:global-type ld:meta="class-type" ld:level="0" type-name="job_handler" original-name="job_handlerst" custom-methods="true"> <ld:field ld:meta="container" ld:level="1" ld:subtype="stl-vector" ld:is-container="true"> <ld:item ld:level="2" ld:meta="pointer" ld:is-container="true"> <ld:item ld:meta="compound" ld:level="2"> <ld:field ld:level="3" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:item> </ld:item> </ld:field> <ld:field ld:level="1" ld:meta="static-array" count="2000" ld:is-container="true"> <ld:item ld:meta="compound" ld:level="1"> <ld:field ld:level="2" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:item> </ld:field> </ld:global-type> </ld:data-definition> """ PROTO = """ message job_handler { message T_anon_1 { required int32 anon_1 = 1; } repeated T_anon_1 anon_1 = 1; message T_anon_2 { required int32 anon_1 = 1; } repeated T_anon_2 anon_2 = 2; } """ CPP = """ void DFProto::describe_job_handler(dfproto::job_handler* proto, df::job_handler* dfhack) { auto describe_T_anon_1 = [](dfproto::job_handler_T_anon_1* proto, df::job_handler::T_anon_1* dfhack) { proto->set_anon_1(dfhack->anon_1); }; for (size_t i=0; i<dfhack->anon_1.size(); i++) { if (dfhack->anon_1[i] != NULL) { describe_T_anon_1(proto->add_anon_1(), dfhack->anon_1[i]); } } auto describe_T_anon_2 = [](dfproto::job_handler_T_anon_2* proto, df::job_handler::T_anon_2* dfhack) { proto->set_anon_1(dfhack->anon_1); }; for (size_t i=0; i<2000; i++) { describe_T_anon_2(proto->add_anon_2(), &dfhack->anon_2[i]); } } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS)
class TestRenderField(unittest.TestCase): def setUp(self): self.sut_proto = ProtoRenderer('ns').set_comment_ignored(True).set_ignore_no_export(False) self.sut_cpp = CppRenderer('ns', 'dfproto', 'DFProto').set_comment_ignored(True).set_ignore_no_export(False) self.maxDiff= None def assertStructEqual(self, str1, str2): self.assertEqual(''.join(str1.split()), ''.join(str2.split()), str1+'/'+str2) def check_rendering(self, XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, TYPE=None): xml = etree.fromstring(XML)[0] out = self.sut_proto.render_field(xml) self.assertStructEqual(out, PROTO) self.assertEqual(list(self.sut_proto.imports), IMPORTS) self.sut_cpp.outer_types = [TYPE] out = self.sut_cpp.render_field(xml) self.assertStructEqual(out, CPP) self.assertEqual(list(self.sut_cpp.imports), IMPORTS) self.assertEqual(list(self.sut_cpp.dfproto_imports), DFPROTO_IMPORTS) # # enum # def test_render_field_enum(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:subtype="enum" name="type" base-type="int32_t" type-name="talk_choice_type" ld:level="1" ld:meta="global"/> </ld:data-definition> """ PROTO = """ required talk_choice_type type = 1; """ CPP = """ dfproto::talk_choice_type type; describe_talk_choice_type(&type, &dfhack->type); proto->set_type(type); """ IMPORTS = ['talk_choice_type'] DFPROTO_IMPORTS = ['talk_choice_type'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_field_local_enum(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:subtype="enum" base-type="int32_t" name="state" ld:level="1" ld:meta="compound" ld:typedef-name="T_state"> <enum-item name="started"/> <enum-item name="active"/> </ld:field> </ld:data-definition> """ PROTO = """ enum T_state { T_state_started = 0; T_state_active = 1; } required T_state state = 1; """ CPP = """ auto describe_T_state = [](dfproto::mytype_T_state* proto, df::mytype::T_state* dfhack) { *proto = static_cast<dfproto::mytype_T_state>(*dfhack); }; dfproto::mytype_T_state state; describe_T_state(&state, &dfhack->state); proto->set_state(state); """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype') def test_render_field_anon_enum(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:subtype="enum" base-type="int32_t" name="role" ld:level="1" ld:meta="compound"> <enum-item name="Other" comment="eat, drink, pickup equipment"/> <enum-item name="Reagent"/> </ld:field> </ld:data-definition> """ PROTO = """ enum T_role { T_role_Other = 0; /* eat, drink, pickup equipment */ T_role_Reagent = 1; } required T_role role = 1; """ CPP = """ auto describe_T_role = [](dfproto::mytype_T_role* proto, df::mytype::T_role* dfhack) { *proto = static_cast<dfproto::mytype_T_role>(*dfhack); }; dfproto::mytype_T_role role; describe_T_role(&role, &dfhack->role); proto->set_role(role); """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype') # # bitfield # def test_render_field_bitfield(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:subtype="bitfield" name="flags_0" type-name="knowledge_scholar_flags_0" ld:level="2" ld:meta="global"/> </ld:data-definition> """ PROTO = """ required knowledge_scholar_flags_0 flags_0 = 1; """ CPP = """ describe_knowledge_scholar_flags_0(proto->mutable_flags_0(), &dfhack->flags_0); """ IMPORTS = ['knowledge_scholar_flags_0'] DFPROTO_IMPORTS = ['knowledge_scholar_flags_0'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_field_local_bitfield(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:subtype="bitfield" name="gems_use" ld:level="1" ld:meta="compound"> <ld:field name="noun" ld:level="2" ld:meta="number" ld:subtype="flag-bit" ld:bits="1"/> <ld:field name="adj" ld:level="2" ld:meta="number" ld:subtype="flag-bit" ld:bits="1"/> <ld:field name="adj_noun" ld:level="2" ld:meta="number" ld:subtype="flag-bit" ld:bits="1"/> </ld:field> </ld:data-definition> """ PROTO = """ message T_gems_use { enum mask { noun = 0x0; adj = 0x1; adj_noun = 0x2; } required fixed32 flags = 1; } required T_gems_use gems_use = 1; """ CPP = """ auto describe_T_gems_use = [](dfproto::mytype_T_gems_use* proto, df::mytype::T_gems_use* dfhack) { proto->set_flags(dfhack->whole); }; describe_T_gems_use(proto->mutable_gems_use(), &dfhack->gems_use); """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype') def test_render_field_anon_bitfield(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:subtype="bitfield" since="v0.42.01" ld:level="1" ld:meta="compound" ld:anon-name="anon_3" ld:typedef-name="T_anon_3"> <ld:field name="petition_not_accepted" comment="this gets unset by accepting a petition" ld:level="2" ld:meta="number" ld:subtype="flag-bit" ld:bits="1"/> <ld:field name="convicted_accepted" comment="convicted for PositionCorruption/accepted for Location" ld:level="2" ld:meta="number" ld:subtype="flag-bit" ld:bits="1"/> </ld:field> </ld:data-definition> """ PROTO = """ message T_anon_3 { enum mask { petition_not_accepted = 0x0; /* this gets unset by accepting a petition */ convicted_accepted = 0x1; /* convicted for PositionCorruption/accepted for Location */ } required fixed32 flags = 1; } required T_anon_3 anon_3 = 1; """ CPP = """ auto describe_T_anon_3 = [](dfproto::mytype_T_anon_3* proto, df::mytype::T_anon_3* dfhack) { proto->set_flags(dfhack->whole); }; describe_T_anon_3(proto->mutable_anon_3(), &dfhack->anon_3); """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype') # # df-linked-list # def test_render_field_job_list_link(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:meta="container" ld:level="1" ld:subtype="df-linked-list" name="list" type-name="job_list_link" ld:is-container="true"> <ld:item ld:level="2" ld:meta="global" type-name="job_list_link"/> </ld:field> </ld:data-definition> """ PROTO = """ required job_list_link list = 1; """ CPP = """ describe_job_list_link(proto->mutable_list(), &dfhack->list); """ IMPORTS = ['job_list_link'] DFPROTO_IMPORTS = ['job_list_link'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) # # container # def test_render_field_container_vector_of_primitives(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:meta="container" ld:level="1" ld:subtype="stl-vector" type-name="int16_t" name="talk_choices" ld:is-container="true"> <ld:item ld:level="2" ld:meta="number" ld:subtype="int16_t" ld:bits="16"/> </ld:field> </ld:data-definition> """ PROTO = """ repeated int32 talk_choices = 1; """ CPP = """ for (size_t i=0; i<dfhack->talk_choices.size(); i++) { proto->add_talk_choices(dfhack->talk_choices[i]); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_field_container_array_of_primitives(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:level="1" ld:meta="static-array" name="words" count="7" ld:is-container="true"> <ld:item ref-target="language_word" ld:level="2" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:field> </ld:data-definition> """ PROTO = """ repeated int32 words = 1; """ CPP = """ for (size_t i=0; i<7; i++) { proto->add_words(dfhack->words[i]); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_field_container_array_of_global_type(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:level="1" ld:meta="static-array" name="armorstand_pos" count="6" ld:is-container="true"> <ld:item type-name="coord" ld:level="2" ld:meta="global"/> </ld:field> </ld:data-definition> """ PROTO = """ repeated coord armorstand_pos = 1; """ CPP = """ for (size_t i=0; i<6; i++) { describe_coord(proto->add_armorstand_pos(), &dfhack->armorstand_pos[i]); } """ IMPORTS = ['coord'] DFPROTO_IMPORTS = ['coord'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_field_container_of_anon_compounds(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:level="2" ld:meta="static-array" name="approx" count="40" since="v0.40.01" comment="10 * cosine/sine of the index in units of 1/40 of a circle" ld:is-container="true"> <ld:item ld:meta="compound" ld:level="2"> <ld:field name="cos" ld:level="3" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> <ld:field name="sin" ld:level="3" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:item> </ld:field> </ld:data-definition> """ PROTO = """ /* 10 * cosine/sine of the index in units of 1/40 of a circle */ message T_approx { required int32 cos = 1; required int32 sin = 2; } repeated T_approx approx = 1; """ CPP = """ auto describe_T_approx = [](dfproto::mytype_T_approx* proto, df::mytype::T_approx* dfhack) { proto->set_cos(dfhack->cos); proto->set_sin(dfhack->sin); }; for (size_t i=0; i<40; i++) { describe_T_approx(proto->add_approx(), &dfhack->approx[i]); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype') def test_render_field_container_of_bitfields(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:meta="container" ld:level="1" ld:subtype="stl-vector" name="killed_undead" ld:is-container="true"> <ld:item ld:subtype="bitfield" base-type="uint16_t" ld:level="2" ld:meta="compound"> <ld:field name="zombie" ld:level="3" ld:meta="number" ld:subtype="flag-bit" ld:bits="1"/> <ld:field name="ghostly" ld:level="3" ld:meta="number" ld:subtype="flag-bit" ld:bits="1"/> </ld:item> </ld:field> </ld:data-definition> """ PROTO = """ message T_killed_undead { enum mask { zombie = 0x0; ghostly = 0x1; } required fixed32 flags = 1; } repeated T_killed_undead killed_undead = 1; """ CPP = """ auto describe_T_killed_undead = [](dfproto::mytype_T_killed_undead* proto, df::mytype::T_killed_undead* dfhack) { proto->set_flags(dfhack->whole); }; for (size_t i=0; i<dfhack->killed_undead.size(); i++) { describe_T_killed_undead(proto->add_killed_undead(), &dfhack->killed_undead[i]); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype') def test_render_field_container_of_empty_bitfields(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:meta="container" ld:level="1" ld:subtype="stl-vector" name="can_connect" ld:is-container="true"> <ld:item ld:subtype="bitfield" type-name="machine_conn_modes" ld:level="2" ld:meta="global"/> </ld:field> </ld:data-definition> """ PROTO = """ message machine_conn_modes { required fixed32 flags = 1; } repeated machine_conn_modes can_connect = 1; """ CPP = """ auto describe_machine_conn_modes = [](dfproto::mytype_machine_conn_modes* proto, df::mytype::machine_conn_modes* dfhack) { proto->set_flags(dfhack->whole); }; for (size_t i=0; i<dfhack->can_connect.size(); i++) { describe_machine_conn_modes(proto->add_can_connect(), &dfhack->can_connect[i]); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype') def test_render_field_container_of_enums(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:level="1" ld:meta="static-array" name="parts_of_speech" count="7" ld:is-container="true"> <ld:item ld:subtype="enum" base-type="int16_t" type-name="part_of_speech" ld:level="2" ld:meta="global"/> </ld:field> </ld:data-definition> """ PROTO = """ repeated part_of_speech parts_of_speech = 1; """ CPP = """ for (size_t i=0; i<7; i++) { dfproto::part_of_speech value; describe_part_of_speech(&value, &dfhack->parts_of_speech[i]); proto->add_parts_of_speech(value); } """ IMPORTS = ['part_of_speech'] DFPROTO_IMPORTS = ['part_of_speech'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype') # suffix '_type' is automatically interpreted as an enum type def test_render_field_container_of_enums_with_suffix_type(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:level="2" ld:meta="static-array" name="relationship" type-name="vague_relationship_type" count="6" comment="unused elements are uninitialized" ld:is-container="true"> <ld:item ld:level="3" ld:meta="global" type-name="vague_relationship_type"/> </ld:field> </ld:data-definition> """ PROTO = """ /* unused elements are uninitialized */ repeated vague_relationship_type relationship = 1; """ CPP = """ for (size_t i=0; i<6; i++) { dfproto::vague_relationship_type value; describe_vague_relationship_type(&value, &dfhack->relationship[i]); proto->add_relationship(value); } """ IMPORTS = ['vague_relationship_type'] DFPROTO_IMPORTS = ['vague_relationship_type'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_field_container_of_local_enums(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:meta="container" ld:level="1" ld:subtype="stl-vector" name="options" ld:is-container="true"> <ld:item ld:subtype="enum" base-type="int32_t" name="options" ld:level="2" ld:meta="compound"> <enum-item name="Return"/> <enum-item name="Save"/> </ld:item> </ld:field> </ld:data-definition> """ PROTO = """ enum T_options { T_options_Return = 0; T_options_Save = 1; } repeated T_options options = 1; """ CPP = """ auto describe_T_options = [](dfproto::mytype_T_options* proto, df::mytype::T_options* dfhack) { *proto = static_cast<dfproto::mytype_T_options>(*dfhack); }; for (size_t i=0; i<dfhack->options.size(); i++) { dfproto::mytype_T_options value; describe_T_options(&value, &dfhack->options[i]); proto->add_options(value); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype') def test_render_field_empty_container(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:meta="container" ld:level="1" ld:subtype="stl-vector" since="v0.40.01" comment="not saved" ld:is-container="true"/> </ld:data-definition> """ PROTO = """ /* ignored container anon_1 */ """ CPP = """ /* ignored empty container anon_1 */ """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) # # container of pointers # def test_render_field_container_of_pointers_to_primitive(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:meta="container" ld:level="1" ld:subtype="stl-vector" name="name_singular" pointer-type="stl-string" ld:is-container="true"> <ld:item ld:meta="pointer" ld:is-container="true" ld:level="2" type-name="stl-string"> <ld:item ld:level="3" ld:meta="primitive" ld:subtype="stl-string"/> </ld:item> </ld:field> </ld:data-definition> """ PROTO = """ repeated string name_singular = 1; """ CPP = """ for (size_t i=0; i<dfhack->name_singular.size(); i++) { if (dfhack->name_singular[i] != NULL) { proto->add_name_singular(*dfhack->name_singular[i]); } } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_field_container_of_pointers_to_global_type(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:meta="container" ld:level="1" ld:subtype="stl-vector" name="children" pointer-type="building" ld:is-container="true"> <ld:item ld:meta="pointer" ld:is-container="true" ld:level="2" type-name="building"> <ld:item ld:level="3" ld:meta="global" type-name="building"/> </ld:item> </ld:field> </ld:data-definition> """ PROTO = """ repeated building children = 1; """ CPP = """ for (size_t i=0; i<dfhack->children.size(); i++) { if (dfhack->children[i] != NULL) { describe_building(proto->add_children(), dfhack->children[i]); } } """ IMPORTS = ['building'] DFPROTO_IMPORTS = ['building'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_field_container_of_pointers_to_anon_compound(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:meta="container" ld:level="1" ld:subtype="stl-vector" name="postings" comment="entries never removed" ld:is-container="true"> <ld:item ld:level="2" ld:meta="pointer" ld:is-container="true"> <ld:item ld:meta="compound" ld:level="2"> <ld:field name="idx" comment="equal to position in vector" ld:level="3" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:item> </ld:item> </ld:field> </ld:data-definition> """ PROTO = """ /* entries never removed */ message T_postings { required int32 idx = 1; /* equal to position in vector */ } repeated T_postings postings = 1; """ CPP = """ auto describe_T_postings = [](dfproto::mytype_T_postings* proto, df::mytype::T_postings* dfhack) { proto->set_idx(dfhack->idx); }; for (size_t i=0; i<dfhack->postings.size(); i++) { if (dfhack->postings[i] != NULL) { describe_T_postings(proto->add_postings(), dfhack->postings[i]); } } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype') def test_render_field_array_of_vectors(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:level="2" ld:meta="static-array" name="layer_x" count="5" ld:is-container="true"> <ld:item ld:meta="container" ld:level="3" ld:subtype="stl-vector" type-name="int16_t" ld:is-container="true"> <ld:item ld:level="4" ld:meta="number" ld:subtype="int16_t" ld:bits="16"/> </ld:item> </ld:field> </ld:data-definition> """ # FIXME: fix after refactoring PROTO = """ /* ignored container of containers layer_x */ """ CPP = """ /* ignored container of containers layer_x */ """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_field_array_of_arrays(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:level="1" ld:meta="static-array" name="supermovie_sound_time" count="16" ld:is-container="true"> <ld:item ld:level="2" ld:meta="static-array" count="200" type-name="int32_t" ld:is-container="true"> <ld:item ld:level="3" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:item> </ld:field> </ld:data-definition> """ # FIXME: fix after refactoring PROTO = """ /* ignored container of containers supermovie_sound_time */ """ CPP = """ /* ignored container of static-arrays supermovie_sound_time */ """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) @unittest.skip('FIXME') def test_render_field_container_of_pointers_to_containers(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:meta="container" ld:level="3" ld:subtype="stl-vector" name="region_masks" ld:is-container="true"> <ld:item ld:level="4" ld:meta="pointer" ld:is-container="true"> <ld:item ld:level="5" ld:meta="static-array" count="16" ld:is-container="true"> <ld:item ld:level="6" ld:meta="static-array" count="16" type-name="uint8_t" comment="1 bit per entity" ld:is-container="true"><ld:item ld:level="7" ld:meta="number" ld:subtype="uint8_t" ld:unsigned="true" ld:bits="8"/></ld:item> </ld:item> </ld:item> </ld:field> </ld:data-definition> """ # FIXME: fix after refactoring PROTO = """ /* ignored container of containers region_masks */ """ CPP = """ /* ignored container of containers region_masks */ """ CPP = None IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) # # pointer # def test_render_field_pointer_to_global_type(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:level="1" ld:meta="pointer" name="profile" type-name="workshop_profile" ld:is-container="true"> <ld:item ld:level="2" ld:meta="global" type-name="workshop_profile"/> </ld:field> </ld:data-definition> """ PROTO = """ optional workshop_profile profile = 1; """ CPP = """ if (dfhack->profile != NULL) { describe_workshop_profile(proto->mutable_profile(), dfhack->profile); } """ IMPORTS = ['workshop_profile'] DFPROTO_IMPORTS = ['workshop_profile'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_field_anon_pointer(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:level="1" ld:meta="pointer" name="p_mattype" type-name="int16_t" ld:is-container="true"> <ld:item ld:level="2" ld:meta="number" ld:subtype="int16_t" ld:bits="16"/> </ld:field> </ld:data-definition> """ PROTO = """ optional int32 p_mattype = 1; """ CPP = """ if (dfhack->p_mattype != NULL) { proto->set_p_mattype(*dfhack->p_mattype); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_field_pointer_to_anon_compound(self): # FIXME: probably need indirection for dfhack->map -> *dfhack->map XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:level="1" ld:meta="pointer" name="map" is-array="true" ld:is-container="true"> <ld:item ld:level="2" ld:meta="pointer" is-array="true" ld:is-container="true"> <ld:item ld:meta="compound" ld:level="2"> <ld:field ld:meta="container" ld:level="3" ld:subtype="stl-vector" name="entities" type-name="int32_t" ref-target="historical_entity" ld:is-container="true"> <ld:item ref-target="historical_entity" ld:level="4" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:field> </ld:item> </ld:item> </ld:field> </ld:data-definition> """ PROTO = """ message T_map { repeated int32 entities = 1; } optional T_map map = 1; """ CPP = """ auto describe_T_map = [](dfproto::entity_claim_mask_T_map* proto, df::entity_claim_mask::T_map* dfhack) { for (size_t i=0; i<dfhack->entities.size(); i++) { proto->add_entities(dfhack->entities[i]); } }; if (dfhack->map != NULL) { describe_T_map(proto->mutable_map(), dfhack->map); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'entity_claim_mask') def test_render_field_pointer_to_array(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:level="1" ld:meta="pointer" name="temporary_trait_changes" comment="sum of inebriation or so personality changing effects" ld:is-container="true"> <ld:item ld:level="2" ld:meta="static-array" type-name="int16_t" name="traits" count="50" index-enum="personality_facet_type" ld:is-container="true"> <ld:item ld:level="3" ld:meta="number" ld:subtype="int16_t" ld:bits="16"/> </ld:item> </ld:field> </ld:data-definition> """ PROTO = """ /* sum of inebriation or so personality changing effects */ repeated int32 temporary_trait_changes = 1; """ CPP = """ if (dfhack->temporary_trait_changes != NULL) { for (size_t i=0; i<50; i++) { proto->add_temporary_trait_changes((*dfhack->temporary_trait_changes)[i]); } } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) @unittest.skip('FIXME') def test_render_field_pointer_to_vector(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:level="1" ld:meta="pointer" name="spheres" ld:is-container="true"> <ld:item ld:meta="container" ld:level="2" ld:subtype="stl-vector" ld:is-container="true"> <ld:item ld:subtype="enum" base-type="int16_t" type-name="sphere_type" ld:level="3" ld:meta="global"/> </ld:item> </ld:field> </ld:data-definition> """ PROTO = """ repeated sphere_type spheres = 1; """ CPP = """ if (dfhack->spheres != NULL) { for (size_t i=0; i<dfhack->spheres->size(); i++) { dfproto::sphere_type value; describe_sphere_type(&value, &(*dfhack->spheres)[i])); proto->add_spheres(value); } } """ IMPORTS = ['sphere_type'] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS) def test_render_field_pointer_to_unknown_type(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:level="1" ld:meta="pointer" since="v0.47.02" ld:is-container="true"/> </ld:data-definition> """ PROTO = """ /* ignored pointer to unknown type */ """ CPP = """ /* ignored pointer to unknown type */ """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'entity_claim_mask') # # compound # def test_render_field_local_compound(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field name="unk" ld:level="1" ld:meta="compound" ld:typedef-name="T_unk"> <ld:field ld:level="2" ld:meta="pointer" name="event" type-name="entity_event" ld:is-container="true"> <ld:item ld:level="3" ld:meta="global" type-name="entity_event"/> </ld:field> <ld:field ld:level="2" ld:meta="number" ld:subtype="int32_t" ld:bits="32" ld:anon-name="anon_2"/> </ld:field> </ld:data-definition> """ PROTO = """ message T_unk { optional entity_event event = 1; required int32 anon_2 = 2; } required T_unk unk = 1; """ CPP = """ auto describe_T_unk = [](dfproto::mytype_T_unk* proto, df::mytype::T_unk* dfhack) { if (dfhack->event != NULL) { describe_entity_event(proto->mutable_event(), dfhack->event); } proto->set_anon_2(dfhack->anon_2); }; describe_T_unk(proto->mutable_unk(), &dfhack->unk); """ IMPORTS = ['entity_event'] DFPROTO_IMPORTS = ['entity_event'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype') def test_render_field_anon_compound(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:anon-compound="true" ld:level="1" ld:meta="compound"> <ld:field name="x" ld:level="2" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> <ld:field base-type="int16_t" name="item_type" type-name="item_type" ld:level="2" ld:meta="global"/> </ld:field> </ld:data-definition> """ PROTO = """ message T_anon { required int32 x = 1; required item_type item_type = 2; } """ CPP = """ auto describe_T_anon_1 = [](dfproto::mytype_T_anon_1* proto, df::mytype::T_anon_1* dfhack) { proto->set_x(dfhack->x); describe_item_type(proto->mutable_item_type(), &dfhack->item_type); }; describe_T_anon_1(proto->mutable_anon_1(), &dfhack->anon_1); """ IMPORTS = ['item_type'] DFPROTO_IMPORTS = ['item_type'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype') # # union # def test_render_field_union_of_compounds(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field name="abuse_data" is-union="true" ld:level="1" ld:meta="compound"> <ld:field name="Piled" ld:level="2" ld:meta="compound"> <ld:field ld:subtype="enum" name="pile_type" base-type="int32_t" ld:level="3" ld:meta="compound"> <enum-item name="GrislyMound"/> <enum-item name="GrotesquePillar"/> <enum-item name="GruesomeSculpture"/> </ld:field> </ld:field> <ld:field name="Flayed" ld:level="2" ld:meta="compound"> <ld:field name="structure" ref-target="abstract_building" ld:level="3" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:field> </ld:field> </ld:data-definition> """ PROTO = """ message T_piled { enum T_pile_type { T_pile_type_GrislyMound = 0; T_pile_type_GrotesquePillar = 1; T_pile_type_GruesomeSculpture = 2; } required T_pile_type pile_type = 1; } message T_flayed { required int32 structure = 1; } oneof abuse_data { T_piled piled = 1; T_flayed flayed = 2; } """ # FIXME CPP = """ /* failed to find a discriminator for union T_abuse_data */ """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype') # # conversion # def test_render_field_with_converted_type(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field name="name" type-name="language_name" export-as="string" ld:level="1" ld:meta="global"/> </ld:data-definition> """ PROTO = """ required string name = 1; """ CPP = """ convert_language_name_to_string(&dfhack->name, proto->mutable_name()); """ IMPORTS = [] DFPROTO_IMPORTS = ['conversion'] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype') # # non-regression tests from bug fixing # def test_bugfix_anon_container_of_anon_compounds(self): XML = """ <ld:data-definition xmlns:ld="ns"> <ld:field ld:level="1" ld:meta="static-array" count="2000" ld:is-container="true"> <ld:item ld:meta="compound" ld:level="1"> <ld:field ld:level="2" ld:meta="number" ld:subtype="int32_t" ld:bits="32"/> </ld:item> </ld:field> </ld:data-definition> """ PROTO = """ message T_anon_1 { required int32 anon_1 = 1; } repeated T_anon_1 anon_1 = 1; """ CPP = """ auto describe_T_anon_1 = [](dfproto::mytype_T_anon_1* proto, df::mytype::T_anon_1* dfhack) { proto->set_anon_1(dfhack->anon_1); }; for (size_t i=0; i<2000; i++) { describe_T_anon_1(proto->add_anon_1(), &dfhack->anon_1[i]); } """ IMPORTS = [] DFPROTO_IMPORTS = [] self.check_rendering(XML, PROTO, CPP, IMPORTS, DFPROTO_IMPORTS, 'mytype')
def render_cpp(self): rdr = CppRenderer(self.ns, self.proto_ns, 'DFProto') rdr.set_comment_ignored(self.comment_ignored).set_ignore_no_export( self.ignore_no_export) for tokens in self.exceptions_rename: rdr.add_exception_rename(tokens[1], tokens[2]) for tokens in self.exceptions_index: rdr.add_exception_index(tokens[1], tokens[2]) for tokens in self.exceptions_ignore: rdr.add_exception_ignore(tokens[1]) for tokens in self.exceptions_enum: rdr.add_exception_enum(tokens[1]) typout = rdr.render_type(self.xml) out = '/* THIS FILE WAS GENERATED. DO NOT EDIT. */\n' # this type may have hidden dependencies for k, v in self.exceptions_depends: if k == self.get_type_name(): out += '#include \"%s.h\"\n' % (v) out += '#include \"%s.h\"\n' % (self.get_type_name()) # protobuf and dfhack dependencies for imp in rdr.imports: out += '#include \"df/%s.h\"\n' % (imp) out += '#include \"%s.pb.h\"\n' % (imp) # conversion code for other types for imp in rdr.dfproto_imports: out += '#include \"%s.h\"\n' % (imp) out += '\n' + typout return out