How would you go about reading a simple file format using C or C++?
For example, take the Wavefront .obj file format, sample:
# this is a box
o 1
v -0.5 -0.5 0.5
v -0.5 -0.5 -0.5
v -0.5 0.5 -0.5
v -0.5 0.5 0.5
v 0.5 -0.5 0.5
v 0.5 -0.5 -0.5
v 0.5 0.5 -0.5
v 0.5 0.5 0.5
usemtl Default
f 4 3 2 1
f 2 6 5 1
f 3 7 6 2
f 8 7 3 4
f 5 8 4 1
f 6 7 8 5
Since the files can be quite large, I made an intermediate class (FileBuffer) with an operator[]. It only every has 4096 bytes of the file in memory, and reads new parts whenever needed. The file format is really simple, but I don't prefer to use something like flex/bison for this. It would only complicate matters.
What would be a proper way to go about interpreting this (kind of) file? Currently I have a whole lot of nested for/while loops and many counters keeping track. Also many switch/elseif statements. How would I make this code maintainable and more structured ov开发者_如何学Goerall?
Thanks.
If it were me, I'd leverage as much of the standard library as I could:
struct Command { /* Abstract Base Class */ ... };
struct VCommand : Command { std::vector<double> dims; ... }
struct FCommand : Command { std::vector<int> vertexes; ... }
struct FootCommand : Command { enum {LEFT, RIGHT, IN, OUT} e; ... }
std::vector<Command*> commandList; // DANGER: raw pointers
void ParseInput(std::istream& in) {
// untested code:
std::string line;
while(getline(in, line)) {
std::stringstream lineStream(line);
std::string command;
lineStream >> command;
if(command == "v") {
std::istream_iterator<double>(lineStream) begin;
std::istream_iterator<double> end;
// Add the "v" command to the parse tree
commandList.push_back(new VCommand(begin, end));
} else if (command == "f") {
std::istream_iterator<int>(lineStream) begin;
std::istream_iterator<int> end;
// Add the "v" command to the parse tree
commandList.push_back(new FCommand(begin, end));
} else if (command == "quit") {
...
} else if (command == "put_your_left_foot_in") {
...
commandList.push_back(new FootCommand(LEFT, IN));
}
}
}
If I understand the format correctly, each line defines
a specific type of data, with the type determined by the first
word. I'd start by defining an abstract base class and
a concrete instance of the class for each type of line; I'd
register these instances in a std::map<std::string,
LineParser*>
.
For reading the file, I'd probably install a filtering streambuf to get rid of the comments and the empty lines upstream. Then a simple loop would do the trick:
std::string line;
while ( std::getline( filteredInput, line ) ) {
std::string keyword;
std::istringstream toParse( line );
toParse >> keyword;
std::map<std::string, LineParser*>::const_iterator
parser = registry.find( keyword );
if ( parser == registry.end() ) {
// Syntax error: unknown keyword...
} else {
parser->parse( toParse );
}
}
I would start with defining (or obtaining) the file grammar/structure.
Then build a parser for the input stream based on that grammar.
精彩评论