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:
sum = a = 0,
while a < 10 {
sum = sum + a * a,
a = a +1
},
print(sum) # 285The while loop in our calculator has the following semantic:
- The
whileloop must be an expression and it returns the value of the last expression in the block. - The block does not create a new scope.
- If the block is empty or the block in the loop is never executed, the value of the
whileloop isfalse.
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:
✗➜ 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:
- The
forloop must be an expression and it returns the value of the last expression in the block. - The block does not create a new scope.
- If the block is empty or the block in the loop is never executed, the value of the
forloop isfalse.
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:
➜ 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 = 3Strings
Add strings to the language. Allow the + operator to be used to concatenate strings:
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:
➜ 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 = 3Plus Operator and Strings
The following program:
➜ 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 + functionmust 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:
print((fun(x) { x+1 }+"hello")(2)) # "3hello"that when compiled produces:
➜ 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
3helloNotice 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 translatorPruebas, 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
- Configure npm
- What are scopes?
- What is Github Registry?
- Other ways to set the Scope (opens in a new tab)
One way to set GitHub as the registry is to add a publishConfig field to your package.json file
{
"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 publishat 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: