# 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-2122/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-2122/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 this section 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-2122.

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#

#Labs

  1. Task GitHub-AluXXXX Form
  2. Lab GitHub Campus Expert
  3. Lab GitHub Project Board
  4. Lab GitPod and Visual Studio Code
  5. Lab IAAS
  6. Lab Espree Logging
  7. Lab Hello Compilers
  8. Lab Constant Folding
  9. Lab ast-types
  10. Lab egg-parser
  11. Lab Lexer Generator
  12. Lab The Egg Interpreter
  13. Lab Adding OOP to the Egg Parser
  14. Lab Extending the Egg Interpreter
  15. Lab TFA: Final Project PL
  16. Task Training Exam for PL

Comments#

Last Updated: 9 months ago