【问题标题】:Evaluating expressions inside C++ strings: "Hi ${user} from ${host}"评估 C++ 字符串中的表达式:“Hi ${user} from ${host}”
【发布时间】:2010-09-20 18:30:37
【问题描述】:

我正在寻找一种干净的 C++ 方法来解析包含在 ${} 中包装的表达式的字符串,并从以编程方式计算的表达式构建结果字符串。

示例:如果我实施程序让“user”评估为“foo”等,“Hi ${user} from ${host}”将被评估为“Hi foo from bar”等。

我正在考虑的当前方法包括一个状态机,它一次从字符串中读取一个字符,并在到达“}”后评估表达式。有什么提示或其他建议吗?

注意:boost:: 是最受欢迎的! :-)

更新感谢前三个建议!不幸的是,我的例子太简单了!我需要能够检查 ${} 中的内容,所以这不是简单的搜索和替换。也许它会说 ${uppercase:foo} 然后我必须使用“foo”作为哈希映射中的键,然后将其转换为大写,但是在编写上面的原始问题时,我试图避免 ${} 的内部细节... :-)

【问题讨论】:

    标签: c++ parsing boost string expression


    【解决方案1】:
    #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 &lt;= m_length 而不是m_char &lt; m_length?如果字符串以文字结尾,则“lexme”将以空字节结尾。
    • 此代码不在 cpp.sh 上运行。需要进行以下更改:评论#include &lt;conio.h&gt;_getch();,将Token::E Token() const 更改为Token::E Token_() constexp.Token() 更改为exp.Token_()
    【解决方案2】:

    打算有多少个评估表达式?如果它足够小,您可能只想使用蛮力。

    例如,如果您有一个从您的key 到它的valuestd::map&lt;string, string&gt;,例如userMatt Cruikshank,您可能只想遍历整个地图并做一个简单的将每个"${" + key + "}" 的字符串替换为其value

    【讨论】:

      【解决方案3】:

      Boost::Regex 将是我建议的路线。 regex_replace 算法应该可以完成大部分繁重的工作。

      【讨论】:

        【解决方案4】:

        如果您不喜欢我的第一个答案,请深入了解 Boost Regex - 可能是 boost::regex_replace

        【讨论】:

          【解决方案5】:

          表达式可以有多复杂?它们只是标识符,还是可以是“${numBad/(double)total*100.0}%”之类的实际表达式?

          【讨论】:

            【解决方案6】:

            您必须使用 ${ 和 } 分隔符还是可以使用其他分隔符?

            你并不真正关心解析。您只想生成并格式化其中包含占位符数据的字符串。对吧?

            对于平台中立的方法,请考虑不起眼的 sprintf 函数。它是最普遍的,并且可以满足我假设您的需要。它适用于“char stars”,因此您将不得不进行一些内存管理。

            您使用的是 STL 吗?然后考虑 basic_string& replace 函数。它并不能完全满足您的需求,但您可以让它发挥作用。

            如果您使用 ATL/MFC,请考虑 CStringT::Format 方法。

            【讨论】:

            • 抱歉,我需要那种格式。关于 sprintf,我无法控制格式字符串,并且使用带有非文字格式字符串的 printf() 样式调用可能会很危险。
            【解决方案7】:

            如果您要单独管理变量,为什么不采用嵌入式解释器。我过去使用过tcl,但您可以尝试为嵌入而设计的luaRubyPython 是另外两个易于嵌入但不那么轻量级的可嵌入解释器。策略是实例化解释器(上下文),向其中添加变量,然后在该上下文中评估字符串。解释器将正确处理可能导致您的应用程序出现安全或稳定性问题的格式错误的输入。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2016-04-24
              • 2012-05-29
              • 2020-06-26
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多