# (Optional TFA 2)Extending the Egg Interpreter
# Planificación 22/23
Planificación del final de la asignatura
Entrega de TFA Martes 9, Miércoles 10 y Jueves 11 de Mayo de 2023
Se puede entregar como TFA las prácticas Extending the Egg Syntax (TFA-1) y Extending de Egg Interpreter (TFA-2) y/o hacer propuestas alternativas como las sugeridas en TFA. La mayor parte de las propuestas en TFA asumen que TFA-1 y TFA-2 han sido ya realizadas. Si se decide por una propuesta alternativa, consulte a los profesores para verificar su validez.
La revisión del TFA tendrá lugar los días Martes 9 (PE103), Miércoles 10 (PE102) y jueves 11 de Mayo (PE101) en horario de clase. Es conveniente que el Martes 9 y el Miércoles 10 acuda con el portátil a clase. Si desea hacer una exposición de su trabajo ante la clase comuníqueselo a los profesores con antelación.
- Extending the Egg Syntax (TFA-1)
- Extending de Egg Interpreter (TFA-2s)
- TFA
# 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)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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;
}
2
3
4
5
6
Another is to do Monkey Patching in the object class.
# Monkey Patching
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.
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
)
2
3
4
5
6
7
8
➜ egg-oop-parser-solution git:(master) bin/egg examples/curry-method
12
12
60
1
1
2
3
4
5
6
# Currying
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 new window) on arg1, ...
.
For instance:
✗ cat examples/curry-no-method.egg
print(+[4](2))
2
In Egg +
is a function that takes an arbritrary number of numbers:
and returns its sum. The curried
Execution:
✗ bin/eggc.js examples/curry-no-method.egg
✗ npx evm examples/curry-no-method.json
6
2
3
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
2
3
4
5
6
7
We have added an attribute numParams
to the Egg Function objects that returns the number of parameters in its declaration.
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 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
2
This is another example of currying in Egg:
➜ 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
)
2
3
4
5
6
7
8
The ambiguities that arise in the expression 4.+
are discussed in section The Dot Ambiguity: Property dot or Mantissa dot?.
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/curry-method
12
12
60
1
1
2
3
4
5
6
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 other than the ambiguity that arise in the expression 4.+
(See section Property dot or Mantissa dot?).
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
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(`...`);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 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)
)
2
3
4
5
6
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/set-simple
[[1,{"x":3}],3]
2
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;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 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), ... ]?
}
...
}
2
3
4
5
6
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
)
2
3
4
5
6
7
8
9
10
11
That when executed produces:
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/specialform-property-4
["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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 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()})
)
2
3
4
5
6
7
8
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 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}
)
2
3
4
5
6
7
8
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/map-colon-leftside
{"x":4,"y":{"z":3}}
3
{"z":50}
2
3
4
# 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]
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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]
2
3
4
5
6
7
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 new window) for an alternative solution using only the object as environment:
const obj = Object.create(env); // {}; // Object.create(null);
obj["this"] = obj;
2
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
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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)
)
2
3
4
5
6
7
8
9
10
11
12
13
The x
option is an extension introduced by XRegExp (opens new window) (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
2
3
4
5
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]
};
};
2
3
4
5
6
7
and inside the grammar we add a new production for the regexps:
expression ->
%STRING optProperties {% buildStringValue %}
| %NUMBER optProperties {% buildNumberValue %}
| %REGEXP optProperties {% buildRegexpValue %}
| ...
2
3
4
5
Now the section of the AST for the a regexp like:
def(r, r/(\w+) # word
\s+ # spaces
(\d+) # number
/x)
2
3
4
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"
]
},
}
]
},
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 new window) 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;
};
2
3
4
5
6
7
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');
...
2
3
4
5
6
7
8
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
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 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 new window).
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
)
2
3
4
5
6
7
8
9
10
11
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"))
)
2
3
4
5
6
Here is an execution:
➜ egg-oop-parser-solution git:(master) ✗ bin/egg examples/require/client
inside module
5
2
3
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)
)
2
3
4
5
6
7
8
9
10
11
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
2
3
4
5
6
7
8
➜ davafons-tfa-1819-egg git:(casiano) ✗ bin/egg.js examples/for.egg
1
2
3
4
[ 4 ]
7
2
3
4
5
6
7
8
Notes
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)
)
)
)
2
3
4
5
6
7
8
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
2
3
4
5
# 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-2122
.
Since this package contains executables, you should read the section bin (opens new window) 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"
}
2
3
4
# Resources
latest-version-cli (opens new window) Get the latest version of an npm package
Egg Virtual Machine with OOP extensions for Windows/Linux/Mac OS (opens new window)
Eloquent JS: Chapter 11. Project: A Programming Language (opens new window)
Módulo en npm @crguezl/eloquentjsegg (opens new window)
El lenguaje egg: repo en GitHub (opens new window). 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 new window): 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 new window) se muestra como hacer un bucle REPL
Vídeo Programando un bucle REPL para el lenguaje Egg (opens new window)
XRegExp (opens new window): 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 new window)