Подтвердить что ты не робот

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

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

Для добавления пользовательских операторов можно использовать сторонние инструменты, такие как 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 для поддержки нового оператора?

4b9b3361

Ответ 1

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


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

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

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

1. Что такое синтаксис и семантика?

В целом - язык состоит из двух вещей.

  • Синтаксис - это символы на языке, такие как унарные операторы типа ++, а также Expression как FunctionExpression, которые представляют собой встроенную функцию. Синтаксис представляет собой только используемые символы, а не их значение. Короче говоря, синтаксис - это только рисунки букв и символов - он не содержит неотъемлемого значения.

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

2. Как анализируются языки программирования? Что такое синтаксическое дерево?

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

function foo(){ return 5;}

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

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

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

3. Расширение синтаксиса языка.

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

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

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

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

5. Как добавить оператор на язык JavaScript.

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

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

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

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

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

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

To 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 для нашего дерева, которая рекурсивно посещает все ее 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, и посещает его. Мы передаем ему функцию и запускаем ее на каждом node.

Теперь рассмотрим наш специальный новый оператор:

visitor(syntax,function(el){ // for every node in the syntax
    if(el.type === "BinaryExpression"){ // if it 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 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, чтобы вы могли играть с ним.

Ответ 2

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

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

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

EDIT

sweet.js теперь поддерживает инфиксные операторы.