# (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.

# 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) 
  }
}
1
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;
}
1
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
1
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
)
1
2
3
4
5
6
7
8
➜  egg-oop-parser-solution git:(master) bin/egg examples/curry-method 
12
12
60
1
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"
1

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))
1
2

In Egg + is a function that takes an arbritrary number of numbers:

and returns its sum. The curried is the function defined by .

Execution:

✗ bin/eggc.js examples/curry-no-method.egg
✗ npx evm examples/curry-no-method.json   
6
1
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
1
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
1
print(+.4(2))
1
➜  egg-oop-parser-solution git:(master) ✗ bin/egg examples/curry-no-method-dot 
6
1
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
)
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
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(`...`);
  }
1
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)
)
1
2
3
4
5
6
➜  egg-oop-parser-solution git:(master) ✗ bin/egg examples/set-simple
[[1,{"x":3}],3]
1
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;
}
1
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), ... ]?
  }
  ...
}
1
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
)
1
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
1
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()})
)
1
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
1
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}
)
1
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}
1
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]
)
1
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]
1
2
3
4
5
6
7

To add objects to Egg, you can follow these instructions:

  1. Add the corresponding function entry to specialForms["object"]
  2. Create a new environment objEnv for the object having as parent the current environment
  3. Create the object obj as a JS object so that it has all the properties of JS objects
  4. Add self to the object environment objEnv. Has to reference the just created object obj
  5. Traverse the args ASTs (has to be a forest with an even number of trees) taking the key value pair on each step
  6. Evaluate the pairs key, value in the context of the object environment objEnv updating both the object entry and the object environment objEnv entry
  7. 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;
1
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
)
1
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
1
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)
)
1
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
1
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)?)/;
1

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]
  };
};
1
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 %}
    | ...
1
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)
1
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"
            ]
          },
        }
      ]
    },
1
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;
};
1
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');
...
1
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
)
1
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
1
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
)
1
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"))
)
1
2
3
4
5
6

Here is an execution:

➜  egg-oop-parser-solution git:(master) ✗ bin/egg examples/require/client
inside module
5
1
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
1
➜  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)
)
1
2
3
4
5
6
7
8
9
10
11

We want the following semantic for the for loop:

  1. The for loop has to return the last evaluated expression
  2. Create a new scope for the loop so that x inside the loop refers to a variable that is local to the loop
  3. ➜  davafons-tfa-1819-egg git:(casiano) ✗ bin/egg.js examples/for.egg
    
    1
    2
    3
    4
    [ 4 ]
    7
    
    1
    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
1
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 of leftEvaluate since it implies modification of the x 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 
1
(
  def(a, [4,3,2,1]),
  a.forEach(
    fun(x,i,ra, 
      print("Element",i,"of ",ra,"is",x)
    )
  )
)
1
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
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
1
{
  "egg": "./bin/egg.js",
  "evm": "./bin/evm.js"
}
1
2
3
4

# Resources

Grading Rubric#

Comments#

Last Updated: 2 months ago