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 🙏

© 2025 – Pkg Stats / Ryan Hefner

parsetokens

v2.0.0

Published

TDOP, Top Down Operator Precedence

Readme

TDOP, Top Down Operator Precedence

Douglas Crockford [email protected]

2010-11-12

tdop.html contains a description of Vaughn Pratt's Top Down Operator Precedence, and describes a parser for Simplified JavaScript in Simplified JavaScript.

index.html parses parse.js and displays its AST. The page depends on json2.js (which is not included in this project) and on parse.js and tokens.js (which are).

tdop.js contains a Simplified JavaScript parser. See tdop.html for commentary.

tokens.js produces an array of token objects from a string.

Pasos seguidos

Reescribir el analizador léxico (Apartados 1, 2 y 3).

El cambio más significante es el uso de las expresiones regulares en la nueva versión del analizador. Estos cambios se hacen dentro del fichero tokens.js Usaremos las siguientes Regexp para identificar que tipo de token encontremos:

    var WHITES              = /\s+/y;
    var ID                  = /[a-zA-Z_]\w*/y;
    var NUM                 = /\d+(\.\d*)?([eE][+-]?\d+)?/y;
    var STRING              = /('(\\.|[^'])*'|"(\\.|[^"])*")/y;
    var ONELINECOMMENT      = /\/\/.*/y;
    var MULTIPLELINECOMMENT = /\/[*](.|\n)*?[*]\//y;
    var OPERATORS           = />>>=|>>>|>>=|<<=|[*][*]=|===|!==|==|<=|>=|!=|&&|[|][|]|[+][+]|--|[+]=|-=|[*]=|[/]=|%=|&=|\^=|[|]=|<<|>>|<|>|=|[+]|-|[*]|[/]|%|\^|&|[|]|~|:|;|,|[(]|[)]|\[|\]|./y;

