简体   繁体   English

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

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

The answer to the question Is it possible to create custom operators in JavaScript? 问题的答案是否可以在JavaScript中创建自定义运算符? is not yet , but @Benjamin suggested that it would be possible to add a new operator using third party tools : 没有 ,但@Benjamin建议可以使用第三方工具添加新的运算符:

It is possible to use third party tools like sweet.js to add custom operators though that'd require an extra compilation step. 虽然需要额外的编译步骤,但可以使用像sweet.js这样的第三方工具来添加自定义运算符。

I will take the same example, like in the previous question: 我会采用相同的例子,就像上一个问题一样:

(ℝ, ∘), x ∘ y = x + 2y (ℝ,∘),x∘y= x + 2y

For any two real numbers x and y : x ∘ y is x + 2y that is also a real number. 对于任何两个实数xyx∘yx + 2y ,也是实数。 How can I add this operator in my extended JavaScript language? 如何在我的扩展JavaScript语言中添加此运算符?

After the following code will be run: 运行以下代码后:

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

console.log(z);

The output will contain 输出将包含

8

(because 8 is 2 + 2 * 3 ) (因为82 + 2 * 3


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

Yes, it's possible and not even very hard :) 是的,这是可能的,甚至不是很难:)


We'll need to discuss a few things: 我们需要讨论一些事情:

  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. 如何向JavaScript语言添加运算符。

If you're lazy and just want to see it in action - I put the working code on GitHub 如果你很懒,只是想看看它的实际效果 - 我把工作代码放在GitHub上

1. What is syntax and semantics? 1.什么是语法和语义?

Very generally - a language is composed of two things. 一般来说 - 一种语言由两件事组成。

  • Syntax - these are the symbols in the language like unary operators like ++ , as well as Expression s like a FunctionExpression that represent an "inline" function. 语法 - 这些是语言中的符号,如++等一元运算符,以及Expression像“内联”函数的FunctionExpression Expression 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. 简而言之,语法只是字母和符号的图形 - 它没有固有的含义。

  • 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. 它与我们的语法有意义,没有它,语法只是带有顺序的符号列表。

2. How are programming languages parsed? 2.如何解析编程语言? What is a syntax tree? 什么是语法树?

At some point, when something executes your code in JavaScript or any other programming language - it needs to understand that code. 在某些时候,当某些东西用JavaScript或任何其他编程语言执行你的代码时 - 它需要理解那些代码。 A part of this called lexing (or tokenizing, let's not go into subtle differences here) means breaking up code like: 这个名为lexing的一部分(或标记化,我们不会在这里进行细微差别)意味着分解如下代码:

function foo(){ return 5;}

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

This part is entirely in the syntax, all it does is break it up to parts like function,foo,(,),{,return,5,;,} . 这部分完全在语法中,它所做的就是将它分解为function,foo,(,),{,return,5,;,} It still has no understanding of the code. 它仍然不了解代码。

After that - a Syntax Tree is built. 之后 - 构建一个Syntax Tree 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)

This is rather simple, just to show you it isn't always so linear, let's check 5 +5 : 这很简单,只是为了告诉你它并不总是如此线性,让我们检查5 +5

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

Such splits can occur. 可能发生这种分裂。

Basically, a syntax tree allows us to express the syntax. 基本上,语法树允许我们表达语法。

This is where x ∘ y fails - it sees and doesn't understand the syntax. 这是x ∘ y失败的地方 - 它看到并且不理解语法。

3. Extending the language syntax. 3.扩展语言语法。

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语法可以替换我们的运算符。

What we'll be making is not JavaScript. 我们要做的不是 JavaScript。 It does not follow the JavaScript specification and a standards complaint JS parser will throw an exception on it. 它不遵循JavaScript规范和标准投诉JS解析器将抛出异常。

4. Extending the language semantics 4.扩展语言语义

This we do all the time anyway :) All we'll do here is just define a function to call when the operator is called. 我们总是这样做:)我们在这里所做的只是定义一个在调用运算符时调用的函数。

5. How do I add an operator to the JavaScript language. 5.如何向JavaScript语言添加运算符。

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. 让我先说一下这个前缀后我们不会在这里添加一个运算符,而是 - 我们正在定义我们自己的语言 - 让我们称它为“CakeLanguage”或其他东西并添加运算符吧。 This is because is not a part of the JS grammar and the JS grammar does not allow arbitrary operators like some other languages . 这是因为不是JS语法的一部分,并且JS语法不允许像其他语言那样的任意运算符。

