Я ищу чистый способ C++ для синтаксического анализа строки, содержащей выражения, заключенные в $ {}, и построения строки результата из программно вычисленных выражений.
Пример: «Привет, $ {user} от $ {host}» будет оцениваться как «Привет, foo from bar», если я реализую программу, позволяющую «пользователю» оценивать значение «foo» и т. д.
Текущий подход, о котором я думаю, состоит из конечного автомата, который съедает по одному символу из строки и оценивает выражение после достижения '}'. Есть какие-нибудь подсказки или другие предложения?
Примечание: boost :: приветствуется! :-)
Обновлять Спасибо за первые три предложения! К сожалению, я сделал пример слишком простым! Мне нужно иметь возможность исследовать содержимое в $ {}, чтобы это не простой поиск и замена. Может быть, он скажет $ {uppercase: foo}, а затем мне придется использовать "foo" в качестве ключа в хэш-карте, а затем преобразовать его в верхний регистр, но я попытался избежать внутренних деталей $ {} при написании исходного вопроса выше ... :-)





Сколько выражений оценки нужно иметь? Если он достаточно мал, вы можете просто использовать грубую силу.
Например, если у вас есть std::map<string, string>, который идет от вашего key к его value, например, от user к Matt Cruikshank, вы можете просто перебрать всю карту и выполнить простую замену в своей строке каждого "${" + key + "}" на его value.
Я бы предложил маршрут Boost :: Regex. Алгоритм regex_replace должен сделать большую часть вашей тяжелой работы.
Если вам не нравится мой первый ответ, попробуйте Boost Regex - возможно, boost :: regex_replace.
Насколько сложными могут быть выражения? Являются ли они просто идентификаторами или могут быть фактическими выражениями типа «$ {numBad / (double) total * 100.0}%»?
Обязательно ли использовать разделители $ {и} или можно использовать другие разделители?
Вы действительно не заботитесь о разборе. Вы просто хотите сгенерировать и отформатировать строки с данными-заполнителями в них. Верно?
Для нейтрального подхода к платформе рассмотрим скромную функцию спринт. Он наиболее распространен и делает то, что, как я полагаю, вам нужно. Он работает с символами «звездочки», так что вам придется немного заняться управлением памятью.
Вы используете STL? Затем рассмотрим функцию basic_string и заменить. Это не совсем то, что вы хотите, но вы можете заставить его работать.
Если вы используете ATL / MFC, рассмотрите метод CStringT :: Формат.
#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();
}
С удовольствием объясню что-нибудь по поводу этого кода :)
Почему m_char <= m_length, а не m_char < m_length? Если строка заканчивается литералом, "lexme" завершается нулевым байтом.
Этот код не работает на cpp.sh. Требуются следующие изменения: прокомментируйте #include <conio.h> и _getch();, замените Token::E Token() const на Token::E Token_() const и exp.Token() на exp.Token_().
Если вы управляете переменными отдельно, почему бы не пойти по пути встраиваемого интерпретатора. Раньше я использовал tcl, но вы можете попробовать lua, который предназначен для встраивания. Рубин и Python - два других встраиваемых интерпретатора, которые легко встраивать, но они не такие легкие. Стратегия состоит в том, чтобы создать экземпляр интерпретатора (контекста), добавить к нему переменные, а затем оценить строки в этом контексте. Интерпретатор будет правильно обрабатывать искаженный ввод, который может привести к проблемам с безопасностью или стабильностью вашего приложения.
Извините, мне нужен этот формат. Что касается sprintf, я бы не контролировал строки формата, и использование вызова в стиле printf () со строкой формата, не являющейся литералом, было бы потенциально опасным.