Extending Egg

Metodologia

  1. Trabaje partiendo de la práctica anterior. Puede usar la misma working copy.
  2. Añada como remoto el repo de GitHub dado por la asignación de esta tarea. Quizá el nombre del remoto podría ser el nombre de la práctica.
  3. Haga también una branch con el nombre de cada práctica y manténgala actualizada hasta el último push de entrega de esa práctica.
  4. Mantenga los nombres de los ejemplos que aparecen en las descripciones de las prácticas.

Índices Negativos

Añada índices negativos (a la Ruby) para los arrays

➜  eloquentjsegg git:(private2021) ✗ cat examples/array-neg.egg
do{
  def(x, array(1, array(2,3))),
  print(element(x, -1)),        # [ 2, 3 ]
}
➜  eloquentjsegg git:(private2021) ✗ bin/egg.js examples/array-neg.egg
[ 2, 3 ]

Extendiendo element: Posibilidad de Indexar con mas de un Índice

  • Añada la posibilidad de indexar con mas de un índice a element
 ➜  eloquentjsegg git:(private2021) ✗ cat examples/array-index.egg 
do(
  def(x, array(1, array(2,3))),
  print(element(x,0)),          # 1
  print(element(x,1)),          #  [ 2, 3 ]
  print(element(x,1,1)),        # 3
  print(element(x,-1,-1)),      # 3
  print(element(x,-1,0))        # 2
 )

Ejecución:

➜  eloquentjsegg git:(private2021) ✗ bin/egg.js examples/array-index.egg 
1
[ 2, 3 ]
3
3
2

Set: Modificar Elementos de un Array

Extienda set para que se puedan modificar elementos de los arrays

➜  eloquentjsegg git:(private2021) ✗ cat examples/array-set-index.egg
do(
  def(x, array(1,2,3, array(9,8,7))),
  set(x, 2, 9),
  print(x),             # [ 1, 2, 9, [ 9, 8, 7 ] ]
  set(x, 3, 1, 1000),
  print(x)              # [ 1, 2, 9, [ 9, 1000, 7 ] ]
)

Ejecución:

➜  eloquentjsegg git:(private2021) ✗ bin/egg.js examples/array-set-index.egg 
[ 1, 2, 9, [ 9, 8, 7 ] ]
[ 1, 2, 9, [ 9, 1000, 7 ] ]

No se debería poder hacer un set con índices de una variable no estructurada

[.../p7-t3-egg-2-04-16-2020-03-13-25/davafons(casiano)]$ cat examples/set-error.egg
do(
  def(x,4),
  set(x, 1, 2),
  print(x)
)
[.../p7-t3-egg-2-04-16-2020-03-13-25/davafons(casiano)]$ bin/egg.js examples/set-error.egg
TypeError: The object '4' is not indexable!  

Mapas, Hashes o Diccionarios

  • Añada mapas/hashes al lenguaje Egg
➜  davafons-tfa-1819-egg git:(casiano) ✗ cat examples/map.egg      
do {
  def(x, map{"x", 4, "y", map{"z", 3}}),
  print(x),                               # { x: 4, y: { z: 3 } }
  print(element(x, "x")),                 # 4
  print(element(x, "y")),                 # { z: 3 }
  print(element(x, "y", "z")),            # 3
  set(x, "y", "z", 50),
  print(element(x, "y"))                  # { z: 50 }
}

Ejecución:


➜  davafons-tfa-1819-egg git:(casiano) ✗ bin/egg.js -r examples/map.egg

Map(2) { 'x' => 4, 'y' => Map(1) { 'z' => 3 } }
4
Map(1) { 'z' => 3 }
3
Map(1) { 'z' => 50 }

Dos puntos como operador léxico

Nos gustaría poder escribir los hashes/mapas usando : para separar el nombre de la clave del valor, como en este ejemplo:

➜  davafons-tfa-1819-egg git:(casiano) ✗ cat examples/map-colon.egg
do {
  def(x, map{x: 4, y: map{z: 3}}),
  print(x),                               # { x: 4, y: { z: 3 } }
  print(element(x, "x")),                 # 4
  print(element(x, "y")),                 # { z: 3 }
  print(element(x, "y", "z")),            # 3
  set(x, "y", "z", 50),
  print(element(x, "y"))                  # { z: 50 }
}
➜  davafons-tfa-1819-egg git:(casiano) ✗ bin/egg.js examples/map-colon.egg

Map(2) { 'x' => 4, 'y' => Map(1) { 'z' => 3 } }
4
Map(1) { 'z' => 3 }
3
Map(1) { 'z' => 50 }

