Egg Interpreter

The Egg Interpreter

Modificaciones en el módulo de Análisis Sintáctico

Para facilitar la labor de hacer esta práctica es conveniente que volvamos al módulo egg-parser y modifiquemos un poco su API para la fabricación del intérprete que vamos a construir.

A estas alturas del curso, el módulo que escribimos en la práctica egg-parser debería hacer uso del generador de analizadores léxicos realizado en la práctica lexer generator.

El analizador léxico de nuestro parser debería ser algo así:

const { tokens } = require('./tokens.js');
const { nearleyLexer } = require("@ull-esit-pl-2324/lexer-generator-solution");
let lexer = nearleyLexer(tokens);
module.exports = lexer;

Además del ejecutable eggc que ya proveía el paquete egg-parser, ahora le añadiremos un fichero src/parse.js que constituirá el punto de entrada o main del módulo. Sigue un extracto de como podría ser el package.json:

{
  "name": "@ull-esit-pl-2324/egg-parser-solution",
  "version": "1.0.3",
  "description": "Lab for language processors",
  "main": "src/parse.js",
  "bin": {
    "eggc": "bin/eggc.js"
  },
  "publishConfig": {
    "registry": "https://npm.pkg.github.com",
    "access": "restricted"
  },
  ...
}

El fichero parse.js exportará un conjunto de funciones y objetos que facilitarán las labores de parsing desde el módulo de esta práctica. Entre ellas estas:

module.exports = {
  lex,  // El analizador léxico
  parse,
  parseFromFile,
  parBalance, 
  SPACE // La expresión regular para blancos y comentarios
  /* ... etc. */
};

Egg Repeat Evaluate Print Loop

AST Classes and AST Interpretation

Get familiar with the anatomy of the Eggs ASTs.

See the section on Class Structure for ASTs and Interpretation of AST Nodes

Have a look at section Code Smells if you want to know more about Smells, the Open-Closed Principle and the The Strategy Pattern

Providing an Assignment

See Interpretation of Assignment Expressions

Function Interpretation

See section Function Interpretation

Ejecutables

El programa egg deberá ejecutar el programa .egg que se le pasa por línea de comandos. El intérprete evm ejecuta los ficheros json generados por eggc

Examples folder

Añada una carpeta examples en la que guardará los ejemplos con los que va comprobando la funcionalidad de su compilador:

[~/.../crguezl-egg(master)]$ tree examples/ -I '*evm'
examples/
├── array.egg
├── greater-x-5.egg
├── if.egg
├── ...
└── two.egg

Cada vez que introduzca una nueva funcionalidad cree uno o varios ejemplos que sirvan para ilustrarla y comprobar el buen funcionamiento.

Por ejemplo, cuando trabajemos en la tarea Fixing Scope podemos añadir a nuestro directorio examples un par de ejemplos que ilustran como debería funcionar.

Uno que produzca una excepción:

[~/.../crguezl-egg(private2019)]$ cat examples/scope-err.egg
do(
  set(x,9),
  print(x) # ReferenceError: Tried setting an undefined variable: x
)

y al menos otro que muestre como unas variables ocultan a otras:

[~/.../crguezl-egg(private2019)]$ cat examples/scope.egg
do(
  def(x,9),
  /* def crea una nueva variable local */
  def(f, fun{
    do{
      def(x, 4),
      print(x) # 4
    }
  }),
  /* set no crea una nueva variable local */
  def(g, fun{set(x, 8)}),
  f(),
  print(x), # 9
  g(),
  print(x) # 8
)

Conforme programamos, vamos ejecutando nuestra solución contra estos programas. Cuando tengamos la solución correcta la salida debería ser algo así:

[~/.../crguezl-egg(private2019)]$ bin/egg.js examples/scope-err.egg
ReferenceError: Tried setting an undefined variable: x
[~/.../crguezl-egg(private2019)]$ bin/egg.js examples/scope.egg
4
9
8

Uno de nuestros objetivos es reciclar esos ejemplos en las pruebas y en la documentación.

Test Folder

Añadimos una carpeta test y en ella los programas de prueba test/test.js (Mocha o Jest, use lo que prefiera. Los ejemplos que siguen están en Mocha).

