Esprima/Espree Examples

Esprima/Espree Examples

En el Repo ULL-ESIT-GRADOII-PL/esprima-pegjs-jsconfeu-talk encontrará el material de esta lección. Clone este repo.

The examples in this repo use a couple of JavaScript compiler frameworks: Esprima and Espree.

Espree started out as a fork of Esprima v1.2.2, the last stable published released of Esprima before work on ECMAScript 6 began. Espree is now built on top of Acorn, which has a modular architecture that allows extension of core functionality. The goal of Espree is to produce output that is similar to Esprima with a similar API so that it can be used in place of Esprima.

REPL example

Una vez clonado el repo ULL-ESIT-GRADOII-PL/esprima-pegjs-jsconfeu-talk, instalamos las dependencias:

➜  esprima-pegjs-jsconfeu-talk git:(master) npm i

y arrancamos el bucle REPL de Node.JS:

➜  esprima-pegjs-jsconfeu-talk git:(master) node
Welcome to Node.js v14.4.0.
Type ".help" for more information.

Cargamos espree:

> const espree = require('espree')
undefined
> espree.version
'7.3.1'
> espree.latestEcmaVersion
12
> espree.supportedEcmaVersions
[
  3,  5,  6,  7, 8,
  9, 10, 11, 12
]

Navegar en el árbol AST es complicado. El atributo espree.visitorKeys nos proporciona la lista de nodos y los nombres de los atributos de sus hijos

Hagamos un análisis léxico:

> espree.tokenize('answer = /* comment*/ 42', { range: true })
[
  Token {
    type: 'Identifier',
    value: 'answer',
    start: 0,
    end: 6,
    range: [ 0, 6 ]
  },
  Token {
    type: 'Punctuator',
    value: '=',
    start: 7,
    end: 8,
    range: [ 7, 8 ]
  },
  Token {
    type: 'Numeric',
    value: '42',
    start: 22,
    end: 24,
    range: [ 22, 24 ]
  }
]

Hagamos ahora un análisis sintáctico:

> espree.parse('const answer = 42', { tokens: true })
Uncaught [SyntaxError: The keyword 'const' is reserved
] {
  index: 0,
  lineNumber: 1,
  column: 1
}

La versión ECMA de JS usada por defecto por espree es la 5 y esta no admite const

Especifiquemos la versión ECMA que queremos:

> espree.parse('const answer = 42', 
              { ecmaVersion: espree.latestEcmaVersion, 
                tokens: true }
              )
Node {
  type: 'Program',
  start: 0,
  end: 17,
  body: [
    Node {
      type: 'VariableDeclaration',
      start: 0,
      end: 17,
      declarations: [Array],
      kind: 'const'
    }
  ],
  sourceType: 'script',
  tokens: [
    Token { type: 'Keyword', value: 'const', start: 0, end: 5 },
    Token { type: 'Identifier', value: 'answer', start: 6, end: 12 },
    Token { type: 'Punctuator', value: '=', start: 13, end: 14 },
    Token { type: 'Numeric', value: '42', start: 15, end: 17 }
  ]
}

Observe que el Árbol no aparece completo. El log que usa el bucle REPL de Node lo trunca en el hijo declarations (sólo nos muestra que es un array [Array] sin expandirlo) para que la salida no sea excesivamente larga.

Para que nos muestre el árbol vamos a usar el método util.inspect del módulo util que convierte un objeto en una string:

> const util = require('util')
undefined
> console.log(
    util.inspect(
        espree.parse('const answer = 42',{ecmaVersion: 6}), 
        {depth: null}
    )
 )
Node {
  type: 'Program',
  start: 0,
  end: 17,
  body: [
    Node {
      type: 'VariableDeclaration',
      start: 0,
      end: 17,
      declarations: [
        Node {
          type: 'VariableDeclarator',
          start: 6,
          end: 17,
          id: Node {
            type: 'Identifier',
            start: 6,
            end: 12,
            name: 'answer'
          },
          init: Node {
            type: 'Literal',
            start: 15,
            end: 17,
            value: 42,
            raw: '42'
          }
        }
      ],
      kind: 'const'
    }
  ],
  sourceType: 'script'
}
undefined

Usando la herramienta web https://astexplorer.net podemos navegar el AST producido por varios compiladores JS:

Example: Searching for Specific Identifiers

The file idgrep.js is a very simple example of using Esprima to do static analysis on JavaScript code.

It provides a function idgrep that finds the appearances of identifiers matching a search string inside the input code.

Given an input like this:

➜  esprima-pegjs-jsconfeu-talk git:(master) cat hacky.js 
// This is a hack!
const hacky = () => {
    let hack = 3;
    return 'hacky string';
}

if we search for hack it produces:

➜  esprima-pegjs-jsconfeu-talk git:(master) ./idgrep.js hacky.js
1:6: const hacky = () => {
2:8:     let hack = 3;

Observe how the appearances of hack inside the comment or the string aren’t shown

Práctica Espree Logging

ASTExplorer

Master the Art of the AST

Syntax Analysis: PEG.js Example

altjs.js is the code for the “AltJS language in 5 minutes” section presented in the second half of the talk Parsing, Compiling, and Static Metaprogramming by Patrick Dubroy

References