@kiliankgr/scope-intro
v1.0.0
Published
A simple calculator written in Jison
Downloads
6
Readme
Lab Introduction to Scope Analysis
Ejecucion
- Para la ejecución del programa primero descargaremos las dependencias $ 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
Modificaremos la gramatica para tratar con variables, numeros complejos, palabras reservadas ...
%left ','
%right '='
%left '=='
%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])}
| e '@' e { $$ = buildMax($e1, $e2, true); }
| e '&' e { $$ = buildMin($e1, $e2, true); }
| 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); }
| PID '(' e ')' { $$ = buildCallExpression($PID, [$3], true); }
| ID { $$ = buildIdentifier($($1))} //$(algo) lo concatena con el $ delante
;
scope-class
Creamos una clase para tratar con el árbol ast.
class Scope {
constructor(parent) {
this.parent = parent;
this.initialized = new Set();
this.used = new Set();
this.letDeclarations = []; //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.letDeclarations.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.letDeclarations);
}
notDeclared() {
return difference(this.used, this.initialized)
}
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.letDeclarations.length;
}
}
Scope
Por ultimo deberemos de rellenar las funciones del fichero scope.js para así transformar el arbol AST generado por la libreria recast a nuestro antojo mediante la librería ast-types
/**
* Detect what support functions are used: dependency analysis
* recoge el arbolmediante Ast-types y busca las dependecias que soporte
* @function
* @param dAst - Arbol AST
* @returns dAst modificado
*/
function dependencies(dAst) {
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, {
visitFunctionExpression(path) {
let node =path.node;
scope = new Scope(scope);
//mark parameters as initialized
let params = node.params;
for (let param of params) {
scope.setAsInitializated(param.name);
}
this.traverse(path);
if (scope.length > 0) { //inset 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;
}
Functions
dependencies(dAst) ⇒
Detect what support functions are used: dependency analysis recoge el arbolmediante Ast-types y busca las dependecias que soporte
Kind: global function
Returns: dAst modificado
| Param | Description | | --- | --- | | dAst | Arbol AST |
scopeAnalysis(dAst) ⇒
Builds the set of variables that are initialized in the program and Detects which variables are used in the program
Kind: global function
Returns: dAst modificado
| Param | Description | | --- | --- | | dAst | Arbol AST |
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
Inicialmente hemos tratado de realizar las pruebas mediante nyc, pero al paracer tiene problemas al reconocer algunos de nuestros archivos y por tanto los test salen erroneos.
Por lo tanto hemos recurrido al uso de la libreria c8 similar a nyc
Documentación (modificar)
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.
Véase el lab scope-intro