Creamos también un subdirectorio test/examples en el que copiamos nuestro ejemplo de prueba:

cp examples/scope.egg test/examples/

y junto a el escribimos un fichero con la salida esperada test/examples/scope.egg.expected.

Una estructura como esta:

test/
├── examples
│   ├── scope.egg
│   └── scope.egg.expected
└── test.js

Cada vez que logramos implementar una nueva funcionalidad o un nuevo objetivo añadimos en el directorio examples uno o varios programas examples/objetivo.egg cuya ejecución muestra el buen funcionamiento de nuestro código. También lo añadimos a test/examples/objetivo.egg así como la salida esperada test/examples/objetivo.egg.expected.

De esta forma la prueba se reduce a comprobar que la salida (stdout/stderr) de la ejecución del programa test/examples/objetivo.egg es igual a los contenidos de test/examples/objetivo.egg.expected.

Procura evitar que la salida sea dependiente de la versión de node.js o del Sistema Operativo.

Puede usar el módulo @ull-esit-pl/example2test (opens in a new tab) para simplificar esta metodología

[~/.../test(private2019)]$ cat scopes.js
let fs = require('fs');
let should = require("should");
let e2t = require('@ull-esit-pl/example2test');
let eggvm = require('../lib/eggvm.js');
 
describe("Testing scopes", function() {
  let runTest = (programName, done) => {
    e2t({
      exampleInput: programName + '.egg',
      executable: 'node bin/egg.js',
      assertion: (result, expected) => result.replace(/\s+/g,'').should.eql(expected.replace(/\s+/g,'')),
      done: done,
    });
  };
 
  it("should  not allow the use of non declared variables", function() {
    let program = fs.readFileSync('examples/scope-err.egg', 'utf8');
    (() => { eggvm.run(program); }).should.throw(/setting.+undefined.+variable/i);
  });
 
  it("testing scope.egg", function(done) {
    runTest('scope', done);
  });
});

Como se puede apreciar, el objeto eggvm exportado por el módulo lib/eggvm.js dispone de un método run que ejecuta la cadena que se le pasa como entrada.

No olvides ejecutar todas las pruebas npm test cada vez que resuelves un nuevo objetivo

[~/.../crguezl-egg(private2019)]$ npx mocha test/scopes.js
  Testing scopes
    ✓ should  not allow the use of non declared variables
    ✓ testing scope.egg (138ms)
  2 passing (151ms)

Continuous Integration

Use GitHub Actions para añadir CI al proyecto

To install Private Packages inside a GitHub Action review these sections:

GitHub Registry

Publique el compilador como módulo en GH Registry en el ámbito @ull-esit-pl-2324.

Puesto que este paquete contiene ejecutables es conveniente que lea la sección bin (opens in a new tab) de la documentación de npm.js sobre package.json:

[~/.../crguezl-egg(master)]$ jq .bin package.json
{
  "egg": "./bin/egg.js",
  "evm": "./bin/evm.js"
}

Fundamentos

Esta es la segunda de una serie de prácticas sobre el lenguaje Egg. Lea el capítulo 12 "A Programming Language" del libro EJS:

Salte la sección Parsing de ese capítulo y pase directamente a la sección The Evaluator.

Videos

Clase del 2024/04/17

The leftEvaluate method for apply nodes:


Clase del 2024/04/16

El intérprete Egg: Repaso de la interpretación de los nodos value, word y apply. Interpretación de las funciones. La sentencia de asignación.


2024/04/15 lecture

Interpreting the ASTs of Egg: Values, Word and Apply nodes. Meta functions and functions. The memory hierarchy.


Clase del 2024/04/10

Modifying the parser to use our lexer-generator instead of moo-ignore. A REPL for Egg. Interpretation of the ASTs of Egg: Values and Word nodes.


Clase 2023/04/19

Interpretación de los Nodos del AST de Egg. Interpretation of Assignment Expressions Function Interpretation.


Building a Repeat Evaluate Print Loop

Vídeo Programando un bucle REPL para el lenguaje Egg (opens in a new tab)


Rubric
















egg-interpreter Repos

References