Beispiel #1
0
 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'
Beispiel #4
0
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)
Beispiel #5
0
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