# 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;
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"
},
...
}
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. */
};
2
3
4
5
6
7
8
# Egg Repeat Evaluate Print Loop
- Véase la sección Construcción de un 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
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
)
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
)
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
2
[~/.../crguezl-egg(private2019)]$ bin/egg.js examples/scope.egg
4
9
8
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/
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
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
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);
});
});
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)
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
{
"egg": "./bin/egg.js",
"evm": "./bin/evm.js"
}
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
Puede encontrar soluciones diferentes (no la solución recomendada en esta práctica) a algunos de los problemas planteados en esta práctica en la rama
master
de este repo ULL-ESIT-PL-1617/egg (opens new window).También puede encontrarlo como módulo en npm @crguezl/eloquentjsegg (opens new window)
Eloquent JS: Chapter 11. Project: A Programming Language (opens new window)
El lenguaje egg: repo en GitHub (opens new window). Contiene un analizador sintáctico PDR y una solución a los problemas de separar el analizador léxico del sintáctico PDR así como al de separar los códigos y los tres ejecutables. También tiene ejemplos de pruebas en Mocha y Chai
NodeJS Readline gist (opens new window): un sencillo gist que te enseña a usar
readline
para hacer un bucle interactivo. Quizá conviene que lo leas cuando llegues a la sección del problema del REPLEn el repo ULL-ESIT-PL-1617/interpreter-egg (opens new window) se muestra como hacer un bucle REPL
Vídeo Programando un bucle REPL para el lenguaje Egg (opens new window)
XRegExp (opens new window): Un módulo que provee regexp extendidas
Tests. Enlaces sobre Mocking and Stubbing
VSCode Extension Egg Tools: Adds syntax highlighting and code snippets for the Egg language by EloquentJS (opens new window)