Hoy, publicamos la segunda parte de una traducción de la extensión de sintaxis de JavaScript con Babel.

→
Primera parte vertiginosa
Cómo funciona el análisis
El analizador recibe una lista de tokens del sistema de tokenización de código y, considerando los tokens uno por uno, crea un AST. Para tomar una decisión sobre cómo usar los tokens y comprender qué token se puede esperar a continuación, el analizador se refiere a la especificación de la gramática del lenguaje.
La especificación gramatical se parece a esto:
... ExponentiationExpression -> UnaryExpression UpdateExpression ** ExponentiationExpression MultiplicativeExpression -> ExponentiationExpression MultiplicativeExpression ("*" or "/" or "%") ExponentiationExpression AdditiveExpression -> MultiplicativeExpression AdditiveExpression + MultiplicativeExpression AdditiveExpression - MultiplicativeExpression ...
Describe la prioridad de ejecutar expresiones o declaraciones. Por ejemplo, una expresión
AdditiveExpression
puede representar una de las siguientes construcciones:
- Expresión Expresión
MultiplicativeExpression
. - Una expresión
AdditiveExpression
, seguida de un operador de token +
, seguido de una expresión MultiplicativeExpression
. - Una expresión
AdditiveExpression
, seguida de un token " -
", seguido de una expresión MultiplicativeExpression
.
Como resultado, si tenemos la expresión
1 + 2 * 3
, entonces se verá así:
(AdditiveExpression "+" 1 (MultiplicativeExpression "*" 2 3))
Pero no será así:
(MultiplicativeExpression "*" (AdditiveExpression "+" 1 2) 3)
El programa, usando estas reglas, se convierte en código emitido por el analizador:
class Parser {
Tenga en cuenta que aquí hay una versión extremadamente simplificada de lo que realmente está presente en Babel. Pero espero que este código nos permita ilustrar la esencia de lo que está sucediendo.
Como puede ver, el analizador es, por naturaleza, recursivo. Se mueve desde los diseños de menor prioridad a los diseños de mayor prioridad. Por ejemplo,
parseAdditiveExpression
llama a
parseMultiplicativeExpression
, y esta construcción llama a
parseExponentiationExpression
y así sucesivamente. Este proceso recursivo se llama
análisis de descenso recursivo .
Funciones this.eat, this.match, this.next
Es posible que haya notado que en los ejemplos anteriores se utilizaron algunas funciones auxiliares, como
this.eat
,
this.match
,
this.next
y otras. Estas son las funciones internas del analizador de Babel. Sin embargo, estas funciones no son exclusivas de Babel; generalmente están presentes en otros analizadores.
- La función
this.match
devuelve un valor booleano que indica si el token actual cumple la condición especificada. - La función
this.next
en la lista de tokens al siguiente token. - La función
this.eat
devuelve lo mismo que la función this.match
, y si this.match
devuelve true
, this.eat
realiza, antes de devolver true
, una llamada a this.next
. - La función
this.lookahead
permite obtener el siguiente token sin avanzar, lo que ayuda a tomar una decisión sobre el nodo actual.
Si vuelve a mirar el código del analizador que cambiamos, encontrará que leerlo se ha vuelto mucho más fácil:
packages/babel-parser/src/parser/statement.js export default class StatementParser extends ExpressionParser { parseStatementContent() {
Sé que no profundicé en explicar las características de los analizadores. Por lo tanto,
aquí y
allá , un par de recursos útiles sobre este tema. Aprendí muchos de ellos y puedo recomendarlos.
Es posible que le interese saber cómo pude visualizar la sintaxis que creé en Babel AST Explorer cuando mostré el nuevo atributo "
curry
" que apareció en AST.
Esto fue posible debido al hecho de que agregué una nueva función en Babel AST Explorer que le permite cargar su propio analizador en esta herramienta de investigación AST.
Si sigue los
packages/babel-parser/lib
ruta
packages/babel-parser/lib
, puede encontrar una versión compilada del analizador y un mapa de código. En el panel
Babel AST Explorer
, puede ver el botón para cargar su propio analizador. Al descargar
packages/babel-parser/lib/index.js
puede visualizar el AST generado utilizando su propio analizador.
Visualización ASTNuestro complemento para Babel
Ahora que el analizador está completo, escribamos un complemento para Babel.
Pero, tal vez ahora tenga algunas dudas sobre cómo exactamente vamos a usar nuestro propio analizador de Babel, especialmente teniendo en cuenta qué pila de tecnología usamos para construir el proyecto.
Es cierto, no hay nada que temer. El complemento Babel puede proporcionar capacidades de analizador. La
documentación relacionada se puede encontrar en el sitio web de Babel.
babel-plugin-transformation-curry-function.js import customParser from './custom-parser'; export default function ourBabelPlugin() { return { parserOverride(code, opts) { return customParser.parse(code, opts); }, }; }
Dado que creamos una bifurcación del analizador Babel, esto significa que todas las funciones del analizador existentes, así como los complementos integrados, continuarán funcionando completamente bien.
Después de deshacernos de estas dudas, echemos un vistazo a cómo hacer una función de modo que admita curry.
Si no pudo soportar las expectativas y ya trató de agregar nuestro complemento al sistema de construcción de su proyecto, es posible que observe que las funciones que admiten el currículum se compilan en funciones regulares.
Esto sucede porque, después de analizar y transformar el código, Babel usa
@babel/generator
para generar código a partir del AST transformado. Como
@babel/generator
no sabe nada sobre el nuevo atributo
curry
, simplemente lo ignora.
Si algún día las funciones que admiten curry entran en el estándar de JavaScript, es posible que desee hacer un PR para agregar un nuevo código
aquí .
Para hacer que la función soporte el curry, puede envolverlo en un
currying
función de orden superior:
function currying(fn) { const numParamsRequired = fn.length; function curryFactory(params) { return function (...args) { const newParams = params.concat(args); if (newParams.length >= numParamsRequired) { return fn(...newParams); } return curryFactory(newParams); } } return curryFactory([]); }
Si está interesado en las características de la implementación del mecanismo de funciones de curry en JS, eche un vistazo a
este material.
Como resultado, nosotros, transformando una función que admite curry, podemos hacer esto:
Por ahora, no prestaremos atención al mecanismo de
aumento de funciones en JavaScript, que le permite llamar a la función
foo
antes de que se defina.
Así es como se ve el código de transformación:
babel-plugin-transformation-curry-function.js export default function ourBabelPlugin() { return {
Te resultará mucho más fácil descubrirlo si lees
este material sobre las transformaciones en Babel.
Ahora nos enfrentamos a la cuestión de cómo proporcionar a este mecanismo acceso a la función de
currying
. Aquí puede usar uno de dos enfoques.
▍ Enfoque n. ° 1: se puede suponer que la función de curry se declara en el ámbito global
Si es así, entonces el trabajo ya está hecho.
Si, al ejecutar el código compilado, resulta que la función de
currying
no está definida, entonces nos encontraremos con un mensaje de error que parece "
currying is not defined
". Es muy similar al mensaje "
regeneratorRuntime no está definido ".
Por lo tanto, si alguien usa su
babel-plugin-transformation-curry-function
, es posible que deba informarle que necesita instalar el polyfill
currying
para asegurarse de que este complemento funcione correctamente.
▍ Enfoque # 2: puedes usar babel / helpers
Puede agregar una nueva función auxiliar a
@babel/helpers
. Es poco probable que este desarrollo se combine con el
@babel/helpers
oficial
@babel/helpers
. Como resultado, tendrá que encontrar una manera de mostrar a
@babel/core
la ubicación de su
@babel/helpers
:
package.json { "resolutions": { "@babel/helpers": "7.6.0--your-custom-forked-version", }
No lo he intentado yo mismo, pero creo que este mecanismo funcionará. Si lo prueba y tiene problemas, lo
discutiré con gusto.
@babel/helpers
nueva función auxiliar a
@babel/helpers
muy simple.
Primero, vaya al
paquete / babel-helpers / src / helpers.js y agregue una nueva entrada:
helpers.currying = helper("7.6.0")` export default function currying(fn) { const numParamsRequired = fn.length; function curryFactory(params) { return function (...args) { const newParams = params.concat(args); if (newParams.length >= numParamsRequired) { return fn(...newParams); } return curryFactory(newParams); } } return curryFactory([]); } `;
Al describir una función auxiliar,
@babel/core
indica la versión requerida
@babel/core
. Algunas dificultades aquí pueden ser causadas por el
export default
la función de
currying
.
Para usar una función auxiliar, simplemente llame a
this.addHelper()
:
El comando
this.addHelper
, si es necesario, incorporará la función auxiliar en la parte superior del archivo y devolverá un
Identifier
indica la función implementada.
Notas
He estado involucrado en trabajar en Babel durante bastante tiempo, pero aún no he tenido que agregar funciones para admitir la nueva sintaxis de JavaScript en el analizador. Principalmente trabajé en corregir errores y mejorar lo que es relevante para las características oficiales del idioma.
Sin embargo, desde hace algún tiempo estaba ocupado con la idea de agregar nuevas construcciones de sintaxis al lenguaje. Como resultado, decidí escribir material al respecto y probarlo. Es increíblemente agradable ver que todo funciona exactamente como se esperaba.
La capacidad de controlar la sintaxis del lenguaje que usa es una poderosa fuente de inspiración. Esto hace posible, al implementar algunas construcciones complejas, escribir menos código o escribir código más simple que antes. Los mecanismos para transformar código simple en construcciones complejas se automatizan y transfieren a la etapa de compilación. Esto es una reminiscencia de cómo
async/await
resuelve los problemas de devoluciones de llamadas infernales y largas cadenas de promesas.
Resumen
Aquí hablamos sobre cómo modificar las capacidades del analizador de Babel, escribimos nuestro propio complemento de transformación de código, hablamos brevemente sobre
@babel/generator
y sobre la creación de funciones auxiliares usando
@babel/helpers
. La información sobre la transformación del código se proporciona solo esquemáticamente. Lea más sobre ellos
aquí .
En el proceso, tocamos algunas características de los analizadores. Si está interesado en este tema, entonces,
aquí y
allá , recursos que son útiles para usted.
La secuencia de acciones que realizamos es muy similar a parte del proceso que ocurre cuando TC39 recibe una nueva característica de JavaScript.
Aquí está la página del repositorio TC39 donde puede encontrar información sobre las ofertas actuales.
Aquí puede encontrar información más detallada sobre cómo trabajar con ofertas similares. Al proponer una nueva función de JavaScript, quien la ofrece generalmente escribe polyfills o, al bifurcar a Babel, prepara una demostración que prueba que la oración funciona. Como puede ver, crear una bifurcación de un analizador sintáctico o escribir un polyfill no es la parte más difícil del proceso de proponer nuevas características de JS. Es difícil determinar el área temática de innovación, planificar y pensar las opciones para su uso y casos límite; Es difícil reunir las opiniones y sugerencias de los miembros de la comunidad de programadores de JavaScript. Por lo tanto, me gustaría expresar mi gratitud a todos aquellos que encuentran la fuerza para ofrecer las nuevas funciones de JavaScript TC39, desarrollando así este lenguaje.
Aquí hay una página en GitHub que le permitirá ver el panorama general de lo que hicimos aquí.
Estimados lectores! ¿Alguna vez has querido ampliar la sintaxis de JavaScript?
