C ++解析复杂文件,其中每行指定一个命令

时间:2014-03-24 21:49:39

标签: c++ parsing map function-pointers ifstream

所以我需要一些关于如何在C ++中很好地解析文本文件的想法。我正在解析的文件具有以下格式:

  Command_A  list of arguments
  Command_B  list of arguments
  etc etc

现在我正在使用ifstream来打开文件然后我有这个超长的if-else语句系列来确定如何为每种类型的命令做什么。这被证明有点笨拙(特别是因为一些命令用于解析其他文件...所以我已经嵌套if-elses,其中有多个ifstream用于不同的文件)。

我一直在寻找另一种方法,但我不确定什么是最好的方法。我在考虑使用std :: map,其中键是命令字符串,值是函数指针,但我不熟悉在地图中存储函数指针(特别是如果不同的函数具有不同的返回类型等) 。

以下基本上是我目前正在做的事情。我遍历文件并使用" getline"获得当前行。然后我使用stringstream来解析命令。然后我使用很长的if-elses列表来确定要调用的函数。文件中的每一行还附带一个参数列表,因此我使用stringstream来解析这些参数,然后将这些参数传递给我调用的函数。

这里的问题是双重的

1)我有非常多的if-elses(大约50)

2)有些命令要求我解析新文件,因此我必须在当前ifstream中打开另一个ifstream。 (见command_c)

所以我正在寻找一种更简单/更有效/更漂亮的方式来做到这一点。

/*Open file and verify validity*/
std::ifstream parseFile(filename.c_str());
if(!parseFile.good())
{
    cerr<<"ERROR: File is either corrupt or does not exist."<<endl;
    exit(1); //Terminate program
}

//Loop over file line by line
std::string line;
while(!parseFile.eof())
{
    std::getline(parseFile, line);
    std::stringstream ss;
    std::string command;
    ss.str(line);
    ss >> command;

    if(command == "COMMAND_A")
    {
         float x,y,z;
         ss >> x >> y >> z;

         FunctionA(x,y,z);
    }
    else if(command == "COMMAND_B")
    {
        float a,b,c,d,e,f;
        ss >> a >> b >> c >> d >> e >> f;

        FunctionB(a,b,c,d,e,f);
    } 
    else if(command == "Command_C")
    {
        string nextFile;
        ss >> nextFile;

        ParseFile(nextFile); //This is not recursive...this is another function
    }
    else if(...)
    {
      ...
    }

   //  etc, etc (this continues on for a long time)
  }
parseFile.close();

3 个答案:

答案 0 :(得分:0)

你可以有一个命令图并注册一堆函数:

#include<fstream>
#include<functional>
#include<iostream>
#include<map>
#include<sstream>

int main() {

    typedef std::function<bool (std::istringstream&)> command_function;
    typedef std::map<std::string, command_function> command_map;

    command_map map;

    // register commands
    map.insert(command_map::value_type("print", [](std::istringstream& s) {
        std::string line;
        if( ! getline(s, line)) return false;
        std::cout << line << '\n';
        return true;
    }));

    map.insert(command_map::value_type("add", [](std::istringstream& s) {
        double a;
        double b;
        if( ! (s >> a >> b)) return false;
        std::cout << "a + b = " << a + b  << '\n';
        return true;
    }));

    // sample data
    std::istringstream file(
        "print Hello World\n"
        "add 1 2\n");

    // command parsing
    std::string line;
    while(getline(file, line)) {
        std::istringstream line_stream(line);
        std::string command;
        if(line_stream >> command >> std::ws) {
            auto pos = map.find(command);
            if(pos != map.end())
                pos->second(line_stream);
        }
    }
    return 0;
}

答案 1 :(得分:0)

我已经编写了很多类型的解析器,我发现编写一个相当通用的函数通常是一个好主意,该函数需要一行并生成一个字符串列表(例如std::vector<std::string> ,然后处理该列表中的第一个元素作为&#34;我们接下来要做什么&#34;,并让每个命令使用它喜欢的参数(例如,翻译为浮点数,用作文件名等)。 / p>

然后可以将其与基于表的系统组合,其中函数[或对象]与字符串相关联。例如std::map<std::string, BaseCommand> table;

然后你最终得到这样的东西:

class CommandA : BaseCommand
{
public:
    virtual int Run(const std::vector<std::string>& argv);
};

table["CommandA"] = new CommandA;
table["CommandB"] = new CommandB;
... 

std::vector<std::string> argv = parseLine(line); 
if (table.find(argv[0]) != table.end())
{
    int result = table[argv[0]].second->Run(argv);
    if (result < 0)
    {
        ... do error handling here... 
    }
}

当然,您可以通过许多不同的方式来做到这一点,这只是一种可能的解决方案。

答案 2 :(得分:0)

是的,将功能放在地图中。这样做的关键是std::function<void()>。不幸的是,void()意味着它包含不带参数的函数,并且不返回任何内容。显然,你的函数有参数。所以我们所做的是存储函数,每个函数都采用std::stringstream&(行),解析出他们需要的参数,然后调用函数。最简单的方法是使用内联lambda。采用字符串流并且不返回任何内容的Lambdas看起来像这样:[](std::stringstream& ss) {code}

此外,我使用此功能可以轻松检索您的参数:

template<class T>
T get(std::stringstream& ss) 
{
    T t; 
    ss<<t; 
    if (!ss) // if it failed to read
        throw std::runtime_error("could not parse parameter");
    return t;
}

这是地图:

std::unordered_set<std::string, std::function<void(std::stringstream&))> cmd_map= 
    "COMMAND_A", [](std::stringstream& ss)
        {FunctionA(get<float>(ss), get<float>(ss), get<float>(ss));},
    "COMMAND_B", [](std::stringstream& ss)
        {FunctionB(get<float>(ss), get<float>(ss), get<float>(ss), get<float>(ss), get<float>(ss), get<float>(ss));},
    "COMMAND_C", [](std::stringstream& ss)
        {FunctionA(get<string>(ss));},

这是解析器:

//Loop over file line by line
std::string line;
while(std::getline(parseFile, line)) //use this instead of eof
{
    std::stringstream ss(line);
    std::string command;
    ss >> command;

    auto it = cmd_map.find(command);
    if (it != cmd_map.end())
    {
        try 
        {
            (*it)(); //call the function
        } catch(std::runtime_error& err) {
            std::cout << "ERROR: " << err.what() << '\n';
        }
    } else {
        std::cout << "command " << command << " not found";
    }
}
parseFile.close();