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
-
Véase la sección Construcción de un 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 fun
ciones. 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
-
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 in a new tab). -
También puede encontrarlo como módulo en npm @crguezl/eloquentjsegg (opens in a new tab)
-
Eloquent JS: Chapter 11. Project: A Programming Language (opens in a new tab)
-
El lenguaje egg: repo en GitHub (opens in a new tab). 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 in a new tab): 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 REPL -
En el repo ULL-ESIT-PL-1617/interpreter-egg (opens in a new tab) se muestra como hacer un bucle REPL
-
Vídeo Programando un bucle REPL para el lenguaje Egg (opens in a new tab)
-
XRegExp (opens in a new tab): 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 in a new tab)