Una forma de hacer esto es empezar haciendo que el análisis léxico acepte el carácter : para el token COMMA

const xRegExp = require('xregexp');
...
const COMMA = new TokenRegex('COMMA', xRegExp(`(
                ,          # comma
                |
                :(?!=)     # a ':' that isn't followed by an '=' (':=' is reserved word)
             )`, 'yx'));   // flag x allows spaces and comments inside the regexp

y redefiniendo el token WORD:

const WORD = new TokenRegex('WORD', xRegExp(`
                 (\\[\\]|     # [] is a reserved word for arrays
                  :=|         # := is a reserved word for 'define'
                  [^\\s\\(\\)\\{\\}\\[\\]\\.,:"]+   # Avoid some chars
             )`, 'yx'));

y trucando nuestro analizador léxico para que siempre que una WORD vaya seguida de : se retorne una STRING

  __transformTokens(tokens) {
    for(let i = 0; i < tokens.length; ++i) {

      // x: => "x",
      if(tokens[i].type === 'WORD') {
        const nextToken = tokens[i + 1];
        if(nextToken && nextToken.value === ':') {
          tokens[i].type = 'STRING';
        }
      }
      ...
    }
    ...
    return tokens;
  }

El Método sub deberá funcionar con los diccionarios/mapas

Extienda el Monkey Patching sub para que permita indexar los mapas:

➜  eloquentjsegg git:(private2021) ✗ cat examples/map-sub.egg 
do(
  def(x, map{a: 1, b: 4, c: map{d: 5, e: 3}}),
  print(x["sub"]("a")),      # 1
  print(x["sub"]("c", "d")), # 5
  print(x["sub"]("c")["e"]), # 3
  print(x["sub"]("b"))       # 4
)                                                                                    

Ejecución:

➜  eloquentjsegg git:(private2021) ✗ bin/egg.js examples/map-sub.egg
1
5
3
4

OOP en Egg

Añada objetos al lenguaje Egg de manera que podamos escribir programas como este:

➜  eloquentjsegg git:(brackets-method-access) ✗ cat examples/objects.egg       
do {
  def(x, object {
    "c", 0,
    "gc",  ->{element(self, "c")},
    "sc",  ->{value, =(self, "c", value)},
    "inc", ->{=(self, "c", +(element(self, "c"),1))}
  }),

  print(x["gc"]()), # 0
  x["sc"](4),
  print(x["gc"]()), # 4
  x["inc"](),
  print(x["gc"]()), # 5
  print(x["c"]),    # 5
}

Donde self hace referencia al objeto.

Ejecución:

➜  eloquentjsegg git:(brackets-method-access) ✗ bin/egg.js examples/objects.egg  
0
4
5
5

DOT

Syntactic Sugar: Introduzca el operador punto (dot) para poder acceder a los métodos y atributos de un objeto.

La idea es que una expresión como:

  a.b.c(arg1)

es equivalente a esta otra expresión:

  a["b"]["c"](arg1)

Esto es,el dot es como una llamada/apply del objeto en el que el primer argumento es el atributo/método

➜  eloquentjsegg git:(private2021) ✗ cat examples/dot.egg  
do(
  def(x, array(1,4,5)),
  def(s, x.join("-")),                 # The same as x["join"]("-")
  print(s),                            # 1-4-5
  print(array(1,4,5).join("-").length) # 5 Same as array(1,4,5)["join"]("-")["length"]
)

Ejecución:

➜  eloquentjsegg git:(private2021) ✗ bin/egg.js examples/dot.egg
1-4-5
5

Otro ejemplo, esta vez con objetos Egg.

➜  eloquentjsegg git:(brackets-method-access) ✗ cat examples/dot-obj-2.egg 
do (
  def(x, object ( 
    c:   0,
    gc:  ->{self.c},
    sc:  ->{value, =(self, "c", value)},
    inc: ->{=(self, "c", +(self.c, 1))}
  )),
  # print(x),
  print(x["c"]),           # 0
  print(x.c),              # 0
  print(x.gc()),           # 0 calls the function!
  print(element(x, "gc")), # [Function: bound ]
  print(x["sub"]("gc")),   # [Function: bound ]
  print(x.sub("gc")),      # [Function: bound ]
  x.sc(5),
  print(x.gc()),           # 5
  x["sc"](9),
  print(x.gc())            # 9
)

Para poder acceder al atributo sub de un objeto Egg como se ha hecho en las líneas:

  print(x["sub"]("gc")),   # [Function: bound ]
  print(x.sub("gc")),      # [Function: bound ]

