Extending Egg
Metodologia
- Trabaje partiendo de la práctica anterior. Puede usar la misma working copy.
- 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.
- 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.
- 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 porr/
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
- Eloquent JS: Chapter 11. Project: A Programming Language
- El lenguaje egg: repo en GitHub
- Repo interpreter-egg
- NodeJS Readline gist
- En el repo ULL-ESIT-PL-1617/interpreter-egg se muestra como hacer un bucle REPL
- XRegExp
- El módulo @ull-esit-pl/example2test
Rúbrica
Incidencias para el Project Board para la prácticaExtending 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
Las llaves
{}
funcionan como alias de los paréntesisSticky
Comentarios
Localización
-
Pruebas
Se usa mocking
Se provee una carpeta
examples
con ejemplos de programasegg
`Se ha automatizado el proceso de pasar del “ejemplo que funciona” a “test unitario que prueba que funciona”
Se hace integración contínua
-
Documentación
Ejecutables, Lenguaje, ASTs, etc.
Documentación del módulo npm (API) y ejecutables como se usan
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
La publicación cumple los estándares de publicación de un módulo (CI, versionado, documentación, etc.)
-
El bucle REPL
Evalúa correctamente y no se despista
Detecta expresiones incompletas
Colores