Extending the Egg Interpreter
Planificación 23/24
Planificación del final de la asignatura
Entrega de TFA los días Jueves 2, Viernes 3 (Semana 14), Lunes 6, Martes 7 y Miércoles 8 (Semana 15) de Mayo de 2024 en horario de clase. Los Lunes, Martes y Miércoles usaremos el aula de teoría (acuda con el portátil a clase).
Si se decide por una propuesta alternativa a las propuestas, consulte a los profesores para verificar su validez.
Hacer los laboratorios
- extended-egg-interpreter es una opción que cuenta para la realización del TFA.
- egg-2-js es una opción que cuenta para la realización del TFA.
Puede también proponer por otra opción que le parezca interesante. En la sección
- TFA encontrará información sobre algunas opciones.
Interpreting Property Nodes
The first thing to do is to fill the class Property
inside the file src/ast.js
providing not only the constructor but the evaluate
and leftEvaluate
methods.
Here is a proposal template:
class Property {
constructor(tree) { ... }
evaluate(env) {
if (this.operator.type == "word" && this.operator.name in specialForms) {
// Is there any meaning for s.t. like while[<(x,4), ... ]?
}
let theObject = this.operator.evaluate(env);
let propsProcessed = this.args.map((arg) => arg.evaluate(env));
let propName = checkNegativeIndex(theObject, propsProcessed[0]);
if (theObject[propName] || propName in theObject) {
// theObject has a property with name "propName"
// Write here the code to get the specified property
} else if (typeof theObject === "function") {
// theObject is a function, curry the function
} else {
throw new TypeError(`Evaluating properties for Object "${JSON.stringify(theObject)}" properties: "${JSON.stringify(propsProcessed)}"`);
}
}
leftEvaluate(env) {
// Interpret s.t. as a[2,3].b in the expression =(a[2,3].b, 4)
}
}
For the method evaluate
we can follow the same structure of the evaluate
of Apply
nodes.
The object is in the operator
child and the properties in the args
array.
We check in line 13 that the object really has that property. If so, we have to iteratively traverse the properties indexing in the property the previous object to obtain the new object. Take care of negative indices like in x[-1, -2]
and cases in which the index returns a function object, like in =(x, 4["toFixed"])
, since in that cases the function is itended to be a method and has to be binded to the object.
Negative Indices in arrays
The interpreter has to understand indexation on negative indices. The consequence is that when accessing properties, we must check if that is the case. A solution is to write a helper and use it wherever is needed:
function checkNegativeIndex(obj, element) {
if (Array.isArray(obj) && element < 0 ) {
element += obj.length;
}
return element;
}
Another is to do Monkey Patching in the object class.
Instead of using checkNegativeIndex
, we can alternatively add to the object class a method at
that is called object.at(property)
and returns object[property]
but when property
is a negative number and object
is an array, it returns object[length+property]
. Then we can use at
wherever is needed. Choose the option that suits you.
Monkey Patching
You can also use Monkey Patching to extend any of the basic classes. For instance,
the following code augments the Number
class with methods for the numeric operations:
const binOp = {
"+": (a, b) => a + b,
"-": (a, b) => a - b,
"*": (a, b) => a * b,
"/": (a, b) => a / b,
};
[ "+", "-", "*", "/",].forEach(op => {
Number.prototype[op] = function(...args) {
try {
let sum = this;
for(let i=0; i<args.length; i++) {
sum = binOp[op](sum, args[i]);
}
return sum;
} catch (e) {
throw new Error(`Error in Number method '${op}'\n`,e)
}
};
}) // end of forEach
that combined with currying allows to achieve examples like this one:
➜ egg-oop-parser-solution git:(master) cat examples/curry-method.egg
do (
print(4["+"][5](3)),
print(4.+[5](3)), # Same thing 12
print(4["*"][5](3)), # 4["*"](5, 3) # 60
print(6["/"][2](3)), # 6["/"](2, 3) # 1
print(6["-"][2](3)) # 6["/"](2, 3) # 1
)
➜ egg-oop-parser-solution git:(master) bin/egg examples/curry-method
12
12
60
1
1
Currying in Egg
When the argument used to index a function object is not an attribute of the function
someFun[arg1, ... ] # and "arg1" is not a property of "someFun"
then we want arg1, ...
to be interpreted as arguments for someFun
and the expression returns the currying of the function (opens in a new tab) on arg1, ...
.
For instance:
✗ cat examples/curry-no-method.egg
print(+[4](2))
In this version of the Egg interpreter +
is a function that takes an arbritrary number of numbers:
and returns its sum. The curried
is the function defined by
Here is the implementation of the arithmetic operations in this version of the Egg interpreter that take an arbritrary number of numbers:
// arithmetics
[
'+',
'-',
'*',
'/',
'**',
].forEach(op => {
topEnv[op] = new Function('...s', `return s.reduce((a,b) => a ${op} b);`);
});
Execution:
➜ egg-oop-parser-solution git:(master) ✗ bin/eggc.js examples/curry-no-method.egg
➜ egg-oop-parser-solution git:(master) ✗ bin/evm examples/curry-no-method.json
6
However, if the attribute exists we want an ordinary property evaluation, as in this example:
➜ egg-oop-parser-solution git:(master) cat examples/function-length-property.egg
do(
def(f, fun(x, y, +(x,y))),
print(f["numParams"]) # JS length property is not supported
)
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/function-length-property
2
Design Consideration
The decision of overloading the meaning of the property access for functions is a risky one but has few consequences over the grammar design.
The decision of overloading the meaning of the property access for functions has consequences during the interpretation phase.
In this case the idea behind the proposal is that
Any potential argument of a function can be viewed as a property of such function whose value is the function curried for that argument
which makes the design proposal consistent with the idea of property
Currying and the dot operator
The dot operator for objects a.b
is defined in such a way that a.b
and a["b"]
are the same thing. This is why the former program examples/curry-no-method.egg
can be rewritten this way:
➜ egg-oop-parser-solution git:(master) ✗ cat examples/curry-no-method-dot.egg
print(+.4(2))
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/curry-no-method-dot
6
Changing the evaluate
method
You have to add the code in lines 12-14 to return the curryfied function:
evaluate(env) {
if (this.operator.type == "word" && this.operator.name in specialForms) {
// ... ?
}
let theObject = this.operator.evaluate(env);
let propsProcessed = this.args.map((arg) => arg.evaluate(env));
let propName = checkNegativeIndex(theObject, propsProcessed[0]);
if (theObject[propName] || propName in theObject) {
// ... theObject has a property with name "propName"
} else if (typeof theObject === "function") {
// theObject is a function, curry the function
// using propsProcessed as fixed arguments
} else
throw new TypeError(`...`);
}
Examples of currying 4["+", 5](3)
➜ eloquentjsegg git:(main) ✗ cat test/examples/curry-multiple-indices.egg
do(
print(4["+", 5](3)),
print(4["+", 5, 9](3))
)
Here is the execution:
➜ eloquentjsegg git:(main) ✗ bin/egg.js test/examples/curry-multiple-indices.egg
12
21
and here is the section of AST corresponding to the sub-expression 4["+", 5](3)
:
{
"type": "apply",
"operator": {
"type": "property",
"operator": { "type": "value", "value": 4 },
"args": [
{ "type": "value", "value": "+" },
{ "type": "value", "value": 5 }
]
},
"args": [
{ "type": "value", "value": 3 }
]
}
Leftvalues and Extended Assignments
We have to define the leftEvaluate
method to support expressions like set(a[0, -1].x, 3)
in this program:
➜ egg-oop-parser-solution git:(master) ✗ cat examples/set-simple.egg
do(
def(a, [[1,{x:2}],3]),
set(a[0, -1].x, 3),
print(a)
)
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/set-simple
[[1,{"x":3}],3]
Remember that we defined references in Egg as an array where the first element is the JS reference to an Egg scope, an object, an array, a map, etc. and the following elements describe the position inside the object.
For instance, for the expression set(a[0, -1].x, 3)
, if leftSide
denotes the AST for a[0, -1].x
, the call leftSide.leftEvaluate(env)
has to return an array with the entry for a
in its scope env["a"]
and then the computed indices 0
, something like a.length-1
and "x"
. Notice that when the leftEvaluate
of a property node is called, necessarily the operator of the leftSide
AST has to be a reference (the array a
in the example set(a[0, -1].x, 3)
).
Recall that leftEvaluate
is called from set
.
To help you with this task, here I leave to you an implementation of set
:
specialForms['='] = specialForms['set'] = function(args, env) {
if (args.length !== 2) {
throw new SyntaxError(`Bad use of set '${JSON.stringify(args, null, 0)}.substring(0,20)}'`);
}
let valueTree = args[args.length-1];
let value = valueTree.evaluate(env);
let leftSide = args[0];
let [s, ...index] = leftSide.leftEvaluate(env);
let last = index.length-1
for (let j = 0; j < last; j++) {
index[j] = checkNegativeIndex(s, index[j]);
s = s[index[j]];
}
index[last] = checkNegativeIndex(s, index[last]);
s[index[last]] = value;
return value;
}
Accesing Egg ASTs and Evaluating them from Egg Programs
Proposal
Coming back to the evaluate
method of the Property
nodes, it may be worth considering this lines of code that were in the code of the evaluate
method of the Apply
nodes:
evaluate(env) {
if (this.operator.type == "word" && this.operator.name in specialForms) {
// Is there any meaning for s.t. like while[<(x,4), ... ]?
}
...
}
Not much comes to my mind that may mean The attribute of a language construct.
The Syntactically Correct, Semantically Absurd Language Design Pattern Again!
This is clearly a case of what we have called The Syntactically Correct, Semantically Absurd Language Design Pattern.
One meaning that may be useful for expressions like while[<(x,4), ... ]
is to return an object with two properties:
- The AST of the corresponding
Apply
node - The current scope/env
eval
With that in mind and adding an eval
function, we can write Egg programs like the following:
➜ egg-oop-parser-solution git:(master) ✗ cat examples/specialform-property-4.egg
(
def(b,4),
def(state, do[
print(+(b,1))
]),
=(state.scope.b, 9),
print(Object.keys(state)), # ["ast","scope"]
print(JSON.stringify(state.ast, null, 2)), # the AST of do(print(+(b,1))))
eval(state) # 10 evals the AST in the current scope
)
That when executed produces:
➜ egg-oop-parser-solution git:(master) ✗ bin/evm-crguezl.js examples/specialform-property-4.json
["ast","scope"]
{
"type": "apply",
"operator": {
"type": "word",
"name": "do"
},
"args": [
{
"type": "apply",
"operator": {
"type": "word",
"name": "print"
},
"args": [
{
"type": "apply",
"operator": {
"type": "word",
"name": "+"
},
"args": [
{
"type": "word",
"name": "b"
},
{
"type": "value",
"value": 1
}
]
}
]
}
]
}
10
Here the evm-crguezl.js
script points to the solution in the eloquentjsegg
repository branch private-2223
:
➜ egg-oop-parser-solution git:(master) ✗ ls -l bin/
total 24
-rwxr-xr-x 1 casianorodriguezleon staff 215 Apr 22 13:10 egg
-rwxr-xr-x 1 casianorodriguezleon staff 1678 Apr 22 12:29 eggc.js
lrwxr-xr-x 1 casianorodriguezleon staff 85 Apr 22 12:32 evm -> /Users/casianorodriguezleon/campus-virtual/shared/egg/oop-evm-releases/evm-2122-macos
lrwxr-xr-x 1 casianorodriguezleon staff 78 Apr 23 10:57 evm-crguezl.js -> /Users/casianorodriguezleon/campus-virtual/shared/egg/eloquentjsegg/bin/evm.js
Adding the parser to topEnv
We can add the parser
to the virtual machine memory topEnv
and in this way produce an AST from an input string that can be evaluated later:
➜ egg-oop-parser-solution git:(master) ✗ cat examples/eval.egg
(
def(b,4),
def(input, "print(def(b,+(b,1)))"),
def(ast, parse(input)),
print(JSON.stringify(ast,null,2)),
eval({ast: ast, scope: scope()})
)
Here we have added the specialForm
scope()
that with no arguments returns the current scope.
When executed, examples/eval.egg
produces this output:
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/eval
{
"type": "apply",
"operator": {
"type": "word",
"name": "print"
},
"args": [
{
"type": "apply",
"operator": {
"type": "word",
"name": "def"
},
"args": [
{
"type": "word",
"name": "b"
},
{
"type": "apply",
"operator": {
"type": "word",
"name": "+"
},
"args": [
{
"type": "word",
"name": "b"
},
{
"type": "value",
"value": 1
}
]
}
]
}
]
}
5
Maps, Hashes or Dictionaries
Hashes are easy to implement. Here you have an example of use:
➜ egg-oop-parser-solution git:(master) ✗ cat examples/map-colon-leftside.egg
(
def(x, map(x: 4, y: map(z: 3))),
print(x), # {"x":4,"y":{"z":3}}
print(x[y:"z"]), # 3
=(x.y.z, 50),
print(x.y) # {"z":50}
)
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/map-colon-leftside
{"x":4,"y":{"z":3}}
3
{"z":50}
Objects
Let us introduce Objects in Egg. They will be pretty similar to JS objects, but the word self
will be the equivalent of JS this
and will refer to the object. Here is an example:
➜ egg-oop-parser-solution git:(master) ✗ cat examples/object-colon-selector.egg
do (
def(x, {
c: [1, 2, 3], # array literals!
gc: fun(
element(self, "c") # old way works
),
sc: fun(value, # look at the left side of the assignment!
=(self.c[0], value)
),
inc: fun(
=(self.c[0], +(self.c[0], 1))
)
}),
print(x),
print(x.gc()), # [1, 2, 3]
x.sc(4),
print(x.gc()), # [4,2,3]
x.inc(),
print(x.gc()), # [5,2,3]
print(x.c.pop()), # 3
print(x.c) # [5,2]
)
and here is an execution:
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/object-colon-selector
{"c":[1,2,3]}
[1,2,3]
[4,2,3]
[5,2,3]
3
[5,2]
To add objects to Egg, you can follow these instructions:
- Add the corresponding function entry to
specialForms["object"]
- Create a new environment
objEnv
for the object having as parent the current environment - Create the object
obj
as a JS object so that it has all the properties of JS objects - Add
self
to the object environmentobjEnv
. Has to reference the just created objectobj
- Traverse the
args
ASTs (has to be a forest with an even number of trees) taking the key value pair on each step - Evaluate the pairs key, value in the context of the object environment
objEnv
updating both the object entry and the object environmentobjEnv
entry - Return the just created object
obj
Alternative solution: Using the object as Environment
See file lib/eggvm.js
in branch private2019
in ULL-ESIT-PL-1819/private-egg/lib/eggvm.js (opens in a new tab) for an alternative solution using only the object as environment:
const obj = Object.create(env); // {}; // Object.create(null);
obj["this"] = obj;
However, it produces a cyclic reference. See the execution for this old example (In this version this
refers to the object being built. Brackets and curly brackets are synonyms of the parenthesis):
➜ eloquentjsegg git:(private2019) ✗ cat examples/bind.egg
do (
def(x, object (
"c", 0,
"gc", ->{element[this, "c"]},
"sc", ->{value, =(this, "c", value)},
"inc", ->{=(this, "c", +(element[this, "c"],1))}
)),
print(x),
x.sc(4),
define(g, element(x, "gc")),
print(g), # [Function: bound ]
print(g()), # 4
define(h, element(x, "sc")),
print(h), # [Function: bound ]
print(h(5)), # 5
print(x.c), # 5
print(x.gc()), # 5
print(g()), # 5
)
Execution:
➜ eloquentjsegg git:(private2019) ✗ bin/egg.js examples/bind.egg
<ref *1> Map {
this: [Circular *1],
c: 0,
gc: [Function: bound ],
sc: [Function: bound ],
inc: [Function: bound ]
}
[Function: bound ]
4
[Function: bound ]
5
5
5
5
RegExps
It will be nice to have support for RegExps in Egg:
➜ egg-oop-parser-solution git:(master) cat examples/regexp-simple.egg
(
def(r, r/(\w+) # word
\s+ # spaces
(\d+) # number
/x),
def(s, r.test("a 4")),
def(m, r.exec("a word <a 42> followed by a number")),
print(s),
print(m),
=(m, r.exec("no word followed by a number")),
print(m)
)
The x
option is an extension introduced by XRegExp (opens in a new tab) (but doesn't exists in regular JS) and allow us to use spaces and comments inside the Regexp.
When we execute the former program we get:
➜ egg-oop-parser-solution git:(master) bin/eggc.js examples/regexp-simple.egg
➜ egg-oop-parser-solution git:(master) npx evm examples/regexp-simple.json
true
["a 42","a","42"]
null
To add RegExps to Egg we have to modify not only the interpreter, but the lexer and grammar from the lab Adding OOP to the Egg Parser.
Inside the src/tokens.js
of your parser you have to add a regexp for the regexps:
const REGEXP = /(?<REGEXP>r\/((?:[^\/\\]|\\.)*)\/(\w*?\b)?)/;
That will match expressions like r/ characters that aren't slashes or escaped characters /
.
Question: Why we do not use slash delimiters /regexp/
to denote Egg regexps?
Question: Why /(?<REGEXP>r\/((?:[^\/\\]|\\.)*)\/(\w*?\b)?)/
accepts newlines?
It is better to take advantage of the value
transformer to return as value an object
describing the regexp:
REGEXP.value = (value) => {
let [source, flags] = value.split('/').slice(1);
return {
type: 'RegExp',
info: [ source, flags]
};
};
and inside the grammar we add a new production for the regexps:
expression ->
%STRING optProperties {% buildStringValue %}
| %NUMBER optProperties {% buildNumberValue %}
| %REGEXP optProperties {% buildRegexpValue %}
| ...
Now the section of the AST for the a regexp like:
def(r, r/(\w+) # word
\s+ # spaces
(\d+) # number
/x)
Has to look something similar to this:
{
"type": "apply",
"operator": { "type": "word", "name": "def" },
"args": [
{ "type": "word", "name": "r" },
{
"type": "value",
"value": {
"type": "RegExp",
"info": [
"(\\w+) # word\n \\s+ # spaces\n (\\d+) # number \n ",
"x"
]
},
}
]
},
To make it work from the interpreter side, we need to modify the j2a
entry for value
since it has to build a XRegExp (opens in a new tab) object from the info inside the AST:
j2a['value'] = (j) => {
let obj = new Value(j);
if (typeof obj.value === "object") {
obj.value = new topEnv[obj.value.type](...obj.value.info);
}
return obj;
};
Of course, we have added an entry to the associative memory topEnv
when the Egg Virtual Machine starts:
topEnv['null'] = null;
topEnv['true'] = true;
topEnv['false'] = false;
topEnv['undefined'] = undefined;
topEnv['RegExp'] = require('xregexp');
topEnv['fetch'] = require('node-fetch');
topEnv['fs'] = require('fs');
...
So that topEnv[obj.value.type]
is topEnv[RegExp]
and that contains the xregexp
object.
Example
Here is another example of use:
➜ egg-oop-parser-solution git:(master) ✗ cat examples/regexp.egg
(
def(d, r/
(?<year> \d{4} ) -? # year
(?<month> \d{2} ) -? # month
(?<day> \d{2} ) # day
/x),
print(d.test("1987-07-14")), # true
def(m, d.exec("1987-07-14")),
print(util.inspect(m, {depth: null})), /* [ '1987-07-14', '1987', '07', '14', index: 0, input: '1987-07-14', groups: undefined ] */
print(m.index), # 0
def(x, RegExp.exec("2015-01-22", d)),
print(util.inspect(x, {depth: null})),
print(x.year), # 2015
print(x.month) # 01
)
and this is a capture of one execution:
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/regexp
true
[
'1987-07-14',
'1987',
'07',
'14',
index: 0,
input: '1987-07-14',
groups: undefined
]
0
[
'2015-02-22',
'2015',
'01',
'22',
index: 0,
input: '2015-01-22',
groups: undefined,
year: '2015',
month: '01',
day: '22'
]
2015
01
Require
Expand the language with a require
expression to allow the use of libraries.
Review the video How to implement the "require" functionality
Here is a link to the Repo corresponding to the video (opens in a new tab).
In this exercise:
- Memoize libraries so they don't load twice
- Try to add this functionality without touching the main code using the strategy pattern + registry pattern
Example
Module code:
➜ egg-oop-parser-solution git:(master) ✗ cat examples/require/module.egg
# module. Exports z
do(
print("inside module"),
def(z, map(inc: ->(x,
+(x,1)
) # end fun
) # end map
), # end of def
z # el último valor será exportado
)
Client program:
➜ egg-oop-parser-solution git:(master) ✗ cat examples/require/client.egg
do(
def(z, require("examples/require/module.egg")),
print(z.inc(4)),
def(w, require("examples/require/module.egg"))
)
Here is an execution:
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/require/client
inside module
5
Notice how inside module
appears only once even though the module is required twice
For Loops
Extend the language with one or more types of for
loops
Conventional For Loop
Let us add a conventional for
loop:
[.../TFA-04-16-2020-03-22-00/davafons(casiano)]$ cat examples/for.egg
➜ davafons-tfa-1819-egg git:(casiano) ✗ cat examples/for.egg
do(
def(x, 7),
def(b,
for(define(x, 1), <(x, 5), ++(x),
print(x)
)
),
print(b),
print(x)
)
We want the following semantic for the for
loop:
- The
for
loop has to return the last evaluated expression - Create a new scope for the loop so that
x
inside the loop refers to a variable that is local to the loop
➜ davafons-tfa-1819-egg git:(casiano) ✗ bin/egg.js examples/for.egg
1
2
3
4
[ 4 ]
7
-
Notice that in this version of the Egg interpreter the function
print
returns an array with the values of the received arguments. Thus, the value returned by the loop is[ 4 ]
-
This version also implements the
++
operator++(x)
. Notice that this operator is a case ofleftEvaluate
since it implies modification of thex
variable
For Each Loop
We already have a loop to go through the iterable objects, since the generated JS objects have easy acces to their methods:
➜ eloquentjsegg git:(private2021) ✗ cat examples/for-js.egg
(
def(a, [4,3,2,1]),
a.forEach(
fun(x,i,ra,
print("Element",i,"of ",ra,"is",x)
)
)
)
When executed gives:
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/for-js
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
Tests
Add a test
folder and in it, add the
test programs like test/test.js
(Mocha or Jest, use whichever you prefer).
Continuous Integration
Use GitHub Actions para añadir CI al proyecto
To install Private Packages inside a GitHub Action review these sections:
GitHub Registry
Publish the extended interpreter as a module in the GH Registry in scope @ull-esit-pl-2324
.
Since this package contains executables, you should read the section bin (opens in a new tab) from the npm.js documentation on package.json
You have to add an entry like this one:
[~/.../crguezl-egg(master)]$ jq .bin package.json
{
"egg": "./bin/egg.js",
"evm": "./bin/evm.js"
}
Videos
En el curso 2022/2023
2023/05/02
Lab egg-oop-parser:
2023/05/03
Lab egg-oop-parser y parte de como se extiende el intérprete:
2023/05/08
Lab extended Egg Interpreter:
En el curso 2021/2022
2022/05/03
Extending Egg: evaluate for the Property Class, Negative Indices, Monkey Patching, Currying, Intro to leftEvaluate
2022/05/04
Extending the Egg Interpreter: fromList2AST, buildXXX, set and leftEvaluate
2022/05/09
Exam example: parsing JSON and translating Egg ASTs to term notation. Extending the Egg Interpreter: Hashes, Objects
2022/05/10
Martes 2022/05/10. TFA. Extending the Egg Interpreter: Objects, RegExps, On the properties of Code
2022/05/11
Miércoles 2022/05/11. Extending Egg: For loops. TFA: Async Programming in Egg. Translation from Egg to JS
Building a Repeat Evaluate Print Loop
Vídeo Programando un bucle REPL para el lenguaje Egg (opens in a new tab)
Require
Rubric
extended-egg Repos
Resources
-
latest-version-cli (opens in a new tab) Get the latest version of an npm package
-
Egg Virtual Machine with OOP extensions for Windows/Linux/Mac OS (opens in a new tab)
-
Eloquent JS: Chapter 11. Project: A Programming Language (opens in a new tab)
-
Módulo en npm @crguezl/eloquentjsegg (opens in a new tab)
-
El lenguaje egg: repo en GitHub (opens in a new tab). It contains a PDR parser and a solution to the problems of separating the lexical analyzer from the PDR parser as well as the problem of separating the codes and the three executables. It also has sample tests on Mocha and Chai
-
NodeJS Readline gist (opens in a new tab): a simple gist that teaches you how to use
readline
to do an interactive loop. You might want to read it when you get to the REPL problem section. -
Video How to implement the "require" functionality
-
En el repo ULL-ESIT-PL-1617/interpreter-egg (opens in a new tab) se muestra como hacer un bucle REPL
-
Vídeo Programando un bucle REPL para el lenguaje Egg (opens in a new tab)
-
XRegExp (opens in a new tab): Un módulo que provee regexp extendidas
-
Tests. Enlaces sobre Mocking and Stubbing
-
VSCode Extension Egg Tools: Adds syntax highlighting and code snippets for the Egg language by EloquentJS (opens in a new tab)