我如何扩展JavaScript语言以支持新的运算符? [英] How would I extend the JavaScript language to support a new operator?

查看:161
本文介绍了我如何扩展JavaScript语言以支持新的运算符?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题的答案 是否可以在JavaScript中创建自定义运算符? 尚未 ,但 @Benjamin建议可能使用第三方工具添加新运算符

The answer to the question Is it possible to create custom operators in JavaScript? is not yet, but @Benjamin suggested that it would be possible to add a new operator using third party tools:


可以使用像sweet这样的第三方工具。 js添加自定义运算符虽然需要额外的编译步骤。

It is possible to use third party tools like sweet.js to add custom operators though that'd require an extra compilation step.

我将采用相同的例子,就像上一个问题一样:

I will take the same example, like in the previous question:

对于任何两个实数 x y x∘y x + 2y ,这也是一个真实的数。如何在我的扩展JavaScript语言中添加此运算符?

For any two real numbers x and y: x ∘ y is x + 2y that is also a real number. How can I add this operator in my extended JavaScript language?

运行以下代码后:

var x = 2
  , y = 3
  , z = x ∘ y;

console.log(z);

输出将包含

8

(因为 8 2 + 2 * 3

如何我会扩展JavaScript语言以支持新的运算符吗?

How would I extend the JavaScript language to support a new operator?

推荐答案

是的,这是可能的,甚至不是很难:)

Yes, it's possible and not even very hard :)

我们需要讨论以下几点:

We'll need to discuss a few things:


  1. 什么是语法和语义。

  2. 如何解析编程语言?什么是语法树?

  3. 扩展语言语法。

  4. 扩展语言语义。

  5. 如何为JavaScript语言添加运算符。

  1. What are syntax and semantics.
  2. How are programming languages parsed? What is a syntax tree?
  3. Extending the language syntax.
  4. Extending the language semantics.
  5. How do I add an operator to the JavaScript language.

如果你很懒,只想看到它的运行 - 我将工作代码放在GitHub上

If you're lazy and just want to see it in action - I put the working code on GitHub

非常一般 - 一种语言由两件事组成。

Very generally - a language is composed of two things.


  • 语法 - 这些是语言中的符号,如 ++ ,以及 Expression s就像 FunctionExpression 表示内联函数。语法仅表示使用的符号,不表示的含义。简而言之,语法只是字母和符号的图纸 - 它没有固有的含义。

  • Syntax - these are the symbols in the language like unary operators like ++, as well as Expressions like a FunctionExpression that represent an "inline" function. The syntax represents just the symbols used and not their meaning. In short the syntax is just the drawings of letters and symbols - it holds no inherent meaning.

语义 tie对这些符号有意义。语义就是说 ++ 表示递增1,实际上这里是确切的定义。它与我们的语法有意义,没有它,语法只是一个带有顺序的符号列表。

Semantics ties meaning to these symbols. Semantics is what says ++ means "increment by one", in fact here is the exact defintion. It ties meaning to our syntax and without it the syntax is just a list of symbols with an order.

在某些时候,当某些东西用JavaScript或任何其他编程语言执行代码时 - 它需要理解该代码。其中一部分称为 lexing (或令人称意,我们不会在这里进行细微差别)意味着分解代码如下:

At some point, when something executes your code in JavaScript or any other programming language - it needs to understand that code. A part of this called lexing (or tokenizing, let's not go into subtle differences here) means breaking up code like:

function foo(){ return 5;}

进入其有意义的部分 - 这就是说这里有一个函数关键字,后跟一个标识符,一个空参数列表,然后一个块打开 { 包含一个带有文字 5 的return关键字,然后是分号,然后是一个结束块}

Into its meaningful parts - that is saying that there is a function keyword here, followed by an identifier, an empty arguments list, then a block opening { containing a return keyword with the literal 5, then a semicolon, then an end block }.

这部分语法中的完全,它所做的就是将它分解为函数等部分, foo,(,),{,return,5,;,} 。它仍然不理解代码。

This part is entirely in the syntax, all it does is break it up to parts like function,foo,(,),{,return,5,;,} . It still has no understanding of the code.

之后 - 语法树建成。语法树更了解语法,但仍然完全是语法。例如,语法树会看到以下标记:

After that - a Syntax Tree is built. A syntax tree is more aware of the grammar but is still entirely syntactic. For example, a syntax tree would see the tokens of:

function foo(){ return 5;}

并弄清楚嘿!有一个函数声明在这里!。

And figure out "Hey! There is a function declaration here!".

它被称为树,因为它只是 - 树允许嵌套。

It's called a tree because it's just that - trees allow nesting.

例如,上面的代码可以产生如下代码:

For example, the code above can produce something like:

                                        Program
                                  FunctionDeclaration (identifier = 'foo')
                                     BlockStatement
                                     ReturnStatement
                                     Literal (5)

这很简单,只是为了告诉你它并不总是那么线性,让我们检查 5 +5

This is rather simple, just to show you it isn't always so linear, let's check 5 +5:

                                        Program
                                  ExpressionStatement
                               BinaryExpression (operator +)
                            Literal (5)       Literal(5)   // notice the split her

这种拆分可能会发生。

基本上,语法树允许我们表达语法。

Basically, a syntax tree allows us to express the syntax.

这是 x的地方∘y 失败 - 它看到并且不理解语法。

This is where x ∘ y fails - it sees and doesn't understand the syntax.

这只需要一个解析语法的项目。我们在这里要做的是阅读我们的语言的语法,它与JavaScript不同(并且不符合规范),并用JavaScript语法可以替换我们的运算符。

This just requires a project that parses the syntax. What we'll do here is read the syntax of "our" language which is not the same as JavaScript (and does not comply to the specification) and replace our operator with something the JavaScript syntax is OK with.

我们要做的是不是 JavaScript。它不遵循JavaScript规范和标准投诉JS解析器将抛出异常。

What we'll be making is not JavaScript. It does not follow the JavaScript specification and a standards complaint JS parser will throw an exception on it.

我们无论如何都要这样做:)我们在这里所做的只是定义一个在调用运算符时调用的函数。

This we do all the time anyway :) All we'll do here is just define a function to call when the operator is called.

让我先说一下这个前缀后我们添加一个这里是JS的操作符,而不是 - 我们正在定义我们自己的语言 - 让我们称它为CakeLanguage或者其他东西并将操作符添加到它。这是因为不是JS语法的一部分,JS语法不允许像其他一些语言

Let me just start by saying after this prefix that we'll not be adding an operator to JS here, rather - we're defining our own language - let's call it "CakeLanguage" or something and add the operator it it. This is because is not a part of the JS grammar and the JS grammar does not allow arbitrary operators like some other languages.

我们将使用两个开源项目:

We'll use two open source projects for this:


  • esprima JS代码并为其生成语法树。

  • escodegen 执行另一个方向,从语法树esprima吐出生成JS代码。

  • esprima which takes JS code and generates the syntax tree for it.
  • escodegen which does the other direction, generating JS code from the syntax tree esprima spits.

你密切关注你知道我们不能直接使用esprima,因为我们会给它不懂的语法。

It you paid close attention you'd know we can't use esprima directly since we'll be giving it grammar it does not understand.

我们将添加一个运算符,它可以实现 x#y === 2x + y 。我们将赋予它多重性的优先级(因为运算符具有运算符优先级)。

We'll add a # operator that does x # y === 2x + y for the fun. We'll give it the precedence of multiplicity (because operators have operator precedence).

因此,在获得Esprima.js副本后 - 我们需要更改以下内容:

So, after you get your copy of Esprima.js - we'll need to change the following:

FnExprTokens - 这是表达式我们需要添加所以它会识别它。之后,它看起来像这样:

To FnExprTokens - that is expressions we'll need to add # so it'd recognize it. Afterwards, it'd look as such:

FnExprTokens = ['(', '{', '[', 'in', 'typeof', 'instanceof', 'new',
                    'return', 'case', 'delete', 'throw', 'void',
                    // assignment operators
                    '=', '+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '>>>=',
                    '&=', '|=', '^=', ',',
                    // binary/unary operators
                    '+', '-', '*', '/', '%','#', '++', '--', '<<', '>>', '>>>', '&',
                    '|', '^', '!', '~', '&&', '||', '?', ':', '===', '==', '>=',
                    '<=', '<', '>', '!=', '!=='];

scanPunctuator 我们将添加它及其char代码作为一种可能的情况: case 0x23://#

To scanPunctuator we'll add it and its char code as a possible case: case 0x23: // #

然后进行测试,看起来像:

And then to the test so it looks like:

 if ('<>=!+-*#%&|^/'.indexOf(ch1) >= 0) {

而不是:

    if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) {

然后到 binaryPrecedence 让它赋予它与多重性相同的优先级:

And then to binaryPrecedence let's give it the same precedence as multiplicity:

case '*':
case '/':
case '#': // put it elsewhere if you want to give it another precedence
case '%':
   prec = 11;
   break;

就是这样!我们刚刚扩展了语言语法以支持运算符。

That's it! We've just extended our language syntax to support the # operator.

我们还没有完成,我们我需要将它转换回JS。

We're not done yet, we need to convert it back to JS.

让我们首先为我们的树定义一个简短的 visitor 函数,递归访问所有它的节点。

Let's first define a short visitor function for our tree that recursively visits all its node.

function visitor(tree,visit){
    for(var i in tree){
        visit(tree[i]);
        if(typeof tree[i] === "object" && tree[i] !== null){
            visitor(tree[i],visit);
        }
    }
}

这只是通过Esprima生成树并访问它。我们传递一个函数并在每个节点上运行它。

This just goes through the Esprima generated tree and visits it. We pass it a function and it runs that on every node.

现在,让我们看看我们的特殊新运算符:

Now, let's treat our special new operator:

visitor(syntax,function(el){ // for every node in the syntax
    if(el.type === "BinaryExpression"){ // if it's a binary expression

        if(el.operator === "#"){ // with the operator #
        el.type = "CallExpression"; // it is now a call expression
        el.callee = {name:"operator_sharp",type:"Identifier"}; // for the function operator_#
        el.arguments = [el.left, el.right]; // with the left and right side as arguments
        delete el.operator; // remove BinaryExpression properties
        delete el.left;
        delete el.right;
        }
    }
});

所以简而言之:

var syntax = esprima.parse("5 # 5");

visitor(syntax,function(el){ // for every node in the syntax
    if(el.type === "BinaryExpression"){ // if it's a binary expression

        if(el.operator === "#"){ // with the operator #
        el.type = "CallExpression"; // it is now a call expression
        el.callee = {name:"operator_sharp",type:"Identifier"}; // for the function operator_#
        el.arguments = [el.left, el.right]; // with the left and right side as arguments
        delete el.operator; // remove BinaryExpression properties
        delete el.left;
        delete el.right;
        }
    }
});

var asJS = escodegen.generate(syntax); // produces operator_sharp(5,5);

我们需要做的最后一件事是定义函数本身:

The last thing we need to do is define the function itself:

function operator_sharp(x,y){
    return 2*x + y;
}

并在我们的代码上方加上。

And include that above our code.

这就是它的全部!如果你读到目前为止 - 你应该得到一个cookie:)

That's all there is to it! If you read so far - you deserve a cookie :)

这是 GitHub上的代码,以便您可以使用它。

Here is the code on GitHub so you can play with it.

这篇关于我如何扩展JavaScript语言以支持新的运算符?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