es necesario añadírselo en topEnv:

  eloquentjsegg git:(private2021)  sed -ne '154p' lib/eggvm.js 
topEnv["sub"] = Object.prototype.sub; 

Recuerde que en la implementación de object que explico en clase, los objetos Egg no heredan de la clase JS Object sino del entorno actual, por lo que no se benefician del Monkey Patching de la clase JS Object

➜  eloquentjsegg git:(brackets-method-access) ✗ bin/egg.js examples/dot-obj-2.egg 
0
0
0
[Function: bound ]
[Function: bound ]
[Function: bound ]
5
9

Otro ejemplo con números:

➜  eloquentjsegg git:(private2021) ✗ cat examples/dot-num.egg   
do{
  print(4.toFixed(2)),
  def(x, 4),
  print(x["toFixed"](2)),
  def(z, x.toFixed(2)),
  print(z),
}
➜  eloquentjsegg git:(private2021) ✗ bin/egg.js examples/dot-num.egg
4.00
4.00
4.00

Require

Expanda el lenguaje con un require para que permita el uso de librerías.

Repase el vídeo Como implementar la funcionalidad de “require”

Aquí tiene un enlace al Repo correspondiente al vídeo.

En este ejercicio:

  • Memoize las librerías para que no se carguen dos veces
  • Procure añadir esta funcionalidad sin tocar el código principal usando el strategy pattern + registry pattern

Ejemplo

Código del Módulo:

[~/.../crguezl-egg(private2019)]$ cat examples/require/module.egg
  # module. Exports z
  do {
    print("inside module"),
    :=(z, map{inc: ->{x, 
                       +(x,1)
                     } # end fun
             } # end map
    ), # end of :=
    z  # el último valor será exportado
  }

Programa Cliente:

[~/.../crguezl-egg(private2019)]$ cat examples/require/client.egg
  do {
    :=(z, require("examples/require/module.egg")),
    print(z.inc(4)),
    :=(w, require("examples/require/module.egg")),
  }

Ejecución:

  [~/.../crguezl-egg(private2019)]$ bin/egg.js examples/require/client.egg 
  inside module
  5

Observe como inside module aparece una sola vez pese a que el módulo es required dos veces

RegExps

  • Añada expresiones regulares al lenguaje Egg.
  • Las delimitaremos mediante r/regexpExpression/ comenzando por r/y terminando con una /.
  • Se admiten cualesquiera caracteres entre los delimitadores, incluyendo retornos de carro.
  • Opcional:
    • Use XRegExp para darle mayor potencia a las expresiones regulares.
    • Recuerde que XRegExp admite 0 o mas repeticiones de estas opciones en las expresiones regulares: [nsxAgimuy]*
  • Las expresiones regulares son un nuevo tipo de token y conllevan una ligera modificación de la sintáxis.
  • Así mismo los AST ahora tendrán un nuevo tipo regex
[.../p6-t3-egg-1-04-16-2020-03-13-25/davafons(master)]$ cat examples/regex-simple.egg
  do {
    :=(r, r/(\w+)
           \s+
           (\d+)  # numero 
          /x),
    :=(s, r.test("a 4")),
    :=(m, r.exec(";;; a 42")),
    print(s),
    print(m),
  }
[.../p6-t3-egg-1-04-16-2020-03-13-25/davafons(master)]$ bin/egg.js examples/regex-simple.egg 
true
[ 'a 42', 'a', '42', index: 4, input: ';;; a 42', groups: undefined ]

Otro ejemplo:

➜  eloquentjsegg git:(brackets-method-access) ✗ cat  examples/regexp-2.egg
do {
  :=(d, r/
         (?<year>  \d{4} ) -?  # year
         (?<month> \d{2} ) -?  # month
         (?<day>   \d{2} )     # day
        /x),
  print(d["test"]("1987-07-14")),  # true
  :=(m, d["exec"]("1987-07-14")),
  print(m), #  [ '1987-07-14', '1987', '07', '14', index: 0, input: '1987-07-14' ] 
  print(m["index"]), # 0

  :=(x, RegExp["exec"]("2015-02-22", d)),
                  /*
                  [ '2015-02-22',
                    '2015',
                    '02',
                    '22',
                    index: 0,
                    input: '2015-02-22',
                    year: '2015',
                    month: '02',
                    day: '22' ]
                  */
  print(x), 
  print(x["year"]), # 2015
  print(x["month"]) # 02
}

Ejecución:

➜  eloquentjsegg git:(brackets-method-access) ✗ bin/egg.js examples/regexp-2.egg
true
[
  '1987-07-14',
  '1987',
  '07',
  '14',
  index: 0,
  input: '1987-07-14',
  groups: undefined
]
0
[
  '2015-02-22',
  '2015',
  '02',
  '22',
  index: 0,
  input: '2015-02-22',
  groups: undefined,
  year: '2015',
  month: '02',
  day: '22'
]
2015
02

Bucles for

Extienda el lenguaje con uno o varios tipos de bucle for

Bucle for convencional

[.../TFA-04-16-2020-03-22-00/davafons(casiano)]$ cat examples/for.egg
do(
  for(define(x, 0), <(x, 5), ++(x),
    print(x)
  )
)
[.../TFA-04-16-2020-03-22-00/davafons(casiano)]$ bin/egg.js examples/for.egg
0
1
2
3
4

Bucle foreach

Aunque al disponer de acceso a los métodos ya tenemos un bucle para recorrer los objetos iterables:

➜  eloquentjsegg git:(private2021) ✗ cat examples/for-js.egg 
do{
  def(a, array{4,3,2,1}),
  a.forEach{
    fun(x,i,ra, 
      print("Element",i,"of ",ra,"is",x)
    )
  }
}

Que cuando se ejecuta:

➜  eloquentjsegg git:(private2021) ✗ bin/egg.js examples/for-js.egg
Element 0 of  [ 4, 3, 2, 1 ]  is 4
Element 1 of  [ 4, 3, 2, 1 ]  is 3
Element 2 of  [ 4, 3, 2, 1 ]  is 2
Element 3 of  [ 4, 3, 2, 1 ]  is 1

Añada a Egg un bucle foreach similar a este:

[.../TFA-04-16-2020-03-22-00/davafons(casiano)]$ cat examples/foreach.egg
do {
  def(x, arr(1, 2, 3)),
  foreach(x, x, print(x)), # Different x from inner and outer scope

  def(y, map(a: 1, b: 2, c: 3)),
  foreach(key, y.keys(), print(key.toUpperCase()))
}
[.../TFA-04-16-2020-03-22-00/davafons(casiano)]$ bin/egg.js examples/foreach.egg
1
2
3
A
B
C

Recursos

Rúbrica

Incidencias para el Project Board para la práctica

Extending Egg

  • Índices negativos

  • element: Funciona con arrays, mapas

  • set: Funciona con arrays, mapas

  • Mapas/Hashes

  • Operador léxico ‘:

  • sub: funciona con mapas

  • Objetos

  • DOT lexical operator

  • require

  • Regexps

  • For loops

  • La llamada a métodos de los objetos JS mediante la sintáxis de brackets funciona y se han añadido Pruebas

  • Se ha añadido currying a la sintáxis de brackets

  • Se han expandido los AST con un nuevo tipo de nodo

  • Se ha hecho uso de Monkey Patching para mejorar la expresividad

  • Se describe la nueva gramática en la documentación

  • Se describen los ASTs correctamente en la documentación

  • Metodología de trabajo y Jerarquía de ficheros

  • Se han añadido clases para los distintos tipos de nodos siguiendo el Strategy Pattern

  • Se dispone de un mecanismo para convertir los JSON en objetos de las clases del AST y el intérprete evm funciona

  • Alias de las palabras reservadas como set/= define/def/:= etc.

  • Analizador Léxico

    1. Las llaves {} funcionan como alias de los paréntesis

    2. Sticky

    3. Comentarios

    4. Localización

  • Pruebas

    1. Se usa mocking

    2. Se provee una carpeta examples con ejemplos de programas egg`

    3. Se ha automatizado el proceso de pasar del “ejemplo que funciona” a “test unitario que prueba que funciona

    4. Se hace integración contínua

  • Documentación

    1. Ejecutables, Lenguaje, ASTs, etc.

    2. Documentación del módulo npm (API) y ejecutables como se usan

    3. Opcional: Documentación de la API de los módulos (parser, eggvm), informe de cubrimiento, etc.

  • set (asignación y manejo de ámbitos)

  • Librerías separadas (Parser, Intérprete, etc.)

  • Ejecutables (uno con opciones o varios ejecutables)

  • Se ha publicado en GitHub Registry

    1. La publicación cumple los estándares de publicación de un módulo (CI, versionado, documentación, etc.)

  • El bucle REPL

    1. Evalúa correctamente y no se despista

    2. Detecta expresiones incompletas

    3. Colores