# 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-2223/lexer-generator-solution");
let lexer = nearleyLexer(tokens);
module.exports = lexer;
1
2
3
4

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-2223/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"
  },
  ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

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. */
};
1
2
3
4
5
6
7
8

# Egg Repeat Evaluate Print Loop

# AST Classes and AST Interpretation

Véase la sección sobre la Estructura de Clases para los ASTs y la Interpretación de los Nodos del AST

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
1
2
3
4
5
6
7

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
)
1
2
3
4
5

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
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

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
1
2
[~/.../crguezl-egg(private2019)]$ bin/egg.js examples/scope.egg
4
9
8
1
2
3
4

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/
1

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
1
2
3
4
5

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 new window) para simplificar esta metodología

[~/.../test(private2019)]$ cat scopes.js
1
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);
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

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)
1
2
3
4
5

# 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-2223.

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

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

# 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.

# Recursos

Grading Rubric#

Comments#

Last Updated: 2 months ago