问题的答案 是否可以在JavaScript中创建自定义运算符? 是 没有的,但@Benjamin建议,这将有可能使用添加一个新的运营商 的第三方工具 :
可以使用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)
2 + 2 * 3
如何扩展JavaScript语言以支持新的运算符?
是的,这是可能的,甚至不是很难:)
我们需要讨论一些事情:
如果您很懒,只是想看看它在运行- 我将工作代码放在GitHub上
通常,一种语言由两部分组成。
语法 -这些是语言中的符号,如一元运算符++()和Expressions(如)FunctionExpression表示“内联”函数。语法仅代表所使用的符号,而 不代表 其含义。简而言之 ,语法只是字母和符号的图示 -它没有固有的含义。
++
Expression
FunctionExpression
语义 与这些符号联系在一起。语义学的++意思是“一个增量”,实际上这里是确切的定义。它把含义与我们的语法联系在一起,没有它,语法只是带有顺序的符号列表。
在某些时候,当某人用JavaScript或任何其他编程语言执行您的代码时-它需要了解该代码。其中一部分叫做 词法分析 (或 词法化 ,在这里不要做细微的区别)意味着分解代码,例如:
function foo(){ return 5;}
对其有意义的部分进行说明- 也就是说,这里有一个function关键字,后跟一个标识符,一个空的参数列表,然后是一个{包含带有文字的return关键字的块开头5,然后是一个分号,然后一个end块}。
function
{
5
}
这部分 完全 是语法,它所做的只是将其分解为function,foo,(,),{,return,5,;,}。它仍然对代码 不了解 。
function,foo,(,),{,return,5,;,}
之后- Syntax Tree建立一个。语法树更了解语法,但仍然完全是语法。例如,语法树将看到以下标记:
Syntax Tree
然后找出“嘿!这里有一个函数声明!”。
之所以称其为树,是因为它-树允许嵌套。
例如,上面的代码可以产生如下内容:
Program FunctionDeclaration (identifier = 'foo') BlockStatement ReturnStatement Literal (5)
这很简单,只是向您展示它并不总是那么线性,让我们检查一下5 +5:
5 +5
Program ExpressionStatement BinaryExpression (operator +) Literal (5) Literal(5) // notice the split her
可能会发生这种分裂。
基本上,语法树使我们可以表达语法。
这就是x ∘ y失败的地方-它看到∘并且不了解语法。
x ∘ y
∘
这仅需要一个解析语法的项目。我们在这里要做的是读取“我们的”语言的语法,该语法与JavaScript不同(并且不符合规范),然后将操作符替换为JavaScript语法可以使用的语言。
我们要做的 不是 JavaScript。它不遵循JavaScript规范,并且标准投诉JS解析器将对其抛出异常。
无论如何,我们始终会这样做:)我们在这里要做的只是定义一个在调用运算符时要调用的函数。
让我首先在此前缀之后说,我们 不会 在此处向JS添加操作符,而是-我们正在定义自己的语言-我们将其称为“ CakeLanguage”或其他名称,然后将其添加到操作符中。这是因为∘它不是JS语法的一部分,并且JS语法不允许像某些其他语言一样使用任意运算符。
为此,我们将使用两个开源项目:
如果您密切注意,您会知道我们 不能 直接使用esprima,因为我们将提供它不了解的语法。
我们将添加一个有趣的#操作符x # y === 2x + y。我们将它赋予多重性的优先级(因为运算符具有运算符优先级)。
#
x # y === 2x + y
因此,在获得Esprima.js的副本之后-我们需要更改以下内容:
到FnExprTokens-这是我们需要添加的 表达式 ,#以便可以识别它。然后,它看起来像这样:
FnExprTokens
FnExprTokens = ['(', '{', '[', 'in', 'typeof', 'instanceof', 'new', 'return', 'case', 'delete', 'throw', 'void', // assignment operators '=', '+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '>>>=', '&=', '|=', '^=', ',', // binary/unary operators '+', '-', '*', '/', '%','#', '++', '--', '<<', '>>', '>>>', '&', '|', '^', '!', '~', '&&', '||', '?', ':', '===', '==', '>=', '<=', '<', '>', '!=', '!=='];
要scanPunctuator添加它及其字符代码(可能的话):case 0x23: // #
scanPunctuator
case 0x23: // #
然后进行测试,如下所示:
if ('<>=!+-*#%&|^/'.indexOf(ch1) >= 0) {
代替:
if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) {
然后binaryPrecedence让我们赋予它与多重性相同的优先级:
binaryPrecedence
case '*': case '/': case '#': // put it elsewhere if you want to give it another precedence case '%': prec = 11; break;
而已!我们刚刚扩展了语言语法以支持#运算符。
我们尚未完成,我们需要将其转换回JS。
首先,visitor为树定义一个简短函数,该函数递归访问其所有节点。
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); } } }
这只是通过Esprima生成的树并进行访问。我们将其传递给函数,并在每个节点上运行该函数。
现在,让我们来对待我们特殊的新运算符:
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);
我们需要做的最后一件事是定义函数本身:
function operator_sharp(x,y){ return 2*x + y; }
并将其包含在我们的代码上方。
这里的所有都是它的!如果到目前为止,您应该得到一个cookie :)
这是GitHub上的代码,因此您可以使用它。