Adding Loops to the Calculator

Adding Loops to the Calculator

The while loop

Queremos extender nuestra calculadora para que soporte bucles while con una sintáxis como la de este ejemplo:

test/data/input/while-sum.calc
sum = a = 0,
while a < 10 {
  sum = sum + a * a,
  a = a +1
},
print(sum) # 285

The while loop in our calculator has the following semantic:

  1. The while loop must be an expression and it returns the value of the last expression in the block.
  2. The block does not create a new scope.
  3. If the block is empty or the block in the loop is never executed, the value of the while loop is false.

Notice the difficulty that arises on our translation since the JS while loop does not return a value.

When our transpiler is called with the above input, a JS program equivalent to this should be generated:

test/data/expectedjs/while-sum.js
✗➜  functions-solution git:(while) bin/calc2js.mjs test/data/input/while-sum.calc 
#!/usr/bin/env node
  const { Complex, print } = require("/Users/casianorodriguezleon/campus-virtual/2223/pl2223/practicas/functions/functions-solution/src/support-lib.js"); 
/* End of support code */
 
let $sum, $a;
 
(  $sum = $a = Complex("0"), 
   (() => {
    let result = false;
    while ($a.lessThan(Complex("10"))) {
        result = ($sum = $sum.add($a.mul($a)), $a = $a.add(Complex("1")));
    }
 
    return result;
    })()
), 
print($sum);

That when executed gives:

➜  functions-solution git:(while) bin/calc2js.mjs test/data/input/while-sum.calc | node
{ re: 285, im: 0 }

The for loop

We also want to add a for loop to our calculator. The syntax will be like this:

➜  functions-solution git:(while) cat test/data/input/for-loop-nested-write.calc 
for(a = 0; a < 5; a = a+1) {
  write("a = "; a),
  for(b = 0; b < a; b = b+1) {
    write("  b = "; b)
  }
}

The for loop in our calculator has the following semantic:

  1. The forloop must be an expression and it returns the value of the last expression in the block.
  2. The block does not create a new scope.
  3. If the block is empty or the block in the loop is never executed, the value of the for loop is false.

In this lab you have also to extend the language with double quote delimited strings like the example "a = " above. Extend the write and print functions to accept multiple arguments separated by semicolons.

When compiled, the former program should produce a JS program like this:

test/data/expectedjs/for-loop-nested-write.js
➜  functions-solution git:(while) bin/calc2js.mjs test/data/input/for-loop-nested-write.calc 
#!/usr/bin/env node
const { Complex, write } = require("/Users/casianorodriguezleon/campus-virtual/2223/pl2223/practicas/functions/functions-solution/src/support-lib.js"); 
  
/* End of support code */
let $a, $b;
 
(() => {
    let result = false;
 
    for ($a = Complex("0"); $a.lessThan(Complex("5")); $a = $a.add(Complex("1"))) {
        result = (write("a = ", $a), 
        (() => {
            let result = false;
 
            for ($b = Complex("0"); $b.lessThan($a); $b = $b.add(Complex("1"))) {
                result = write("  b = ", $b);
            }
 
            return result;
        })());
    }
 
    return result;
})();

When executed, it should produce:

➜  functions-solution git:(while) bin/calc2js.mjs test/data/input/for-loop-nested-write.calc | node 
a =  0
a =  1
  b =  0
a =  2
  b =  0
  b =  1
a =  3
  b =  0
  b =  1
  b =  2
a =  4
  b =  0
  b =  1
  b =  2
  b =  3

Strings

Add strings to the language. Allow the + operator to be used to concatenate strings:

test/data/input/string-add.calc
for(a = 0; a < 5; a = a+1) {
  write("a = " + a),
  for(b = 0; b < a; b = b+1) {
    write("  b = " + b)
  }
}  

That can be translated to:

