如何验证基于CFG的输入

How to validate input based on CFG?

本文关键字:CFG 输入 何验证 验证      更新时间:2023-10-16

考虑以下语法:

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
};

为了更容易在规则之间切换,每个语法规则被分成2部分:一个键(在::=之前)和它的规则(在::=之后),所以对于我上面的语法,下面的映射发生了:

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. 如果该词不是终端,并且在之前的递归中没有使用,则增加令牌索引并进行递归解析(传递新规则和非终端词)

虽然我不确定在这个算法,我仍然试图使和实现它(当然,不成功):

1)启动递归的外部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 - %sn", 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;
    }
}

我应该对我的算法进行哪些更改以使其工作?

根据您想要实现的一次性解析器或编译器的不同,使用不同的方法。对于编译器,编译器主要使用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()。