Crockfords自上而下的运营商优先权

时间:2013-09-16 12:39:22

标签: javascript parsing

出于兴趣,我想学习如何为一种简单的语言编写解析器,最终为我自己的小代码高尔夫语言编写一个解释器,一旦我理解了这些东西的工作原理。

所以我开始阅读Douglas Crockfords的文章Top Down Operator Precedence

注意:如果您想深入了解下面代码段的上下文,您应该阅读本文

我无法理解var语句和赋值运算符=应如何协同工作。

D.C。定义一个赋值运算符,如

var assignment = function (id) {
    return infixr(id, 10, function (left) {
        if (left.id !== "." && left.id !== "[" &&
                left.arity !== "name") {
            left.error("Bad lvalue.");
        }
        this.first = left;
        this.second = expression(9);
        this.assignment = true;
        this.arity = "binary";
        return this;
    });
};
assignment("=");  

注意:[[value]]是指一个简化为其值的标记

现在,如果表达式函数达到例如[[t]],[[=]],[[2]][[=]].led的结果是这样的。

{
    "arity": "binary",
    "value": "=",
    "assignment": true, //<-
    "first": {
        "arity": "name",
        "value": "t"
    },
    "second": {
        "arity": "literal",
        "value": "2"
    }
}

D.C。生成assignment函数,因为

  

我们希望它做两个额外的业务:检查左操作数以确保它是一个合适的左值,并设置一个赋值成员,以便我们可以以后快速识别赋值语句。

这对我来说是有意义的,直到他介绍了这一点 var语句,定义如下。

  

var语句定义当前块中的一个或多个变量。每个名称可以选择后跟=和初始化表达式。

stmt("var", function () {
    var a = [], n, t;
    while (true) {
        n = token;
        if (n.arity !== "name") {
            n.error("Expected a new variable name.");
        }
        scope.define(n);
        advance();
        if (token.id === "=") {
            t = token;
            advance("=");
            t.first = n;
            t.second = expression(0);
            t.arity = "binary";
            a.push(t);
        }
        if (token.id !== ",") {
            break;
        }
        advance(",");
    }
    advance(";");
    return a.length === 0 ? null : a.length === 1 ? a[0] : a;
});

现在,如果解析器到达一组令牌[[var]],[[t]],[[=]],[[1]],则生成的树看起来就像。

{
    "arity": "binary",
    "value": "=",
    "first": {
        "arity": "name",
        "value": "t"
    },
    "second": {
        "arity": "literal",
        "value": "1"
    }
}

我的问题的关键部分是if (token.id === "=") {...}部分。

我不明白为什么我们打电话

    t = token;
    advance("=");
    t.first = n;
    t.second = expression(0);
    t.arity = "binary";
    a.push(t);

而不是

    t = token;
    advance("=");
    t.led (n);
    a.push(t);  

...部分。

会调用我们的[[=]]运算符led函数(赋值函数),其中

  

确保它是一个合适的左值,并设置一个分配成员,以便我们以后快速识别赋值语句。   例如

{
    "arity": "binary",
    "value": "=",
    "assignment": true,
    "first": {
        "arity": "name",
        "value": "t"
    },
    "second": {
        "arity": "literal",
        "value": "1"
    }
}

由于没有lbp的运算符介于0和10之间,因此调用expression(0) vs. expression (9)没有任何区别。 (!(0<0) && !(9<0) && 0<10 && 9<10)

token.id === "="条件阻止了对象成员的分配,因为token.id'[''.'不会是t.led叫。

简而言之,我的问题是:

为什么我们不会在变量声明后面调用赋值运算符的可用led函数。但是,请手动设置语句的firstsecond成员,而不是assignment成员?

这是解析一个简单字符串的两个小提琴。使用original代码,使用assignment运算符led

3 个答案:

答案 0 :(得分:8)

在解析语言时,有两件事情很重要 - 语义和语法。

