Fundamentals of Programming
Jest Build Lexer Example

Ejemplo de pruebas con Jest:

➜  lexer-generator-solution git:(master) ✗ pwd -P
/Users/casianorodriguezleon/campus-virtual/2122/pl2122/practicas-alumnos/lexer-generator/lexer-generator-solution
➜  lexer-generator-solution git:(master) ✗ ls test
build-lexer.test.js          test-grammar-2-args.ne       test-grammar-error-tokens.ne test-grammar.ne
egg                          test-grammar-combined.ne     test-grammar.js
➜  lexer-generator-solution git:(master) ✗ cat test/build-lexer.test.js 
// If you want debugging output run it this way:
// DEBUG=1 npm test
// @ts-check
/**
 * @author Casiano Rodriguez Leon <aluXXX@ull.edu.es>
 * @since 28/03/2122
 * This file imports the buildLexer function and tests it
 */
 
'use strict';
 
const { buildLexer } = require('../src/main.js');
 
describe('buildLexer', () => {
  const SPACE = /(?<SPACE>\s+|\/\/.*)/; SPACE.skip = true;
  const RESERVEDWORD = /(?<RESERVEDWORD>\b(const|let)\b)/;
  const ID = /(?<ID>\b([a-z_]\w*)\b)/;
  const STRING = /(?<STRING>"([^\\"]|\\.")*")/;
  const OP = /(?<OP>[+*\/=-])/;
 
  const myTokens = [
    SPACE,
    RESERVEDWORD,
    ID,
    STRING,
    OP,
  ];
 
  const { lexer } = buildLexer(myTokens);
 
  test('Assignment to string', () => {
    const str = 'const varName = "value"';
    const result = lexer(str);
    const expected = [
      {
        type: 'RESERVEDWORD',
        value: 'const',
        col: 1,
        line: 1,
        length: 5,
      },
      {
        type: 'ID',
        value: 'varName',
        col: 7,
        line: 1,
        length: 7,
      },
      {
        type: 'OP',
        value: '=',
        col: 15,
        line: 1,
        length: 1,
      },
      {
        type: 'STRING',
        value: '"value"',
        col: 17,
        line: 1,
        length: 7,
      },
    ];
    expect(result).toEqual(expected);
  });
 
  test('Assingment spanning two lines', () => {
    const str = 'let x = a + \nb';
    const result = lexer(str);
    const expected = [
      {
        type: 'RESERVEDWORD',
        value: 'let',
        col: 1,
        line: 1,
        length: 3,
      },
      {
        type: 'ID',
        value: 'x',
        col: 5,
        line: 1,
        length: 1,
      },
      {
        type: 'OP',
        value: '=',
        col: 7,
        line: 1,
        length: 1,
      },
      {
        type: 'ID',
        value: 'a',
        col: 9,
        line: 1,
        length: 1,
      },
      {
        type: 'OP',
        value: '+',
        col: 11,
        line: 1,
        length: 1,
      },
      {
        type: 'ID',
        value: 'b',
        col: 1,
        line: 2,
        length: 1,
      },
    ];
    expect(result).toEqual(expected);
  });
 
  test('Input with errors', () => {
    const str = ' // Input with errors\nlet x = 42*c\nconst a = b';
    const result = lexer(str);
    const expected = [
      {
        type: 'RESERVEDWORD',
        value: 'let',
        col: 1,
        line: 2,
        length: 3,
      },
      {
        type: 'ID',
        value: 'x',
        col: 5,
        line: 2,
        length: 1,
      },
      {
        type: 'OP',
        value: '=',
        col: 7,
        line: 2,
        length: 1,
      },
      {
        type: 'ERROR',
        value: '42*c',
        col: 9,
        line: 2,
        length: 4,
      },
      {
        type: 'RESERVEDWORD',
        value: 'const',
        col: 1,
        line: 3,
        length: 5,
      },
      {
        type: 'ID',
        value: 'a',
        col: 7,
        line: 3,
        length: 1,
      },
      {
        type: 'OP',
        value: '=',
        col: 9,
        line: 3,
        length: 1,
      },
      {
        type: 'ID',
        value: 'b',
        col: 11,
        line: 3,
        length: 1,
      },
    ];
    expect(result).toEqual(expected);
  });
 
  test('Input with errors that aren\'t at the end of the line', () => {
    const str = 'let 2x = "hello"';
    const result = lexer(str);
    const expected = [
      {
        type: 'RESERVEDWORD',
        value: 'let',
        col: 1,
        line: 1,
        length: 3,
      },
      {
        type: 'ERROR',
        value:  "2x = \"hello\"",
        col: 5,
        line: 1,
        length: 12,
      }
    ];
    expect(result).toEqual(expected);
  });
 
  test('Shouldn\'t be possible to use unnamed Regexps', () => {
    const OP = /([+*\/=-])/;
 
    expect(() => buildLexer([OP]).lexer).toThrowError(/named/);
  });
 
  test('Shouldn\'t be possible to use Regexps named more than once', () => {
    const OP = /(?<OP>(?<OP2>[+*\/=-]))/;
 
    expect(() => buildLexer([OP]).lexer).toThrowError(/named/);
  });
 
  test('Should be possible to use Regexps with look behinds', () => {
    const OP = /(?<OP>a(?<=b))/;
 
    expect(() => buildLexer([OP]).lexer).not.toThrowError(/named/);
  });
});
 
describe('buildLexer with unicode', () => {
  const SPACE = /(?<SPACE>\s+|\/\/.*)/; SPACE.skip = true;
  const RESERVEDWORD = /(?<RESERVEDWORD>\b(const|let)\b)/;
  const ID = /(?<ID>\b([a-z_]\w*)\b)/;
  const STRING = /(?<STRING>"([^\\"]|\\.")*")/;
  const OP = /(?<OP>[+*💠\/=-])/;
 
  const myTokens = [
    SPACE,
    RESERVEDWORD,
    ID,
    STRING,
    OP,
  ];
 
  const { lexer } = buildLexer(myTokens);
 
  test('Use of emoji operation', () => {
    const str = 'const varName = "value" 💠 "a"';
    const result = lexer(str);
    const expected = [
      {
        type: 'RESERVEDWORD',
        value: 'const',
        col: 1,
        line: 1,
        length: 5,
      },
      {
        type: 'ID',
        value: 'varName',
        col: 7,
        line: 1,
        length: 7,
      },
      {
        type: 'OP',
        value: '=',
        col: 15,
        line: 1,
        length: 1,
      },
      {
        type: 'STRING',
        value: '"value"',
        col: 17,
        line: 1,
        length: 7,
      },
      {
        type: 'OP',
        value: '💠',
        col: 25,
        line: 1,
        length: 2,
      },
      {
        type: 'STRING',
        value: '"a"',
        col: 28,
        line: 1,
        length: 3,
      },
    ];
    expect(result).toEqual(expected);
  });
});