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

C++ parsing complicated file where each line specifies a command

本文关键字:命令 一个 复杂 文件 C++      更新时间:2023-10-16

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

  Command_A  list of arguments
  Command_B  list of arguments
  etc etc

现在我正在使用ifstream打开文件,然后我有一系列超长的if-else语句来确定对每种类型的命令做什么。事实证明,这有点笨拙(尤其是因为有些命令是用于解析其他文件的……所以我为不同的文件嵌套了多个ifstream的if-else(。

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

以下基本上就是我目前正在做的事情。我循环浏览该文件并使用"getline"获取当前行。然后我使用字符串流来解析该命令。然后我使用一个很长的if-else列表来确定调用哪个函数。文件中的每一行都有一个参数列表,所以我使用字符串流来解析这些参数,然后将这些参数传递给我调用的函数。

这里的问题是双重

1( 我有一个非常非常大的数量如果elses(大约50(

2( 有些命令要求我解析新文件,因此我必须在当前ifstream中打开另一个ifstream。(参见命令_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();

您可以有一个命令映射并注册一组函数:

#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 Worldn"
        "add 1 2n");
    // 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;
}

我已经编写了许多类型的解析器,我发现编写一个相当通用的函数通常是一个好主意,该函数使用一行并生成一个字符串列表(例如std::vector<std::string>,然后将该列表中的第一个元素处理为"我们下一步要做什么",并让每个命令随心所欲地使用参数(例如,转换为float,用作文件名等(。

然后可以将其与基于表的系统相结合,在该系统中,函数[或对象]与字符串相关联。例如CCD_ 2。

然后你会得到这样的东西:

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... 
    }
}

当然,有很多不同的方法可以做到这一点,这只是一个可能的解决方案。

是的,将函数放在映射中。做到这一点的关键是std::function<void()>。不幸的是,void()意味着它持有的函数不接受任何参数,也不返回任何值。显然,您的函数有参数。因此,我们所做的是存储每个函数都有一个std::stringstream&(行(,解析出它们需要的参数,然后调用该函数。最简单的方法就是使用内联lambda。接受字符串流而不返回任何内容的Lambda看起来是这样的:[](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();