# Translating Arithmetic Expressions to JavaScript
# Objetivos
Usando Jison generalice el ejemplo en la carpeta ast
del Repo hello-jison (opens new window) constituido por los ficheros:
- minus-ast.jison (opens new window)
- ast-build.js (opens new window)
- minus-ast.l (opens new window)
- ast2js.js (opens new window)
para escribir un programa que, recibiendo como entrada una cadena que contiene una secuencia de expresiones aritméticas de enteros separados por comas, produzca un AST compatible con el del parser espree (opens new window). Use escodegen (opens new window) para generar el código JavaScript correspondiente (vea ast2js.js (opens new window)). Sigue un ejemplo de una posible ejecución:
✗ cat test/data/test1.calc
4 - 2 - 1
✗ bin/calc2js.mjs test/data/test1.calc
console.log(4 - 2 - 1);
✗ bin/calc2js.js test/data/test1.calc | node -
1
2
3
4
5
6
Las expresiones aritméticas deben soportar además de suma, resta, multiplicación y división el menos unario -(2+3)*2
un operador de factorial !
, un operador de potencia **
y paréntesis. Ejemplo: -(2 + 3) * 4! - 5 ** 6
.
# Opciones en línea de comandos
Use commander (opens new window) para procesar la línea de argumentos:
$ bin/calc2js.mjs --help
Usage: calc2js [options] <filename>
Arguments:
filename file with the original code
Options:
-V, --version output the version number
-o, --output <filename> file in which to write the output
-h, --help display help for command
2
3
4
5
6
7
8
9
10
# Dealing with Ambiguity
Para tratar con los temas de ambigüedad en la gramática, puede consultar
- la sección Precedencia y Asociatividad (opens new window) de los viejos apuntes de PL
- la sección Dealing with ambiguity de la práctica hello-compiler y aplíque los conceptos aprendidos a este proyecto.
# Unary minus and exponentiation in escodegen
Exponentiation is ECMAScript 2016
New Features in ECMAScript 2016: JavaScript Exponentiation (**) See for instance: https://www.w3schools.com/js/js_2016.asp (opens new window) and https://262.ecma-international.org/7.0/ (opens new window)
... This specification also includes support for a new exponentiation operator and adds a new method to Array.prototype called includes
Since escodegen does not support this Feature the combination of unary minus and exponentiation currently does not work.
➜ arith2js-parallel-computing-group-parallel git:(essay-2023-02-15-miercoles) ✗ node
Welcome to Node.js v18.8.0.
Type ".help" for more information.
> let espree = require('espree')
> let ast = espree.parse('(-2)**2') // gives an error since the default compiler is ecma 5
Uncaught [SyntaxError: Unexpected token *
> let ast3 = espree.parse('(-2)**2', { ecmaVersion: 7}) // no errors. Right AST
> let escodegen = require('escodegen')
> escodegen.generate(ast3) // escodegen does not support this feature
'-2 ** 2;' // the code generated is wrong
> let gc = escodegen.generate(ast3)
> gc
'-2 ** 2;'
> eval(gc) // the evaluation of the code produces errors
Uncaught:
SyntaxError: Unary operator used immediately before exponentiation expression. Parenthesis must be used to disambiguate operator precedence
> -2 ** 2 # JS does not accept this expression
-2 ** 2
^^^^^
Uncaught:
SyntaxError: Unary operator used immediately before exponentiation expression. Parenthesis must be used to disambiguate operator precedence
> (-2) ** 2 # ... But this code works
4
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Therefore, the following continuation of the former node session suggest the correct translation:
Use Math.pow
> let ast5 = espree.parse('Math.pow(-2,2)')
undefined
> gc = escodegen.generate(ast5)
'Math.pow(-2, 2);'
> eval(gc)
4
2
3
4
5
6
# Pruebas
Añada pruebas a este proyecto usando mocha
# Mocking
Mocking means creating a fake version of an external or internal service that can stand in for the real one, helping your tests run more quickly and more reliably. When your implementation interacts with an object’s properties, rather than its function or behavior, a mock can be used.
# Stubbing
Stubbing, like mocking, means creating a stand-in, but a stub only mocks the behavior, but not the entire object. This is used when your implementation only interacts with a certain behavior of the object.
To give an example: You can stub a database by implementing a simple in-memory structure for storing records. The object under test can then read and write records to the database stub to allow it to execute the test. This could test some behaviour of the object not related to the database and the database stub would be included just to let the test run.
If you instead want to verify that the object under test writes some specific data to the database you will have to mock the database. Your test would then incorporate assertions about what was written to the database mock.
# Ejemplos
See the code at ast/test/test.mjs (opens new window) in the repo hello-jison for an example of stubbing the console.log
.
Cuando vaya a escribir las pruebas de la práctica Hello Compilers podemos intentar una aproximación como esta:
- Tomamos un objeto como
c = { text: "2@1&3", result: 2 }
con el atributotext
conteniendo la expresión de prueba y el atributoresult
el resultado esperado después de la traducción y evaluación del código - Construimos primero el árbol con
t = p.parse(c.text)
- Generamos el JavaScript con
js = escodegen.generate(t)
- Evaluamos el JavaScript con
result = eval(js)
- Si nuestro traductor es correcto
result
debería ser igualc.result
Suena bien ¿Verdad?
Pero en tal aproximación ¡tenemos un problema! y es que el código JavaScript generado para "2@1&3"
nos han pedido que sea:
➜ hello-compilers-solution git:(master) ✗ ./bin/mmt.js '2@1&3'
console.log(Math.max(2, Math.min(1, 3)));
2
y si evaluamos el código resultante:
➜ hello-compilers-solution git:(master) ✗ node
Welcome to Node.js v16.0.0.
> result = eval("console.log(Math.max(2, Math.min(1, 3)));")
2
undefined
> result
undefined
2
3
4
5
6
7
¡La variable result
está undefined
!
Esto es así porque la llamada a console.log()
siempre retorna undefined
(no se confunda por el 2
que aparece en stdout
producido por el console.log
. El valor retornado es undefined
)
Así pues una aproximación como esta no funcionaría:
const p = require("../src/maxmin.js").parser;
const escodegen = require("escodegen");
require("chai").should();
const Checks = [
{ text: "2@1&3", result: 2 },
{ text: "2@1@3", result: 3 },
{ text: "2&1&3", result: 1 },
{ text: "2&1@3", result: 3 },
{ text: "2&(1@3)", result: 2 },
];
describe("Testing hello maxmin translator", () => {
for (let c of Checks) {
it(`Test ${c.text} = ${c.result}`, () => {
const t = p.parse(c.text);
const js = escodegen.generate(t);
const result = eval(js);
result.should.equal(c.result);
console.log = oldLog;
});
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
No funcionaría porque lo que queda en result
es undefined
y no el resultado de Math.max(2, Math.min(1, 3))
.
En efecto, al ejecutar las pruebas obtenemos:
1) Testing hello maxmin translator
Test 2@1&3 = 2:
TypeError: Cannot read property 'should' of undefined
2
3
¿Cómo arreglarlo?
¡El patrón de Stubbing al rescate!
Sustituyamos el método log
del objeto console
con nuestra propia función adaptada a nuestras necesidades de testing console.log = x => x;
que retorna el valor del argumento pasado a console.log
. De esta forma podemos acceder al valor de la evaluación de la expresión:
it(`Test ${c.text} = ${c.result}`, () => {
let oldLog = console.log;
console.log = x => x;
const t = p.parse(c.text);
const js = escodegen.generate(t);
const result = eval(js);
result.should.equal(c.result);
console.log = oldLog;
});
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
Ahora result
contiene la evaluación de la expresión y las pruebas funcionan:
➜ hello-compilers-solution git:(master) ✗ npm test
> hello-compilers@1.0.1 test
> npm run build; mocha
> hello-compilers@1.0.1 build
> jison src/maxmin-ast.jison src/maxmin.l -o src/maxmin.js
Testing hello maxmin translator
✔ Test 2@1&3 = 2
✔ Test 2@1@3 = 3
✔ Test 2&1&3 = 1
✔ Test 2&1@3 = 3
✔ Test 2&(1@3) = 2
5 passing (9ms)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Covering
You can use nyc (opens new window) to do the covering of your mocha tests. See the notes in covering.
Activate the GitHub pages of your repo (use the default branch and the docs
folder) and be sure to include your covering report in the docs
folder.
✗ npm run cov
> hello-jison@1.0.0 cov
> nyc npm test
> hello-jison@1.0.0 test
> npm run compile; mocha test/test.mjs
> hello-jison@1.0.0 compile
> jison src/grammar.jison src/lexer.l -o src/calc.js
✔ transpile(test1.calc, out1.js)
✔ transpile(test2.calc, out2.js)
✔ transpile(test3.calc, out3.js)
3 passing (20ms)
--------------|---------|----------|---------|---------|-----------------------------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------|---------|----------|---------|---------|-----------------------------------------
All files | 58.92 | 45.83 | 47.22 | 56.63 |
ast-build.js | 100 | 100 | 100 | 100 |
calc.js | 57.44 | 45.78 | 40.62 | 54.92 | ...,530-539,548-569,578,580,602-607,610
transpile.js | 81.81 | 50 | 100 | 81.81 | 11-12
--------------|---------|----------|---------|---------|-----------------------------------------
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# References
# Essentials for this lab
- See the examples in the repo crguezl/hello-jison (opens new window)
- https://astexplorer.net (opens new window)
- Tipos de Nodos del AST y nombres de las propiedades de los hijos
- Escodegen repo en GitHub (opens new window)
- Jison Documentation (opens new window)
# Jison and Syntax Analysis
- Análisis Sintáctico Ascendente en JavaScript (opens new window)
- Jison
- Mi primer proyecto utilizando Jison (opens new window) por Erick Navarro
- Folder jison/examples from the Jison distribution (opens new window)
- Jison Debugger (opens new window)
- Precedencia y Asociatividad (opens new window)
- Construcción de las Tablas para el Análisis SLR (opens new window)
- Algoritmo de Análisis LR (yacc/bison/jison) (opens new window)
- Repo ULL-ESIT-PL-1718/jison-aSb (opens new window)
- Repo ULL-ESIT-PL-1718/ull-etsii-grado-pl-jisoncalc (opens new window)
- Leveling Up One’s Parsing Game With ASTs by Vaidehi Joshi 👍