@kiliankgr/scope-intro

1.0.0 • Public • Published

Open in Codespaces

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

scopeAnalysis(dAst)

Builds the set of variables that are initialized in the program and Detects which variables are used in the program

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

Package Sidebar

Install

npm i @kiliankgr/scope-intro

Weekly Downloads

1

Version

1.0.0

License

ISC

Unpacked Size

6 MB

Total Files

149

Last publish

Collaborators

  • kiliankgr