语义var x=5;var x;x=5似乎非常接近(如果不相同)(因为在这两种情况下,首先声明一个变量,然后将一个值赋给该声明的变量。这是你观察到的,并且在大多数情况下是正确的。

语法但是,两者不同(清晰可见)。

在自然语言中,类似的是:

  • 男孩有一个苹果。
  • 有一个苹果,男孩有它。

现在简明扼要!我们来看两个例子。

虽然这两个(几乎)意思相同,但它们显然不是同一个句子。回到JavaScript!

第一个:var x=5read the following way

var                      x              =                  5
-----------------------VariableStatement--------------------
var -------------------        VariableDeclarationList 
var -------------------        VariableDeclaration
var            Identifier -------   Initialiser(opt)
var ------------------- x              = AssignmentExpression
var ------------------- x ------------ = LogicalORExpression
var ------------------- x ------------ = LogicalANDExpression
var ------------------- x ------------ = BitwiseORExpression
var ------------------- x ------------ = BitwiseXORExpression
var ------------------- x ------------ = BitwiseANDExpression 
var ------------------- x ------------ = EqualityExpression
var ------------------- x ------------ = ShiftExpression
var ------------------- x ------------ = AdditiveExpression
var ------------------- x ------------ = MultiplicativeExpression
var ------------------- x ------------ = UnaryExpression
var ------------------- x ------------ = PostfixExpression 
var ------------------- x ------------ = NewExpression
var ------------------- x ------------ = MemberExpression
var ------------------- x ------------ = PrimaryExpression
var ------------------- x ------------ = Literal
var ------------------- x ------------ = NumericLiteral
var ------------------- x ------------ = DecimalLiteral
var ------------------- x ------------ = DecimalDigit 
var ------------------- x ------------ = 5

唷!所有这一切都必须在语法上解析var x = 5,当然,很多都是处理表达式 - 但它就是这样,让我们​​检查另一个版本。

这分为两个陈述。 var x; x = 5第一个是:

var                      x 
--------VariableStatement---
var ---- VariableDeclarationList 
var ---- VariableDeclaration
var                 Idenfifier (optional initializer not present)
var                      x

第二部分是x=5,这是一个赋值语句。我可以继续使用相同的表达式疯狂 - 但它几乎是一样的。

总而言之,虽然两者在语义上产生相同的结果,但在语法上与官方语言语法指定的一样 - 它们是不同的。在这种情况下,结果确实是相同的。

答案 1 :(得分:1)

我没有时间阅读整篇文章,所以我不是百分百肯定的。在我看来,原因是因为var语句中的赋值运算符有点特殊。它不接受所有可能的左值 - 不允许对象的成员(没有.[运算符)。只允许使用普通变量名。

所以我们不能使用普通的assignment函数,因为它允许所有左值。

我对此非常肯定,但以下只是一个猜测:

我们必须选择性地调用assignment函数,并且只有在我们检查到我们使用赋值运算符之后才会调用它。

  advance();
  if (token.id === "=") {
      // OK, Now we know that there is an assignment.

但是函数assignment假设当前令牌是左值,而不是运算符=


我不知道为什么assignment成员未设置为true。这取决于您想要对生成的树做什么。同样,var语句中的赋值有点特殊,设置它可能是不可行的。

答案 2 :(得分:1)

Assignment(例如var t; t = 1;)在概念上与initialization(例如var t = 1;)不同,但两者都会导致内存状态发生变化。使用相同的代码来实现两者是不可取的,因为在未来的语言版本中,人们可以独立地改变。

在讨论赋值运算符重载和复制构造函数时,可以在C ++上显示概念差异。初始化可以调用复制构造函数,赋值可以调用赋值运算符重载。赋值永远不会触发复制构造函数,初始化永远不会使用赋值运算符重载。请参阅tutorial on copy constructor and assignment operator overloading

另一个例子是Strix的一个例子:到目前为止,并非所有的l值都可以在JavaScript中的var之后使用。我认为这是他们在JavaScript中的最大区别,如果不是唯一的。当然,忽略var中明显的范围变化。

可以想到将两者的等号用作巧合。 Pascal使用:=进行分配,使用=进行初始化。 JavaScript也可以使用var t : 1;

之类的东西