Is it possible to create custom operators in JavaScript? 这个问题的答案是 尚未 ,但@Benjamin suggested表示可以使用第三方工具添加新运算符:
虽然需要额外的编译步骤,但可以使用像sweet.js这样的第三方工具来添加自定义运算符。
我将采用相同的例子,就像上一个问题一样:
对于任何两个实数 x 和 y :x∘y是 x + 2y ,这也是一个真实的数字。如何在我的扩展JavaScript语言中添加此运算符?
运行以下代码后:
var x = 2
, y = 3
, z = x ∘ y;
console.log(z);
输出将包含
8
(因为8
是2 + 2 * 3
)
如何扩展JavaScript语言以支持新的运算符?
答案 0 :(得分:40)
是的,它是可能的,甚至不是很难:)
我们需要讨论一些事情:
如果你很懒,只是想看到它的实际效果 - I put the working code on GitHub
非常普遍 - 一种语言由两件事组成。
语法 - 这些是语言中的符号,如++
等一元运算符,以及代表FunctionExpression
的Expression
"内联"功能。语法仅表示使用的符号,不表示的含义。简而言之,语法只是字母和符号的图纸 - 它没有固有的含义。
语义与这些符号有关。语义就是说++
意味着"增加一个",实际上这里是the exact defintion。它与我们的语法有意义,没有它,语法只是一个带有顺序的符号列表。
在某些时候,当某些东西用JavaScript或任何其他编程语言执行代码时 - 它需要理解该代码。其中一部分称为 lexing (或标记化,让我们在这里不会产生微妙的差异)意味着分解代码如下:
function foo(){ return 5;}
进入其有意义的部分 - 也就是说这里有一个function
关键字,后跟一个标识符,一个空参数列表,然后是一个打开{
的块,其中包含一个带有literal {的return关键字{1}},然后是分号,然后是结束块5
。
这部分语法中的完全,它所做的只是将其分解为}
等部分。它仍然不理解代码。
之后 - 构建function,foo,(,),{,return,5,;,}
。语法树更了解语法,但仍然完全是语法。例如,语法树会看到以下标记:
Syntax Tree
弄清楚"嘿!这里有function declaration!"。
它被称为树,因为它只是 - 树允许嵌套。
例如,上面的代码可以产生类似:
function foo(){ return 5;}
这很简单,只是为了告诉你它不是那么线性,让我们检查 Program
FunctionDeclaration (identifier = 'foo')
BlockStatement
ReturnStatement
Literal (5)
:
5 +5
这种分裂可能会发生。
基本上,语法树允许我们表达语法。
这是 Program
ExpressionStatement
BinaryExpression (operator +)
Literal (5) Literal(5) // notice the split her
失败的地方 - 它看到x ∘ y
并且不理解语法。
这只需要一个解析语法的项目。我们在这里做的是阅读"我们的"的语法。语言与JavaScript不同(并且不符合规范),并用JavaScript语法可以替换我们的运算符。
我们要做的是不是 JavaScript。它不遵循JavaScript规范和标准投诉JS解析器会在其上引发异常。
我们总是这样做:)我们在这里做的只是定义一个在调用运算符时调用的函数。
首先我要说的是,在这个前缀之后,我们 称之为" CakeLanguage"或其他东西,并添加操作员。这是因为 我们将使用两个开源项目: 你密切关注你知道我们不能直接使用esprima,因为我们会给它不懂的语法。 我们会添加 因此,在您获得Esprima.js副本后,我们需要更改以下内容: 要 要 然后进行测试,看起来像: 而不是: 然后到 那就是它!我们只是扩展了语言语法以支持 我们尚未完成,我们需要将其转换回JS。 让我们首先为我们的树定义一个简短的 这只是通过Esprima生成的树并访问它。我们传递一个函数,它在每个节点上运行。 现在,让我们来看待我们的特殊新运营商: 简而言之: 我们需要做的最后一件事是定义函数本身: 并将其包含在我们的代码之上。 这就是它的全部!如果你到目前为止阅读 - 你应该得到一个cookie:) 以下是code on GitHub,因此您可以使用它。∘
不是JS语法的一部分,而且JS语法不允许像some other languages这样的任意运算符。∘
运算符#
以获得乐趣。我们将它赋予多重性的优先级(因为运算符具有运算符优先级)。x # y === 2x + y
- 即表达式,我们需要添加FnExprTokens
,以便识别它。之后,它看起来像这样:#
FnExprTokens = ['(', '{', '[', 'in', 'typeof', 'instanceof', 'new',
'return', 'case', 'delete', 'throw', 'void',
// assignment operators
'=', '+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '>>>=',
'&=', '|=', '^=', ',',
// binary/unary operators
'+', '-', '*', '/', '%','#', '++', '--', '<<', '>>', '>>>', '&',
'|', '^', '!', '~', '&&', '||', '?', ':', '===', '==', '>=',
'<=', '<', '>', '!=', '!=='];
,我们会将其及其字符代码添加为可能的案例:scanPunctuator
case 0x23: // #
if ('<>=!+-*#%&|^/'.indexOf(ch1) >= 0) {
if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) {
让它给出与多重性相同的优先级:binaryPrecedence
case '*':
case '/':
case '#': // put it elsewhere if you want to give it another precedence
case '%':
prec = 11;
break;
运算符。#
函数,以递归方式访问其所有节点。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);
}
}
}
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);
答案 1 :(得分:3)
正如我在你的问题的评论中所说,sweet.js doesn't support infix operators。你可以自由地分叉sweet.js并自己添加,或者你只是SOL。
老实说,实现自定义中缀运算符并不值得。 Sweet.js是一个很好的支持工具,它是我所知道的唯一尝试在JS中实现宏的工具。使用自定义预处理器添加自定义中缀运算符可能不值得您获得。
那就是说,如果你正在为非职业工作单独工作,那就做你想做的事......