@kiliankgr/functions

1.0.0 • Public • Published

Open in Codespaces

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 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

Package Sidebar

Install

npm i @kiliankgr/functions

Weekly Downloads

2

Version

1.0.0

License

ISC

Unpacked Size

1.18 MB

Total Files

185

Last publish

Collaborators

  • kiliankgr