resueltos programacion ejemplos codigos codigo javascript node.js operators language-extension

programacion - manual javascript pdf 2017



¿Cómo extendería el lenguaje JavaScript para admitir un nuevo operador? (2)

La respuesta a la pregunta ¿Es posible crear operadores personalizados en JavaScript? Aún no lo es , pero @Benjamin sugirió que sería posible agregar un nuevo operador utilizando herramientas de terceros :

Es posible utilizar herramientas de terceros como sweet.js para agregar operadores personalizados, aunque eso requiera un paso de compilación adicional.

Tomaré el mismo ejemplo, como en la pregunta anterior:

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

Para cualquiera de los dos números reales x e y : x ∘ y es x + 2y que también es un número real. ¿Cómo puedo agregar este operador en mi lenguaje extendido de JavaScript?

Después se ejecutará el siguiente código:

var x = 2 , y = 3 , z = x ∘ y; console.log(z);

La salida contendrá

8

(porque 8 es 2 + 2 * 3 )

¿Cómo extendería el lenguaje JavaScript para admitir un nuevo operador?


Como dije en los comentarios de su pregunta, sweet.js github.com/mozilla/sweet.js/issues/34 todavía. Eres libre de bifurcar sweet.js y agregarlo tú mismo, o simplemente eres SOL.

Honestamente, no vale la pena implementar operadores de infijo personalizados todavía. Sweet.js es una herramienta bien soportada, y es la única que conozco que intenta implementar macros en JS. Añadir operadores de infijo personalizados con un preprocesador personalizado probablemente no valga la pena que pueda tener.

Dicho esto, si estás trabajando solo en esto para un trabajo no profesional, haz lo que quieras ...


Sí, es posible y ni siquiera muy difícil :)

Tendremos que discutir algunas cosas:

  1. Qué son la sintaxis y la semántica.
  2. ¿Cómo se analizan los lenguajes de programación? ¿Qué es un árbol de sintaxis?
  3. Extendiendo la sintaxis del lenguaje.
  4. Extendiendo la semántica del lenguaje.
  5. ¿Cómo agrego un operador al lenguaje JavaScript?

Si eres perezoso y solo quieres verlo en acción, puse el código de trabajo en GitHub

1. ¿Qué es la sintaxis y la semántica?

En general, un lenguaje se compone de dos cosas.

  • Sintaxis : estos son los símbolos en el lenguaje como operadores unarios como ++ , así como Expression como una FunctionExpression Expression que representan una función "en línea". La sintaxis representa solo los símbolos utilizados y no su significado. En resumen, la sintaxis es solo un dibujo de letras y símbolos , no tiene ningún significado inherente.

  • La semántica vincula significado a estos símbolos. La semántica es lo que dice ++ significa "incremento en uno", de hecho, aquí está la definición exacta . Vincula el significado a nuestra sintaxis y, sin ella, la sintaxis es solo una lista de símbolos con un orden.

2. ¿Cómo se analizan los lenguajes de programación? ¿Qué es un árbol de sintaxis?

En algún momento, cuando algo ejecuta su código en JavaScript o en cualquier otro lenguaje de programación, debe comprender ese código. Una parte de esto llamada lexing (o tokenización, no entremos en diferencias sutiles aquí) significa dividir el código como:

function foo(){ return 5;}

En sus partes significativas, es decir, hay una palabra clave de function aquí, seguida de un identificador, una lista de argumentos vacía, luego una apertura de bloque { contiene una palabra clave de retorno con el literal 5 , luego un punto y coma, luego un bloque final } .

Esta parte está completamente en la sintaxis, todo lo que hace es dividirla en partes como function,foo,(,),{,return,5,;,} . Todavía no entiende el código.

Después de eso - se construye un Syntax Tree . Un árbol de sintaxis es más consciente de la gramática, pero sigue siendo completamente sintáctico. Por ejemplo, un árbol de sintaxis vería los tokens de:

function foo(){ return 5;}

Y averiguar "¡Hey! ¡Hay una FunctionExpression aquí!".

Se llama árbol porque es solo eso: los árboles permiten el anidamiento.

Por ejemplo, el código anterior puede producir algo como:

Program FunctionDeclaration (identifier = ''foo'') BlockStatement ReturnStatement Literal (5)

Esto es bastante simple, solo para mostrarle que no siempre es tan lineal, verifiquemos 5 +5 :

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

Tales divisiones pueden ocurrir.

Básicamente, un árbol de sintaxis nos permite expresar la sintaxis.

Aquí es donde x ∘ y fails x ∘ y falla: ve y no entiende la sintaxis.

3. Extendiendo la sintaxis del lenguaje.

Esto solo requiere un proyecto que analiza la sintaxis. Lo que haremos aquí es leer la sintaxis de "nuestro" lenguaje que no es lo mismo que JavaScript (y no cumple con la especificación) y reemplazar a nuestro operador con algo con lo que la sintaxis de JavaScript esté bien.

Lo que vamos a hacer no es JavaScript. No sigue la especificación de JavaScript y una queja estándar JS parser lanzará una excepción en él.

4. Extendiendo la semántica del lenguaje.

Esto lo hacemos todo el tiempo de todos modos :) Todo lo que haremos aquí es simplemente definir una función a la que llamar cuando se llama al operador.

5. ¿Cómo agrego un operador al lenguaje JavaScript?

Permítanme comenzar diciendo después de este prefijo que no agregaremos un operador a JS aquí, más bien, estamos definiendo nuestro propio idioma, llamémoslo "CakeLanguage" o algo así y agregue el operador. Esto se debe a que no es parte de la gramática JS y la gramática JS no permite operadores arbitrarios como otros idiomas .

Usaremos dos proyectos de código abierto para esto:

  • esprima que toma el código JS y genera el árbol de sintaxis para él.
  • escodegen que hace la otra dirección, generando código JS desde el árbol de sintaxis esprima spits.

Si prestara mucha atención, sabría que no podemos utilizar esprima directamente, ya que le daremos una gramática que no comprende.

Agregaremos un operador # que hace x # y === 2x + y para la diversión. Le daremos la prioridad de la multiplicidad (porque los operadores tienen prioridad de operador).

Entonces, después de obtener su copia de Esprima.js, tendremos que cambiar lo siguiente:

Para FnExprTokens , es decir, las expresiones que tendremos que agregar # para que lo reconozca. Después, se vería como tal:

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

Para scanPunctuator lo agregaremos y su código de caracteres como un posible caso: case 0x23: // #

Y luego a la prueba para que se vea como:

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

En lugar de:

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

Y luego a binaryPrecedence vamos a darle la misma prioridad que la multiplicidad:

case ''*'': case ''/'': case ''#'': // put it elsewhere if you want to give it another precedence case ''%'': prec = 11; break;

¡Eso es! Acabamos de ampliar nuestra sintaxis de idioma para admitir el operador # .

No hemos terminado todavía, tenemos que convertirlo de nuevo a JS.

Primero definamos una función corta de visitor para nuestro árbol que recursivamente visita todo su nodo.

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

Esto solo pasa por el árbol generado por Esprima y lo visita. Le pasamos una función y la ejecuta en cada nodo.

Ahora, tratemos a nuestro nuevo operador especial:

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; } } });

Así que en resumen:

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);

Lo último que debemos hacer es definir la función en sí:

function operator_sharp(x,y){ return 2*x + y; }

E incluye eso encima de nuestro código.

¡Eso es todo al respecto! Si lees hasta ahora, te mereces una cookie :)

Aquí está el código en GitHub para que puedas jugar con él.