Tras ello recorreremos la cadena que pasemos al analizador, que contendrá el código a ser evaluado. De esta forma, cuando detecte una coincidencia entre alguna de las Regexp indicadas arriba y el token que se esté analizando en ese momento, se "etiquetará" y guardará para posteriormente devolver todos estos como resultado en formato JSON.

    while (i < this.length) {
        console.log(i);
        tokens.forEach( function(t) { t.lastIndex = i;}); // Only ECMAScript5
        from = i;
        // Ignore whitespace and comments
        if (m = WHITES.exec(this) ||                                                // Si el token coincide con espacios en blanco
           (m = ONELINECOMMENT.exec(this))  ||                                      // o cualquier tipo de comentarios
           (m = MULTIPLELINECOMMENT.exec(this))) { getTok(); }
        // name.
        else if (m = ID.exec(this)) {                                               // Si el token es un ID o identificador
            result.push(make('name', getTok()));
        } 
        // number.                                                                  // Si el token es un número
        else if (m = NUM.exec(this)) {
            n = +getTok();

            if (isFinite(n)) {
                result.push(make('number', n));
            } else {
                make('number', m[0]).error("Bad number");
            }
        } 
        // string
        else if (m = STRING.exec(this)) {                                           // Si el token es una cadena de texto
            result.push(make('string', getTok().replace(/^["']|["']$/g,'')));
        } 
        // operator
        else if (m = OPERATORS.exec(this)) {                                        // Si el token es un operador
            result.push(make('operator', getTok()));
        } else {
          throw "Syntax error near '"+this.substr(i)+"'. Unrecognized token!";
        }
    }
    return result;

En lo referente al apartado que nos pide movernos dentro de la misma cadena usando "lastIndex", añadiremos el llamado "bit sticky" al final de las Regexp. Para ello empleando la letra "y" de la siguiente forma:

     var WHITES              = /\s+/;  <-- Sin sticky bit
     var WHITES              = /\s+/y; <-- Con sticky bit

Para ejecutar el analizador crearemos un fichero server.js que renderizará usando ejs la vista del mismo y lo mostrará en el puerto local 8080:

    [...]
    
    app.set('view engine', 'ejs');
    
    app.get('/', function(req,res){
      res.render('index');
    });
    
    app.get('/help', function(req,res){
      res.render('tdop.ejs');
    });
    
    const server = app.listen(8080, '0.0.0.0', function () {
    
      const host = server.address().address;
      const port = server.address().port;
    
      console.log('Server with sessions and auth listening at http://%s:%s my ip = %s', host, port, ip.address());
    
    });

Usar Sessions para controlar quien accede a la aplicación (Apartado 5).

Para empezar, crearemos alteraremos el directorio de trabajo añadiendo las carpetas "views" y "toProtect" que contendran: - views: las vistas (HTML) que hayamos generado para que los usuarios se autentifiquen o registren. - toProtect: los ficheros que queremos proteger con las sesiones. En este caso almacenaremos los ficheros index.ejs, parse.js y tokens.js pues es el analizador lo que queremos proteger con las sessions. Tras esto crearemos en el fichero auth.js el middleware que se encargará de administrar las sesiones:

const fs = require('fs');
const bcrypt = require('bcrypt-nodejs');
const salt = bcrypt.genSaltSync(10);
const express = require('express');
const router = express.Router();

Importamos las librerías.

module.exports = function(options) {
  const {
    passwordFile, 
    pathToProtect,
    loginView,
    fullLoginView,
    registerFormView,
    errorRegisterView,
    registeredView
  } = options;
  if (!fs.existsSync(passwordFile)) fs.writeFileSync(passwordFile, '{}');
  
  [...]

Definimos las variables con las rutas a las vistas que vamos a usar, así como al directorio con los ficheros que queremos proteger.

  [...]
  const auth = function(req, res, next) {
      if(req.session && req.session.username && req.session.password){
        return next();
      }
      else { // https://developer.mozilla.org/es/docs/Web/HTTP/Status/401 
        return res.sendStatus(401); // 401: falta autenticación para el recurso solicitado.
      }
    };
  [...]

Creamos una función de autenticación de forma que si existe el nombre y contraseña que se ha indicado se puede ver el contenido protegido.

  [...]
  router.use('/content', 
    auth, // our middleware!
    express.static(pathToProtect)
  );
  [...]

Ruta estática del contenido protegido. Solo se muestra si se ha iniciado sesión antes.

  [...]
  router.get('/login', function (req, res) {
    if ( (!req.session.username)) {
      res.render(loginView);
    }
    else if ((req.session.username)) {
      res.render(fullLoginView);
    }
  });

  router.post('/login', function(req,res){
    let configFile = fs.readFileSync(passwordFile);
    let config = JSON.parse(configFile);

    let p = config[req.body.username];
    if (p) {
      if ((req.session) && req.body && req.body.password && (bcrypt.compareSync(req.body.password, p))){
        req.session.username = req.body.username;
        req.session.password = req.body.password;
        req.session.admin = true;
        return res.render(fullLoginView);
      } 
      else
       return res.render(loginView);
    }
    else
     return res.render(loginView);
  });

  router.post('/', function(req,res){
    let configFile = fs.readFileSync(passwordFile);
    let config = JSON.parse(configFile);

    let p = config[req.body.username];
    if (p) {
      if ((req.session) && req.body && req.body.password && (bcrypt.compareSync(req.body.password, p))){
        req.session.username = req.body.username;
        req.session.password = req.body.password;
        req.session.admin = true;
        return res.render(fullLoginView);
      } 
      else
       return res.render(loginView);
    }
    else
     return res.render(loginView);
  });
  [...]

Métodos GET y POST para hacer login.

  [...]
  router.get('/register', function (req, res) {
    if ((!req.session.username)) {
      res.render(registerFormView);
    }
    else{
      res.render(fullLoginView);
    }
  });

  router.post('/register', function (req, res) {
    let configFile = fs.readFileSync(passwordFile);
    let config = JSON.parse(configFile);
    let p = config[req.body.username];

    if (!p) config[req.body.username] = bcrypt.hashSync(req.body.password, salt);
    else return res.render(errorRegisterView, req.body.username);

    let configJSON = JSON.stringify(config);
    fs.writeFileSync(passwordFile, configJSON);
    res.render(registeredView, {username:req.body.username});

  });
  return router;
};

Métodos GET y POST para registrase.

Tras ello modificamos el fichero server.js para que cargue nuestro middleware:

    [... Importamos todas las librerías necesarias ...]
    
    app.use(express.static(__dirname + '/'));
    app.set('views', path.join(__dirname, '/views'));
    app.set('view engine', 'ejs');
    
    app.use(cookieParser());
    app.use(bodyParser.urlencoded({extended : false}));
    
    app.use(session({
        secret: '2C44-4D44-WppQ38S',
        resave: true,
        saveUninitialized: true
    }));
    
    ///////////////////// AQUÍ SE HACE USO DEL MIDDLEWARE //////////////////////
    
    app.use('/', auth({ passwordFile:  path.join(__dirname, '/users.json'),
                        pathToProtect: path.join(__dirname, '/toProtect'),
                        loginView: 'login',
                        fullLoginView: path.join(__dirname, '/toProtect/index'),
                        registerFormView: 'registro',
                        errorRegisterView: 'malregistrado',
                        registeredView: 'registrado',
    }));
    
    app.get('/', function(req,res){
      res.render('login');
    });
    
    [...]

Despliegue de la aplicación

Servicio IAAS

Para desplegar la aplicación en el servicio IAAS se han seguido los pasos indicados en el siguiente enlace: https://github.com/SYTW/iaas-ull-es.

Heroku

Para desplegar la aplicación en Heroku lo primero que haremos será instalar Heroku-CLI en nuestro cliente:

  npm install heroku -g // Recordad que teneis que tener una versión de node superior a la 8.0.0.

Tras ello, nos autentificaremos en heroku con nuestra cuenta (si no se tiene se puede crear directamente desde la web [https://id.heroku.com/login]) usando el siguiente comando:

  heroku login

Crearemos tras esto, crearemos una "heroku app" para alojar nuestra aplicación:

  heroku create

Y empujamos los últimos cambios hechos a esta rama:

  git push heroku master

Para acabar nos aseguramos de que al menos una instancia de la app se está ejecutando:

  heroku ps:scale web=1

Y listo, la aplicación estará disponible para su uso accediendo directamente con la URL proporcionada durante el heroku create o empleando el siguiente comando:

  heroku open