代码之家  ›  专栏  ›  技术社区  ›  user1855952

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

  •  1
  • user1855952  · 技术社区  · 10 年前

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

      Command_A  list of arguments
      Command_B  list of arguments
      etc etc
    

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

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

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

    这里的问题有两个方面

    1) 我有非常多的if else(大约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 回复  |  直到 10 年前
        1
  •  0
  •   user2249683 user2249683    10 年前

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

    #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;
    }
    
        2
  •  0
  •   Mats Petersson    10 年前

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

    然后可以将其与基于表的系统相结合,其中函数[或对象]与字符串相关联。例如 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... 
        }
    }
    

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

        3
  •  0
  •   Mooing Duck    10 年前

    是的,将函数放在地图中。做到这一点的关键是 std::function<void()> 。不幸的是 void() 意味着它包含不接受参数且不返回任何内容的函数。显然,您的函数有参数。所以我们所做的是存储函数 std::stringstream& (行),解析出所需的参数,然后调用函数。最简单的方法就是使用内联lambdas。接受字符串流而不返回任何内容的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();