test/data/actualjs/string-add.js
➜  functions-solution git:(while) bin/calc2js.mjs test/data/input/string-add.calc                   
#!/usr/bin/env node
const { Complex, write } = require("/Users/casianorodriguezleon/campus-virtual/2223/pl2223/practicas/functions/functions-solution/src/support-lib.js"); 
/* End of support code */
let $a, $b;
 
(() => {
    let result = false;
 
    for ($a = Complex("0"); $a.lessThan(Complex("5")); $a = $a.add(Complex("1"))) {
        result = (write("a = ".add($a)), (() => {
            let result = false;
 
            for ($b = Complex("0"); $b.lessThan($a); $b = $b.add(Complex("1"))) {
                result = write("  b = ".add($b));
            }
 
            return result;
        })());
    }
 
    return result;
})();

and when executed produces the expected output:

➜  functions-solution git:(while) bin/calc2js.mjs test/data/input/string-add.calc | node
a = 0
a = 1
  b = 0
a = 2
  b = 0
  b = 1
a = 3
  b = 0
  b = 1
  b = 2
a = 4
  b = 0
  b = 1
  b = 2
  b = 3

Plus Operator and Strings

The following program:

test/data/input/string-string.calc
➜  functions-solution git:(while) cat test/data/input/string-string.calc        
print("a is "+ 4+2i),       # string + number
print("a is "+ (4+2i)),     # string + number
print("a is "+ "a letter"), # string + string
print("a is "+ true),       # string + boolean
print("a is "+ fun() { 1 }) # string + function

must produce an output like this:

➜  functions-solution git:(while) bin/calc2js.mjs test/data/input/string-string.calc | node
a is 42i
a is 4 + 2i
a is a letter
a is true
a is function() {
    return Complex("1");
}

Notices how you have to parenthesize the (4+2i) to get the expected result. Our + operator for strings is not associative!

Symmetry

Consider again the symmetry problem for the + operator on strings. What about these expressions?:

4+2i+" is a complex number"
true+" is a boolean"
fun() { 1 }+" is a function"

Consider the opportunity to extend the other operators -, *, /, etc. to strings. Does it make any sense to do so?

🚫

Think about the symmetry of the + operator when you add a function and a string

It makes sense to add a function and a string like

(fun(x) { x+1 }+"hello")(2) = 3 + "hello" = "3hello"

The idea being that the string "hello" is promoted to the constant function
"hello" and the + promotes the 3 to string "3" and then concatenates the two strings.

Here is an actual example:

test/data/input/function-plus-string.calc
print((fun(x) { x+1 }+"hello")(2)) # "3hello"

that when compiled produces:

test/data/expectedjs/function-plus-string.js
➜  functions-solution git:(while) ✗ bin/calc2js.mjs test/data/input/function-plus-string.calc 
#!/usr/bin/env node
const { print, Complex } = require("/Users/casianorodriguezleon/campus-virtual/2223/pl2223/practicas/functions/functions-solution/src/support-lib.js"); 
/* End of support code */
print(function($x) { return $x.add(Complex("1")); }.add("hello")(Complex("2")));

That with the appropriate extensions to the Function and Complex JS classes may lead to the following execution:

➜  functions-solution git:(while) ✗ bin/calc2js.mjs test/data/input/function-plus-string.calc | node
3hello

Notice that we previously said that "a is "+ fun() { 1 } can be interpreted as the string resulting from concatenating the string " a is" with the source of the function.

Thus, a string plus a function is a string but a function plus a string is a function. Can be considered this a break in the symmetry of the + operator?

Think also about the relation of strings and comparison operators <, >, <=, >= and == etc.

Be sure to produce appropriate error messages when the operators are used with incompatible types.

Videos

2024/03/20

Clase del 20/03/2024. Your workflow when adding a new feature to a translator

Clase del 13/03/2024. Building the Egg AST for 'ID apply'. Adding loops to the calculator language

Adding loops to the calculator language. Building a lexer generator

