如何根据CFG验证输入?

时间:2016-11-05 09:52:07

标签: c++ algorithm parsing context-free-grammar compiler-theory

考虑这个语法:

expr ::= LP expr RP
         | expr PLUS|MINUS expr
         | expr STAR|SLASH expr
         | term

term ::= INTEGER|FLOAT

无上下文语法定义为G = ( V, Σ, R, S ),其中(在本例中):

V = { expr, term }
Σ = { LP, RP, PLUS, MINUS, STAR, SLASH, INTEGER, FLOAT }
R = //was defined above
S = expr

现在让我们定义一个名为Parser的小类,其定义是(代码示例以C ++提供):

class Parser
{
public:
    Parser();
    void Parse();
private:
    void parseRecursive(vector<string> rules, int ruleIndex, int startingTokenIndex, string prevRule);

    bool isTerm(string token);  //returns true if token is in upper case
    vector<string> split(...);  //input: string; output: vector of words splitted by delim

    map<string, vector<string>> ruleNames; //contains grammar definition
    vector<int> tokenList; //our input set of tokens
};

为了便于在规则之间切换,每个语法规则分为两部分:一个键(在::=之前)及其规则(在::=之后),所以对于我的语法来自上面发生以下地图:

std::map<string, vector<string>> ruleNames =
{
    { "expr", {
            "LP expr RP",
            "expr PLUS|MINUS expr",
            "expr STAR|SLASH expr",
            "term"
        }
    },
    { "term", { "INTEGER", "FLOAT" } }
};

出于测试目的,字符串(2 + 3) * 4已被标记化为以下集合

{ TK_LP, TK_INTEGER, TK_PLUS, TK_INTEGER, TK_RP, TK_STAR, TK_INTEGER }

并被用作Parser的输入数据。

现在最困难的部分:算法。根据我的理解,我在想这个:

1)从起始符号向量(LP expr RP)中取出第一条规则并将其拆分为单词。

2)检查规则中的第一个单词是否为终止。

  1. 如果单词是终端,请将其与第一个令牌进行比较。
    • 如果它们相等,请增加令牌索引并移至规则
    • 中的下一个单词
    • 如果它们不相等,请保留令牌索引并转到下一个规则
  2. 如果单词不是终端且未在之前的递归中使用,请增加令牌索引并进入递归解析(传递新规则和非终端词)
  3. 虽然我不确定这个算法,但我仍然试图制作和实现它(当然,不成功):

    1)启动递归的Out Parse函数:

    void Parser::Parse()
    {
        int startingTokenIndex = 0;
        string word = this->startingSymbol;
        for (int ruleIndex = 0; ruleIndex < this->ruleNames[word].size(); ruleIndex++)
        {
            this->parseRecursive(this->ruleNames[word], ruleIndex, startingTokenIndex, "");
        }
    }
    

    2)递归函数:

    void Parser::parseRecursive(vector<string> rules, unsigned ruleIndex, unsigned startingTokenIndex, string prevRule)
    {
        printf("%s - %s\n", rules[ruleIndex].c_str(), this->tokenNames[this->tokenList[startingTokenIndex]].c_str());
        vector<string> temp = this->split(rules[ruleIndex], ' ');
        vector<vector<string>> ruleWords;
        bool breakOutter = false;
    
        for (unsigned wordIndex = 0; wordIndex < temp.size(); wordIndex++)
        {
            ruleWords.push_back(this->split(temp[wordIndex], '|'));
        }
    
        for (unsigned wordIndex = 0; wordIndex < ruleWords.size(); wordIndex++)
        {
            breakOutter = false;
            for (unsigned subWordIndex = 0; subWordIndex < ruleWords[wordIndex].size(); subWordIndex++)
            {
                string word = ruleWords[wordIndex][subWordIndex];
                if (this->isTerm(word))
                {
                    if (this->tokenNames[this->tokenList[startingTokenIndex]] == this->makeToken(word))
                    {
                        printf("%s ", word.c_str());
                        startingTokenIndex++;
                    } else {
                        breakOutter = true;
                        break;
                    }
                } else {
                    if (prevRule != word)
                    {
                        startingTokenIndex++;
                        this->parseRecursive(this->ruleNames[word], 0, startingTokenIndex, word);
                        prevRule = word;
                    }
                }
            }
    
            if (breakOutter)
                break;
        }
    }
    

    我应该对我的算法进行哪些更改才能使其正常工作?

1 个答案:

答案 0 :(得分:0)

根据要实现一次性解析器或编译器的方式,使用不同的方法。对于编译器,编译器主要用于LR,用于LL的手动实现。 基本上,对于LL,手动实现使用递归下降(对于每个非终端,都会创建一个实现该函数的函数)。 例如,对于语法:

S -> S + A | A
A -> a | b

让我们杀死左递归和左因式分解(LL语法不适用于左递归):

S -> As
s -> + As | epsilon
A -> a | b

这样的实现是可能的:

void S (void)
{
    A ();
    s ();
}
void s (void)
{
    if (get_next_token (). value! = '+')
        return;
    A ();
    s ();
}
void A (void)
{
    token * tok = get_next_token ();
    if (tok.value! = 'a' && tok.value! = 'b')
            syntax_error ();
}

如果要添加SDD,则继承的属性将通过参数传递,而合成的属性将作为输出值。

评论: 不要一次收集所有令牌,请根据需要获取它们:get_next_token()。