npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@kiliankgr/functions

v1.0.0

Published

A lab for PL. Intro to Scope Analysis

Downloads

6

Readme

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