4

${} でラップされた式を含む文字列を解析し、プログラムで評価された式から結果文字列を作成するためのクリーンな C++ の方法を探しています。

例: "user" を "foo" に評価させるプログラムを実装すると、"Hi ${user} from ${host}" は "Hi foo from bar" に評価されます。

私が考えている現在のアプローチは、文字列から一度に 1 文字を食べ、「}」に到達した後に式を評価するステート マシンで構成されています。ヒントやその他の提案はありますか?

注: boost:: は大歓迎です! :-)

更新最初の 3 つの提案に感謝します。残念ながら、例を単純化しすぎました。${} 内の内容を調べる必要があるため、単純な検索と置換ではありません。おそらく、${uppercase:foo} と表示され、ハッシュマップのキーとして「foo」を使用して大文字に変換する必要がありますが、上記の元の質問を書くときに ${} の内部の詳細を回避しようとしました... :-)

4

7 に答える 7

5
#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();
}

このコードについて喜んで説明させていただきます:)

于 2008-11-04T21:01:08.507 に答える
0

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

于 2008-11-04T20:56:34.010 に答える
0

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

You don't really care about parsing. You just want to generate and format strings with placeholder data in it. Right?

For a platform neutral approach, consider the humble sprintf function. It is the most ubiquitous and does what I am assuming that you need. It works on "char stars" so you are going to have to get into some memory management.

Are you using STL? Then consider the basic_string& replace function. It doesn't do exactly what you want but you could make it work.

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

于 2008-11-04T20:56:45.990 に答える
0

いくつの評価式を持つつもりですか? 十分に小さい場合は、ブルートフォースを使用したいだけかもしれません.

たとえば、std::map<string, string>が yourkeyから itsvalueへ、たとえばusertoの場合、マップ全体を反復処理して、すべての文字列を itsMatt Cruikshankに単純に置換したい場合があります。"${" + key + "}"value

于 2008-11-04T20:50:28.030 に答える
0

Boost::Regexは、私が提案するルートです。regex_replaceアルゴリズムは、面倒な作業のほとんどを行う必要があります。

于 2008-11-04T20:50:35.050 に答える
0

私の最初の答えが気に入らない場合は、Boost Regex (おそらくboost::regex_replace ) を掘り下げてください。

于 2008-11-04T20:52:27.027 に答える
0

変数を個別に管理している場合は、組み込み可能なインタープリターのルートに進んでみませんか。以前はtclを使っていましたが、埋め込み用に設計された luaを試してみるとよいでしょう。RubyPythonは、組み込みが簡単な 2 つの組み込み可能なインタープリターですが、それほど軽量ではありません。戦略は、インタプリタ (コンテキスト) をインスタンス化し、それに変数を追加してから、そのコンテキスト内で文字列を評価することです。インタープリターは、アプリケーションのセキュリティや安定性の問題につながる可能性のある不正な入力を適切に処理します。

于 2008-11-04T21:18:53.713 に答える