2014-03-24 72 views
1

所以我需要一些關於如何很好地解析C++中的文本文件的想法。那我解析這些文件的格式如下: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)我有一個非常非常多的,如果-別人的(約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(); 
+0

我想'STD :: getline'和'std:istringstream'將會大大減少你的代碼庫的聲音。 – WhozCraig

+0

這就是我正在做的。對於程序的每一行,我都會執行「getline」,然後使用stringstream從行中解析命令。然後,我通過我的if-else循環爲該命令找到正確的操作。 – user1855952

+0

你能告訴我們你的代碼嗎?它有助於說明問題。 :) – 0x499602D2

回答

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; 
} 
+0

這對我來說似乎是最好的解決方案。謝謝! – user1855952

0

我已經寫了很多類型的解析器,我發現它的經常寫一個非常通用的一個好主意函數獲取一行並生成一個字符串列表(例如std::vector<std::string>),然後將該列表中的第一個元素作爲「我們下一步做什麼」來處理,並讓每個命令按照它的喜好使用參數(例如,轉換爲浮動,用作文件名等)

這樣就可以與一個基於表的系統結合在一起,其中一個函數[或者對象]與字符串相關聯。例如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... 
    } 
} 

當然,也有很多不同的方法可以做到這一點,而這僅僅是一個可能的解決方案。

0

是的,把功能放在地圖上。這樣做的關鍵是std::function<void()>。不幸的是,void()意味着它保存不帶參數的函數,並且不返回任何內容。顯然,你的函數有參數。所以我們做的是存儲函數,每個函數都需要一個std::stringstream&(行),解析出他們需要的參數,然後調用函數。最簡單的方法就是使用內聯lambdas。採用字符串流並返回任何內容的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();