Evaluating expressions inside C ++ strings: "Hello $ {user} from $ {host}"

I am looking for a clean C ++ way to parse a string containing expressions enclosed in $ {} and build the resulting string from programmatically evaluated expressions.

Example: "Hello $ {user} from $ {host}" will be evaluated as "Hello foo from bar" if I implement a program so that "user" evaluates to "foo", etc.

The current approach that I think of consists of a state machine that eats one character at a time from a string and evaluates the expression after reaching '}'. Any tips or other suggestions?

Note: boost :: is welcome !:-)

Update Thanks for the first three sentences! Unfortunately, I made this example too simple! I need to examine the contents within $ {} so that it is not a simple search and replace. He might say $ {uppercase: foo}, and then I should use "foo" as the key in the hash map and then convert it to uppercase, but I tried to avoid the internal details of $ {} when writing the original question above. .. :-)

+4
source share
7 answers
#include <iostream> #include <conio.h> #include <string> #include <map> using namespace std; struct Token { enum E { Replace, Literal, Eos }; }; class ParseExp { private: enum State { State_Begin, State_Literal, State_StartRep, State_RepWord, State_EndRep }; string m_str; int m_char; unsigned int m_length; string m_lexme; Token::E m_token; State m_state; public: void Parse(const string& str) { m_char = 0; m_str = str; m_length = str.size(); } Token::E NextToken() { if (m_char >= m_length) m_token = Token::Eos; m_lexme = ""; m_state = State_Begin; bool stop = false; while (m_char <= m_length && !stop) { char ch = m_str[m_char++]; switch (m_state) { case State_Begin: if (ch == '$') { m_state = State_StartRep; m_token = Token::Replace; continue; } else { m_state = State_Literal; m_token = Token::Literal; } break; case State_StartRep: if (ch == '{') { m_state = State_RepWord; continue; } else continue; break; case State_RepWord: if (ch == '}') { stop = true; continue; } break; case State_Literal: if (ch == '$') { stop = true; m_char--; continue; } } m_lexme += ch; } return m_token; } const string& Lexme() const { return m_lexme; } Token::E Token() const { return m_token; } }; string DoReplace(const string& str, const map<string, string>& dict) { ParseExp exp; exp.Parse(str); string ret = ""; while (exp.NextToken() != Token::Eos) { if (exp.Token() == Token::Literal) ret += exp.Lexme(); else { map<string, string>::const_iterator iter = dict.find(exp.Lexme()); if (iter != dict.end()) ret += (*iter).second; else ret += "undefined(" + exp.Lexme() + ")"; } } return ret; } int main() { map<string, string> words; words["hello"] = "hey"; words["test"] = "bla"; cout << DoReplace("${hello} world ${test} ${undef}", words); _getch(); } 

I will be happy to talk about this code :)

+5
source

How many evaluative expressions does it intend to have? If it is small enough, you can just use brute force.

For example, if you have std::map<string, string> that goes from your key to value , for example user to Matt Cruikshank , you can simply iterate over your entire map and make it simple replace the string "${" + key + "}" to the string value .

0
source

Boost :: Regex will be the route I suggest. The regex_replace algorithm should do most of your heavy lifting.

0
source

If you don't like my first answer, go to Boost Regex - maybe boost :: regex_replace .

0
source

How complicated are the expressions? Are they just identifiers or can they be actual expressions like "$ {numBad / (double) total * 100.0}%"?

0
source

Do you need to use $ {and} delimiters or can you use other delimiters?

You don't care about parsing. You just want to generate and format rows with data placeholders. Right?

For a neutral approach to the platform, consider the humble sprintf function. This is the most ubiquitous and does what I assume you need. It works with "char stars", so you will need to master memory management.

Do you use STL? Then consider basic_string & replace . It does not do what you want, but you can make it work.

If you are using ATL / MFC, consider the CStringT :: Format method.

0
source

If you manage the variables separately, why not follow the path of the embedded interpreter. I have used tcl in the past, but you can try lua , which is for embedding. Ruby and Python are two other embedded interpreters that are easy to embed, but not so light. The strategy is to create an instance of the interpreter (context), add variables to it, and then evaluate the lines in this context. The interpreter will correctly handle incorrect data, which can lead to security or stability issues for your application.

0
source

All Articles