Как бы я расширил язык JavaScript для поддержки нового оператора?


Ответ на вопрос Можно ли создавать пользовательские операторы в JavaScript? есть еще нет, но @Benjamin предположил , что можно было бы добавить новый оператор, используя сторонние инструменты :

Можно использовать сторонние инструменты, такие как sweet.js, чтобы добавить пользовательские операторы, хотя это потребует дополнительного шага компиляции.

Я возьму тот же пример, что и в предыдущем вопрос:

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

Для любых двух вещественных чисел x и y: x ∘ y - это x + 2y, которое также является вещественным числом. Как добавить этот оператор в мой расширенный язык JavaScript?

После выполнения следующего кода:

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

console.log(z);

Вывод будет содержать

8

(потому что 8 есть 2 + 2 * 3)


Как бы я расширил язык JavaScript для поддержки нового оператора?

2 21

2 ответа:

Да, это возможно и даже не очень сложно :)


Нам нужно обсудить несколько вещей:

    Что такое синтаксис и семантика?
  1. Как разбираются языки программирования? Что такое синтаксическое дерево?
  2. расширение синтаксиса языка.
  3. Расширение семантики языка.
  4. Как добавить оператор в язык JavaScript.

Если вы ленивы и просто хотите увидеть его в действии - я включил рабочий код. GitHub

[58]}1. Что такое синтаксис и семантика? Очень обобщенно-язык состоит из двух вещей.
  • Синтаксис - это символы в языке, такие как унарные операторы, такие как ++, а также Expressions, такие как a FunctionExpression они представляют собой "встроенную" функцию. Синтаксис представляет только используемые символы и не их значение. Короче говоря синтаксис - это просто рисунки букв и символов - это не имеет никакого внутреннего смысла.

  • Семантика связывает значение с этими символами. Семантика-это то, что говорит ++ означает "приращение на единицу", на самом деле здесь точное определение. Он связывает смысл с нашим синтаксисом, и без него синтаксис - это просто список символов с порядком.

[58]}2. Как разбираются языки программирования? Что такое синтаксическое дерево?

В какой-то момент, когда что-то выполняет ваш код в JavaScript или любом другом программировании язык-он должен понимать этот код. Часть этого называется лексинг (или токенизация, давайте не будем вдаваться в тонкие различия здесь) означает разбиение кода, например:

function foo(){ return 5;}

В его содержательные части - то есть здесь есть ключевое слово function, за которым следует идентификатор, пустой список аргументов, затем блок, открывающий {, содержащий ключевое слово return с литералом 5, затем точка с запятой, затем конечный блок }.

Этой части полностью в синтаксисе, все, что он делает, это разбивает его на части, такие как function,foo,(,),{,return,5,;,} . Он все еще не имеет никакого понимания кода.

После этого-строится Syntax Tree. Синтаксическое дерево более осведомлено о грамматике, но все еще полностью синтаксическое. Например, синтаксическое дерево будет видеть маркеры:

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 терпит неудачу - он видит и не понимает синтаксиса.

[58]}3. Расширение синтаксиса языка.

Для этого просто требуется проект, который анализирует синтаксис. Здесь мы будем читать синтаксис "нашего" языка, который не совпадает с JavaScript (и не соответствует спецификации), и заменять наш оператор на что-то, с чем синтаксис JavaScript в порядке.

То, что мы будем делать, - это не JavaScript. Он не соответствует спецификации JavaScript, и анализатор жалоб JS на стандарты выдаст исключение для него.

[58]}4. Расширение семантики языка

Это мы делаем все время в любом случае :) все, что мы будем делать вот только определите функцию для вызова при вызове оператора.

[130]}5. Как добавить оператор в язык JavaScript. Позвольте мне просто начать, сказав после этого префикса, что мы не будем добавлять оператор к JS здесь, скорее - мы определяем наш собственный язык - давайте назовем его "CakeLanguage" или что-то еще и добавим оператор it it. Это происходит потому, что не является частью грамматики JS, а грамматика JS не допускает произвольных операторов, таких как некоторые другие языки .

Для этого мы используем два проекта с открытым исходным кодом:

  • эсприма который принимает код JS и генерирует синтаксическое дерево для него.
  • эскодеген что делает другое направление, генерируя код JS из синтаксического дерева esprima spits.

Если бы вы обратили на это пристальное внимание, вы бы знали, что мы не можем использовать esprima напрямую, так как мы будем давать ему грамматику, которой у него нет понимать.

Мы добавим оператор #, который делает x # y === 2x + y для удовольствия. Мы дадим ему приоритет кратности (потому что операторы имеют приоритет операторов).

Итак, после того, как вы получите свой экземпляр Esprima.js-нам нужно изменить следующее:

К FnExprTokens - то есть выражениям нам нужно будет добавить #, чтобы он распознал его. Впоследствии это выглядело бы так:

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

К scanPunctuator мы добавим его и его char-код в качестве возможного случая: 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;

Вот оно! Мы только что расширили синтаксис нашего языка для поддержки оператора #.

Мы еще не закончили, нам нужно преобразовать его обратно в JS.

Давайте сначала определим короткую функцию 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);
Последнее, что нам нужно сделать, это определить саму функцию:
function operator_sharp(x,y){
    return 2*x + y;
}

И включите это выше нашего кода.

Вот и все, что нужно сделать! Если Вы читаете до сих пор - вы заслуживаете печенье:)

Вот кодна GitHub , так что вы можете играть с ним.

Как я уже сказал в комментариях к вашему вопросу, милая.js пока не поддерживает операторы инфикса. Ты можешь раскошелиться на сладкое.js и добавьте его сами, или вы просто Сол.

Честно говоря, пока не стоит реализовывать пользовательские операторы инфикса. Сладкий.js-это хорошо поддерживаемый инструмент, и это единственный известный мне инструмент, который пытается реализовать макросы в JS. Добавление пользовательских операторов инфикса с пользовательским препроцессором, вероятно, не стоит того выигрыша, который вы могли бы получить.

Это означает, что если вы работаете над этим в одиночку для непрофессиональной работы, делайте все, что хотите...