2.0.0 • Published 5 years ago

parsetokens v2.0.0

Weekly downloads
2
License
ISC
Repository
github
Last release
5 years ago

TDOP, Top Down Operator Precedence

Douglas Crockford douglas@crockford.com

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