Pruebas, Covering e Integración Continua

Escriba las pruebas, haga un estudio de cubrimiento usando c8 (opens in a new tab) y añada integración continua usando GitHub Actions.

Lea las secciones Testing with Mocha y Jest.

Avoiding the preamble in the tests

In your template for the code generation, use a special string like \n/* End of support code */\n\n to mark the end of the preamble and the beginning of the generated code.

const { {{ dependencies }} } = require("{{root}}/src/support-lib.js");
 
/* End of support code */
 
{{code}}

Then in your tests, use a funtion like the following to remove the preamble by using a regexp that removes from the beginning to the from the generated code:

function removeDependencies(s) {
  const REGULAR_SEPARATION = /^(.|\n)*\n\/\* End of support code \*\/\n\n/
  const pruned = s.replace(REGULAR_SEPARATION, '')
 
  return removeSpaces(pruned);
}

then you can use it in your tests removing the preamble from both the expected and the actual output:

for (let i = 0; i < Test.length; i++) {
  it(`transpile(${Tst[i].input}, ${Tst[i].actualjs}) (No errors: ${Boolean(Tst[i].expectedout)})`, async () => {
    let actualjs = await transpile(Test[i].input, Test[i].actualjs);  
    let expectedjs = fs.readFileSync(Test[i].expectedjs, 'utf-8')
 
    let trimActualJS = removeDependencies(actualjs)
    let trimExpectedJS = removeDependencies(expectedjs)
    assert.equal(trimActualJS, trimExpectedJS);
  });
}

Publishing a package to the GitHub Registry

See the chapter Publishing a package to the GitHub Registry and the sections

One way to set GitHub as the registry is to add a publishConfig field to your package.json file

package.json
{
  "name": "@ull-esit-pl-2324/functions-name-ape1-ape2-aluXXX",
  "version": "1.2.0",
  "description": "A lab for PL. Adding Functions to our Calculator",
  "main": "src/transpile.js",
  "bin": {
    "calc2js": "./bin/calc2js.js"
  },
  "publishConfig": {
    "registry": "https://npm.pkg.github.com"
  },
  ...
}

I noticed many of you are having a hard time trying to publish the module to the GitHub Package Registry.

This class 2023/03/14 from 18:03 to to 35:00 may help to overcome the gotchas when publishing a private npm module in the github package registry.


  • Start to talk about publishing a module at minute 18:03.
  • How to get the GitHub token at minute 19:58
  • Providing the token to the npm client at minute 23:00
  • Issuing npm publish at minute 23:52
  • Associating the GitHub Organization ULL-ESIT-PL-2324 as a scope to the github registry at minute 28:13
  • From minute 34:25 and on, we discuss how to build a web site using a static generator like Vuepress and how to deploy it to GitHub Pages.

Documentación

Documente el módulo incorporando un README.md y la documentación de la función exportada usando JsDoc. Lea la sección Documenting the JavaScript Sources

Rubric
















while Repos

References

  • COMP442/6421 - Compiler Design - week 8 - AST traversal using the Visitor pattern. Concordia University, Montreal , Canada. 2022

  • Compiler Design Module 34 : Semantic Analysis Introduction to Scope. IITI Delhi. Compiler AI Labs. 2020

  • Compiler Design Module 35 : Semantic Analysis as Recursive Descent over AST. IITI Delhi. Compiler AI Labs. 2020

  • ast-types: See the scope section
  • recast

Polymorphism

See the notes by Casiano Rodríguez León. 2011:

  1. Sobrecarga, Polimorfismo e Inferencia de Tipos (opens in a new tab)
  2. Expresiones de Tipo, Sistemas de Tipos y Comprobadores de Tipos (opens in a new tab)]
  3. Equivalencia de Expresiones de Tipo (opens in a new tab)
  4. Un Lenguaje con Funciones Polimorfas (opens in a new tab)