views:

708

answers:

6

I wrote a file parser for a game I'm writing to make it easy for myself to change various aspects of the game (things like the character/stage/collision data). For example, I might have a character class like this:

class Character
{
public:
    int x, y; // Character's location
    Character* teammate;
}

I set up my parser to read in from a file the data structure with syntax similar to C++

Character Sidekick
{
    X = 12
    Y = 0
}

Character AwesomeDude
{
    X = 10
    Y = 50
    Teammate = Sidekick
}

This will create two data structures and put them in a map<std::string, Character*>, where the key string is whatever name I gave it (in this case Sidekick and AwesomeDude). When my parser sees a pointer to a class, like the teammate pointer, it's smart enough to look up in the map to fetch the pointer to that data structure. The problem is that I can't declare Sidekick's teammate to be AwesomeDude because it hasn't been placed into the Character map yet.

I'm trying to find the best way to solve this so that I can have my data structures reference objects that haven't yet been added to the map. The two easiest solutions that I can think of are (a) add the ability to forward declare data structures or (b) have the parser read through the file twice, once to populate the map with pointers to empty data structures and a second time to go through and fill them in.

The problem with (a) is that I also can decide which constructor to call on a class, and if I forward declare something I'd have to have the constructor be apart from the rest of the data, which could be confusing. The problem with (b) is that I might want to declare Sidekick and AwesomeDude in their own files. I'd have to make my parser be able to take a list of files to read rather than just one at a time (this isn't so bad I guess, although sometimes I might want to get a list of files to read from a file). (b) also has the drawback of not being able to use data structures declared later in the constructor itself, but I don't think that's a huge deal.

Which way sounds like a better approach? Is there a third option I haven't thought of? It seems like there ought to be some clever solution to this with pointer references or binding or something... :-/ I suppose this is somewhat subjective based on what features I want to give myself, but any input is welcome.

+13  A: 

When you encounter the reference the first time, simply store it as a reference. Then, you can put the character, or the reference, or whatever on a list of "references that need to be resolved later".

When the file is done, run through those that have references and resolve them.

Will Hartung
Typical two-phase parsing, akin to a compiler/linker system. Good, standard way to go.
Denis Troller
+1  A: 

Will said exactly what I was about to write. Just keep a list or something with the unsolved references.

And don't forget to throw an error if there are unsolved references once you finish reading the file =P

hoagie
+2  A: 

Personally, I'd go with b). Splitting your code into Parser and Validator classes, both operating on the same data structure. The Parser will read and parse a file, filling the data structure and storing any object references as their textual names, leaving the real pointer null in your structure for now.

When you are finished loading the files, use the Validator class to validate and resolve any references, filling in the "real" pointers. You will want to consider how to structure your data to make these lookups nice and fast.

Chris Thornhill
+5  A: 

Well, you asked for a third option. You don't have to use XML, but if you follow the following structure, it would be very simple to use a SAX parser to build your data structure.

At any rate, instead of referencing a teammate, each character references a team (Blue team in this case). This will decouple the circular reference issue. Just make sure you list the teams before the characters.

<team>Blue</team>

<character>
    <name>Sidekick</name>
    <X>12</X>
    <Y>0</Y>
    <teamref>Blue</teamref>
</character>

<character>
    <name>Sidekick</name>
    <X>10</X>
    <Y>50</Y>
    <teamref>Blue</teamref>
</character>
Mark Beckwith
This would certainly work for the example I've given, but the example was an oversimplification of what I really want to do, and there are some cases where decoupling two objects isn't really feasible. Or, at least, it would make it harder for the person making the file to be parsed.
Alex
A: 

Instead of storing Character object in your map, store a proxy for Character. The proxy will than contain a pointer to the actual Character object when the object is loaded. The type of Character::teammate will be changed to this proxy type. When you read in a reference that is not already in your map, you create a proxy and use the proxy. When you load an character which you already have an empty proxy in the map, populate it with your newly loaded character. You may also want to add a counter to keep track of how many empty proxy you have in the map so you know when all referenced characters have been loaded.

Another layer of indirection....it always make programming easier and slower.

Shing Yip
A: 

One option would be to reverse the obligation. The Map is responsible for filling in the reference

template<T> class SymbolMap // I never could rememeber C++ template syntax
{
   ...

   /// fill in target with thing name
   /// if no name yet, add it to the list of thing that will be name
   void Set(T& target, std::string name);

   /// define name as target
   /// go back and fill in anything that needs to be name
   void Define(T target, std::string name);

   /// make sure everything is resolved
   ~SymbolMap()
}

that won't interact well with value/moving semantics but I suspect that not much will.

BCS