Crockfords 自上而下的运算符优先级

Crockfords Top Down Operator Precedence

提问人:Moritz Roessler 提问时间:9/16/2013 最后编辑:Moritz Roessler 更新时间:9/23/2013 访问量:1435

问:

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

因此,我开始阅读道格拉斯·克罗克福德(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"
    }
}

DC 之所以使用这个功能,是因为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) 与调用 expression (9) 没有区别。(!(0<0) && !(9<0) & 0<10 & & 9<10))

token.id === “=” 条件阻止分配给对象成员,因为 token.id 要么是 '[' 要么是 '.',并且不会调用 t.led

简而言之,我的问题是:

为什么我们不调用变量声明后可选的赋值运算符的可用 led 函数。但是手动设置语句的第一个第二个成员,而不是赋值成员?

这是两把小提琴解析一个简单的字符串。使用原始代码,一个使用赋值运算符。led

JavaScript 解析

评论

3赞 glenatron 9/18/2013
您可能还会在计算机科学 StackExchange 网站上得到对此的有趣回应: cs.stackexchange.com
0赞 Moritz Roessler 9/19/2013
@glenatron 嘿,谢谢你的建议,有趣,我什至不知道这个网站。我可以将问题的克隆添加到吗?或者这会被认为是粗鲁的吗?cs
0赞 glenatron 9/19/2013
我肯定会这样做 - 不同网站上的答案类型相当不同,我怀疑你会在那里得到一些有趣的角度。
1赞 D.W. 9/20/2013
@glenatron,供将来参考:请不要建议人们在其他 StackExchange 网站上交叉发布他们的问题(尤其是不要只是在这里发布一两天后)。通常,我们鼓励人们标记迁移问题,或者等待一段时间(一周?)在原始网站上获得答案。让同一个问题的多个副本漂浮在周围对任何人都没有好处。
1赞 D.W. 9/22/2013
@glenatron,“避免交叉发布,尤其是在一个站点上发布后不久”是 StackExchange 的一项政策。我不是它的来源;我只是在报道它。也就是说,该政策充分的理由。这不是讨论政策的地方(这将是 Meta.SO),但您应该了解政策的原因,并且您应该在它存在时遵守它。参考资料: meta.stackexchange.com/q/64068/160917meta.cs.stackexchange.com/a/673/755.

答:

1赞 Strix 9/19/2013 #1

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

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

我对此很确定,但以下只是一个猜测:

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

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

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


我不知道为什么该成员未设置为 。这取决于您要对生成的树执行哪些操作。同样,语句中的赋值有点特殊,设置它可能不可行。assignmenttruevar

评论

0赞 Moritz Roessler 9/19/2013
嘿 =) 谢谢你的回答。 不幸的是,var 语句不允许任何其他赋值,因为条件将是 false as not =。2.. 这就是我所做的. 赋值函数 = 运算符函数,并期望当前令牌是 = 之后的标记(advance(“=”) before t.led 否则就没有意义了),左边的值作为参数作为标记传递,右边的值由调用中的表达式赋值。1.token.id === "="token.id === '[' || token.id === '.'if (token.id === "=") {... t.led(n) ... }3.ledleftassignment
0赞 Moritz Roessler 9/19/2013
4. var t=1与将工作分配成员添加到工作分配的位置基本相同。var t; t=1
0赞 Strix 9/19/2013
你把我带到了那里。我在想也许我们需要摆脱,但这已经足够了.我看不出有任何反对你升级的论据,这似乎是一个完美的选择。只是原始代码对我来说似乎更具可读性:-)t.assignment=true;delete t.assignment;
1赞 Palec 9/20/2013 #2

赋值(例如)与初始化(例如)在概念上不同,尽管两者都会导致内存状态更改。使用同一段代码来实现两者是不可取的,因为在语言的未来版本中,一个代码可能会独立于另一个进行更改。var t; t = 1;var t = 1;

在谈论赋值运算符重载和复制构造函数时,可以在 C++ 上显示概念上的差异。初始化可以调用复制构造函数,赋值可以调用赋值运算符重载。赋值从不触发复制构造函数,初始化从不使用赋值运算符重载。请参阅有关复制构造函数和赋值运算符重载的教程

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

人们可以认为两者都使用等号是巧合。Pascal 用于赋值和初始化。JavaScript 也可以使用类似 .:==var t : 1;

8赞 Benjamin Gruenbaum 9/23/2013 #3

在解析一门语言时,有两件事很重要——语义和语法。

从语义上讲,如果不是完全相同,则看起来非常接近(因为在这两种情况下,首先声明一个变量,然后为该声明的变量赋值。这是你所观察到的,并且在大多数情况下是正确的。var x=5;var x;x=5

然而,在句法上,两者是不同的(这是清晰可见的)。

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

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

现在简明扼要!让我们看一下这两个例子。

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

第一个:按以下方式阅读var x=5

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

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