We'll use two open source projects for this: 我们将使用两个开源项目:

  • esprima which takes JS code and generates the syntax tree for it. esprima ,它接受JS代码并为其生成语法树。
  • escodegen which does the other direction, generating JS code from the syntax tree esprima spits. escodegen执行另一个方向,从语法树esprima spits生成JS代码。

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. 你密切注意你知道我们不能直接使用esprima因为我们会给它不懂的语法。

We'll add a # operator that does x # y === 2x + y for the fun. 我们将添加一个#运算符来执行x # y === 2x + y以获得乐趣。 We'll give it the precedence of multiplicity (because operators have operator precedence). 我们将赋予它多重性的优先级(因为运算符具有运算符优先级)。

So, after you get your copy of Esprima.js - we'll need to change the following: 因此,在您获得Esprima.js副本后 - 我们需要更改以下内容:

To FnExprTokens - that is expressions we'll need to add # so it'd recognize it. 对于FnExprTokens - 这是表达式,我们需要添加#以便识别它。 Afterwards, it'd look as such: 之后,它看起来像这样:

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

To scanPunctuator we'll add it and its char code as a possible case: case 0x23: // # 对于scanPunctuator我们将添加它及其char代码作为一种可能的情况: case 0x23: // #

And then to the test so it looks like: 然后进行测试,看起来像:

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

Instead of: 代替:

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

And then to binaryPrecedence let's give it the same precedence as multiplicity: 然后到binaryPrecedence让我们赋予它与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. 我们刚刚扩展了语言语法以支持#运算符。

We're not done yet, we need to convert it back to JS. 我们尚未完成,我们需要将其转换回JS。

Let's first define a short visitor function for our tree that recursively visits all its node. 让我们首先为我们的树定义一个简短的visitor函数,递归访问它的所有节点。

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

This just goes through the Esprima generated tree and visits it. 这只是通过Esprima生成的树并访问它。 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;
        }
    }
});

So in short: 简而言之:

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. 并将其包含在我们的代码之上。

That's all there is to it! 这里的所有都是它的! If you read so far - you deserve a cookie :) 如果你读到目前为止 - 你应该得到一个cookie :)

Here is the code on GitHub so you can play with it. 这是GitHub上代码,所以你可以玩它。

As I said in the comments of your question, sweet.js doesn't support infix operators yet. 正如我在你的问题的评论中所说,sweet.js 还不支持中缀运算符 You're free to fork sweet.js and add it yourself, or you're simply SOL. 你可以自由地分叉sweet.js并自己添加,或者你只是SOL。

Honestly, it's not worth it to implement custom infix operators yet. 老实说,实现自定义中缀运算符是不值得的。 Sweet.js is a well supported tool, and it's the only one I know of that tries to implement macros in JS. Sweet.js是一个很好的支持工具,它是我所知道的唯一尝试在JS中实现宏的工具。 Adding custom infix operators with a custom preprocessor is probably not worth the gain you might have. 使用自定义预处理器添加自定义中缀运算符可能不值得您获得。

That said, if you're working on this alone for non-professional work, do whatever you want... 也就是说,如果你正在为非职业工作单独工作,那就做你想做的事......

EDIT 编辑

sweet.js does now support infix operators . sweet.js现在支持中缀运营商

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 我在哪里使用JavaScript中的按位运算符? - Where would I use a bitwise operator in JavaScript? 您将如何重载 javascript 中的 [] 运算符 - How would you overload the [] operator in javascript 如何用新的while语句扩展Javascript - How to extend Javascript with new while statment 如何使用新变量扩展JavaScript文字(对象)? - How to extend JavaScript literal (object) with a new variable? 如何“扩展” javascript“类”并添加新内容? - How to “extend” javascript “class” and add new stuff? 如何使用javascript中的新属性扩展现有类? - How to extend existing class with new properties in javascript? JavaScript是否支持&lt;=运算符? - Does JavaScript support <= operator? 使用JavaScript / jQuery,我如何即时对JSON对象进行排序/重新排序以支持各种显示? - With JavaScript/jQuery How would I sort/resort this JSON object on the fly to support various displays? 如何使用JavaScript在:before伪元素之前检测CSS过渡支持? - How would I detect CSS Transition support on :before pseudo-elements with javascript? 在 Javascript 中,我如何使一个函数支持多种参数类型而不被 v8 取消优化? - In Javascript, how would I make a function support multiple parameter types without it becoming deoptimised by v8?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM