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) # 285
The while
loop in our calculator has the following semantic:
- The
while
loop 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
while
loop 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
for
loop 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
for
loop 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 = 3
Strings
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 = 3
Plus 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 + 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:
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
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 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 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: