@kiliankgr/functions
v1.0.0
Published
A lab for PL. Intro to Scope Analysis
Downloads
6
Readme
Lab Add Functions and extend scope analysis for the complex calculator
See Lab Functions
Ejecucion
- Para la ejecución del programa primero descargaremos las dependencias $ npm i o $ npm ci
- Luego compilaremos el programa $ npm run compile
- Tambien podemos compilar y probar los test predefinidos $ npm run test
- Para más información consulte $ npm run help
- Si se quisiera ejecutar con un fichero en particular $ ./bin/calc2js.mjs <nombre_test> [-o <fichero_salida>]
Analizador lexico
Debemos de modificar el analizador lexico para que pueda trabajar con números complejos, así como con palabras reservadas como print, o, identificadores.
%{
const reservedWords = ["fun", "true", "false", "i"]
const predefinedIds = ["print","write"]
const idOrReserved = text => {/* fill the code */
if (reservedWords.find(w => w == text)) return text.toUpperCase();
if (predefinedIds.find(w => w == text)) return 'PID';
return 'ID';
}
%}
number (\d+(\.?\d+)?([eE][-+]?\d+)?"i"?) | "i" //numero, numero flotante, numero elevado a, numero complejo
%%
\s+ /* skip whites */;
"#".* /* skip comments */;
\/\*(.|\n)*?\*\/ /* skip multiline comments*/;
{number} return 'N';
[a-zA-Z_]\w* return idOrReserved(yytext);
'**' return '**'; //power
'==' return '=='; //equal
'&&' return '&&'; // logical and
'||' return '||'; // logical or
[-=+*/!(),<>@&{}\[\]] return yytext;
Gramatica
%left ','
%right '='
%left '||' '&&'
%left '=='
%right '<'
%left '@'
%left '&'
%left '-' '+'
%left '*' '/'
%nonassoc UMINUS
%right '**'
%left '!'
%%
es: e { return { ast: buildRoot($e) }; }
;
e:
/* rules for assignment, comma, print, ids */
e ',' e { $$ = buildSequenceExpression([$e1, $e2]); }
| ID '=' e { $$ = buildAssignmentExpression($($1), '=', $e);}
| e '==' e { $$ = buildCallMemberExpression($e1, 'equals', [$e2])}
//Reglas de operadores lógicos and or not, true false
| e '&&' e { $$ = buildLogicalExpression($e1, '&&', $e2);}
| e '||' e { $$ = buildLogicalExpression($e1, '||', $e2);}
| '!' e { $$ = buildUnaryExpression('!', $e); }
| TRUE { $$ = buildIdentifier("true");}
| FALSE { $$ = buildIdentifier("false");}
| e '<' e { $$ = buildCallMemberExpression($e1, 'lessThan', [$e2]); } // prueba de mi complex y monkey patch
//Reglas max min
| e '@' e { $$ = buildMax($e1, $e2, true); }
| e '&' e { $$ = buildMin($e1, $e2, true); }
//mod
| '<' '(' e ')' { $$ = buildCallMemberExpression($3, 'arg', []); } //modificacion
//Operaciones
| e '-' e { $$ = buildCallMemberExpression($e1, 'sub', [$e2]); }
| e '+' e { $$ = buildCallMemberExpression($e1, 'add', [$e2]); }
| e '*' e { $$ = buildCallMemberExpression($e1, 'mul', [$e2]); }
| e '/' e { $$ = buildCallMemberExpression($e1, 'div', [$e2]); }
| e '**' e { $$ = buildCallMemberExpression($e1, 'pow', [$e2]); }
| '(' e ')' { $$ = $2; }
| '-' e %prec UMINUS { $$ = buildCallMemberExpression($e, 'neg', []); }
| e '!' { $$ = buildCallExpression('factorial', [$e], true); }
| N { $$ = buildCallExpression('Complex',[buildLiteral($N)], true); }
// Llamadas o declaraciones a funcion
| FUN '(' idOrEmpty ')' '{' e '}' { $$ = buildFunctionExpression($idOrEmpty,$e)} //function que solo admite 1 o 0 args
| ID calls {/*console.error(deb($ID), deb($calls)); process.exit(0);*/
$$ = buildIdCalls($($ID), $calls);
} //llamadas con varios args tal que fun( )( )( )
//Identificadores variables
| PID '(' e ')' { $$ = buildCallExpression($PID, [$3], true); }
| ID { $$ = buildIdentifier($($ID))} //$(algo) lo concatena con el $ delante
;
idOrEmpty: /* Empty! */ { $$ = []; }
| ID { $$ = [buildIdentifier($($ID))]; } //lo retornamos como array
;
calls: '(' e ')' calls { $$ = [[$e]].concat($calls); }
| '(' ')' calls { $$ = [[]].concat($calls) }
| '(' e ')' { $$ = [[$e]]; }
| '(' ')' { $$ = [ [] ]; } //lista vacia
;
scope-class.js
const {
buildIdentifier,
buildVariableDeclaration,
buildVariableDeclarator,
} = require('./ast-build');
const { difference } = require('./utils.js');
/** Moduclo con algunas funcionalidades para explorar el arbol ast
* @module scope-class.js
*/
class Scope {
constructor(parent) {
this.parent = parent;
this.initialized = new Set();
this.used = new Set();
this.VarDeclarators = []; //list of VariableDeclarator ASTs for let $a = 4... declarations
//this.errors = []; //contain array of errors in the program
}
add(name) {
this.initialized.add(name);
this.VarDeclarators.push(buildVariableDeclarator(buildIdentifier(name)));
}
setAsInitialized(name) {
this.initialized.add(name);
}
setAsUsed(name) {
this.used.add(name);
}
has(name) {
return this.initialized.has(name);
}
buildDeclaration() {
return buildVariableDeclaration(this.VarDeclarators);
}
/**
* @function return the scope where the variable is declared or null if not found
*/
lookup(name) {
if (this.has(name)) return this;
if (this.parent) return this.parent.lookup(name);
return null;
}
/**
* @function return the set of not declared variables
*/
notDeclared() {
let notDeclared = difference(this.used, this.initialized);
for(let v of this.used) {
let s = this.lookup(v);
if (s) notDeclared.delete(v);
}
return notDeclared;
}
notDeclaredMessage(){
let d = this.notDeclared();
//console.log("Comproabando si alguna variable no a sido declarada: ", d);
if ( d.size > 0 ) {
return Array.from(d).
map(x => x.replace(/^[$]/, '')).
map(x => `Not declared variable '${x}'`).join('\n')
}
return null;
}
get length() {
return this.VarDeclarators.length;
}
}
module.exports = Scope;
scope.js
function dependencies(dAst) {
//const Support =("./support-lib.js");
//const patternIsSupport = RegexpFromNames(Object.keys(Support));
const functionNames = Object.keys(require("./support-lib.js"));
dAst.dependencies = new Set([]);
visit(dAst.ast, {
visitCallExpression(path) {
const node = path.node;
let name = node.callee.name;
if (functionNames.includes(name)) {
dAst.dependencies.add(name);
}
this.traverse(path);
}
});
//console.log("Dast from dependencias: ", dAst.dependencies);
return dAst;
}
/**
* Builds the set of variables that are initialized in the program and
* Detects which variables are used in the program
* @function
* @param dAst - Arbol AST
* @returns dAst modificado
*/
const scopeAnalysis = (dAst) => {
const Scope = require('./scope-class.js');
let scope = new Scope(null); // global scope
//dAst.errors = ""; // conjunto de errores controlados
let ast = dAst.ast;
visit(ast, {
visitFunction(path) {
let node =path.node;
if(!["ArrowFunctionExpression", "FunctionExpression"].includes(node.type)) return false;
scope = new Scope(scope);
//mark parameters as initialized
let params = node.params;
//console.log("FunctionExpression+: ", node.params);
for (let param of params) {
scope.setAsInitialized(param.name);
}
this.traverse(path);
// length is implemented via a getter in the Scope class
if (scope.length > 0) { //insert declarations at the beginning of the function
node.body.body.unshift(scope.buildDeclaration())
}
node.scope = scope;
let d = scope.notDeclaredMessage();
//dAst.errors = d;
if (d) console.error(d+ ' used in function scope');
scope = scope.parent;
},
visitAssignmentExpression(path) {
const node = path.node;
if (node.left.type === "Identifier") {
let name = node.left.name;
if (name && !scope.has(name)) {
if (!dAst.dependencies.has(name)) {
scope.add(name);
}
}
}
this.traverse(path);
},
visitIdentifier(path) {
let name = path.node.name;
if(/^[$]/.test(name) && !dAst.dependencies.has(name)) {
scope.setAsUsed(name);
}
this.traverse(path);
}
});
if (scope.length > 0) {
ast.body.unshift(scope.buildDeclaration());
}
ast.scope = scope;
let d = scope.notDeclaredMessage();
if (d) /*dAst.errors = d+"\n";*/console.error(d)
return dAst;
}
module.exports = {
dependencies,
scopeAnalysis,
}
monkey-patch-case.js
let Operators = {
add: '+',
mul: '*',
div: '/',
equals: '==',
pow: '**',
neg: '-',
};
for ( let op in Operators) {
Boolean.prototype[op] = function (other) {
throw new Error(`Unsupported "${Operators[op]}" for ${other}`)
};
Function.prototype[op] = function(other) {
switch (typeof other) {
case 'boolean':
return (...x) => this(...x)[op](Number(other)) //llamo a la funcion con los mismos argumentos y paso other a un numero
case 'object':
if (other instanceof Complex) {
return(...x) => this(...x)[op](other)
} else {
throw new Error(`Unsupported ${op} for ${other}`)
}
case 'function':
try {
return (...x) => this(...x)[op](other(...x)) //retorna una funcion que recibe los argumentos que se le pasa llama a esta funcion en la que estoy y la opero con la otra funcion sobre los mismos args
} catch (e) {
throw new Error(`Unsupported ${op} for function ${other}`)
}
default:
throw new Error(`Unsupported ${op} for type ${typeof other}`)
}
}
}
Tareas
Traducir correctamente las funciones
- Se tradujeron las funciones añadiendo fun al lexer.
- Moodificamos la gramatica para declaracion de funciones anonimas y llamadas de esta.
- Se creo la FunctionExpression en el arbol ast.
- En el scope las buscamos y declaramos.
Operaciones aritméticas y operadores de comparación
- El fichero monkey y complex.js se "sobrecargan" esas operaciones.
Traducir correctamente las expresiones logicas
- Se tradujeron las expresiones logicas añadiendolas al lexer
- Añadiendolas a la gramatica
- Definiendolas en el arbol ast
Otras tareas
- Declaracion de las variables inicializadas en el preámbulo de las funciones o del programa.
- Da mensaje de error para variables no declaradas
- GitHub Actions
- Documentacion
- Covering
GitHub Actions
Creamos un fichero .yml donde se implementaran las Github Actions
name: Node.js CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x, 18.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v3 #clona el repo
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3 #instalacion de node
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run build --if-present
- run: npm test
Coverage
Mediante c8 hacemos un estudio de coverin
Publicación del paquete node.js
Documentación
Para la documentación del código se han utilizado comentarios del estilo JSDoc. Y se ha utilizado la herramienta jsdoc-to-markdown para la creación de un readme automático que se encuentra en la carpeta /docs.
Uso de IA
En esta práctica no se han utilizado herramientas de IA.
Compromiso de uso de IA
En el desarrollo de esta práctica el alumnado se compromete a no hacer uso de IA que pueda poner en riesgo el crecimiento academico personal del alumno.
References
- Repo ULL-ESIT-GRADOII-PL/esprima-pegjs-jsconfeu-talk
- crguezl/hello-jison
- Espree
- astexplorer.net demo
- idgrep.js
- Introducción a los Compiladores con Jison y Espree