- Give an example that showcases all rules of your EBNF. The program should "do" something as used in the next excercise.
LitType = "bool" | "i32" ;
- If you support pointers, make sure your example covers pointers as well.
(* Used to help with printing when debugging *)
_String = """, Identifier, """ ;
Bool = "true" | "false" ;
Var = Identifier ;
Identifier: = [a-zA-Z][a-zA-Z0-9_]* ;
Num = [0-9]+ ;
```
- Give an example that showcases all rules of your EBNF. The program should "do" something as used in the next exercise.
```rust
fnfibonacci(n:i32)->i32{
if(n==0){
return0;
}elseif(n==1){
return1;
}else{
returnfibonacci(n-2)+fibonacci(n-1);
}
return0;
}
fnmain(){
letfib_20:i32=fibonacci(20);
if(fib_20==6765){
print("fib_20_is_6765");
}
if(fib_20>7000){
print("fib_20_LT_7000");
}else{
print("fib_20_GT_7000");
}
letmutand_res:bool=and_op(true,true);
while(and_res){
and_res=false;
}
print("and_res_after_while_loop");
print(and_res);
letunary_res:i32=unary_op(1);
if(unary_res==-1){
print("unary_ok");
}else{
print("unary_failed");
}
letprec_test:i32=prec_test();
if(prec_test==27){
print("prec_ok");
}else{
print("prec_failed");
}
}
fnand_op(a:bool,b:bool)->bool{
returna&&b;
}
fnunary_op(x:i32)->i32{
return-x;
}
fnprec_test()->i32{
return2*10-3+2*5;
}
```
- Compare your solution to the requirements (as stated in the README.md). What are your contributions to the implementation.
- Compare your solution to the requirements (as stated in the README.md). What are your contributions to the implementation.
The parser was implemented using the [LALRPOP](https://github.com/lalrpop/lalrpop) parser generator. By following the LALRPOP tutorial, to learn the syntax and some of the functionality, an expression parser was implemented. Thus I didn't implemented the expression parser myself but I built upon it and extended it to implement the rest of the parser for my language.
When it comes precedence of the expression parser the tutorial already had it implemented for numerical expressions. When I extended the parser to support logical and relational operations the precedence of those operations works if you don't mix logical and relation operations together. So for the most part in my language sub expressions have to be parenthesized if they contain those operations.
## Your semantics
No error messages, except the already existing error messages from LALRPOP, have been implemented in the parser. And thus no error recovery is possible since the parser just forwards the LALRPOP error message and stops if an error occurs while parsing the program.
# Your semantics
- Give an as complete as possible Structural Operetional Semantics (SOS) for your language
- Give an as complete as possible Structural Operetional Semantics (SOS) for your language
### Operators
Given a expression $e$ in the state $\sigma$ that evaluates to a value $n$ or boolean $b$ the following can be said for the operators in the language
$\frac{}{\lang x := n, \sigma \rang \ \Downarrow \ \sigma [x := n]}$
functions: \
Given a list of arguments $\overrightarrow{v} = [v_1, \ldots, v_n]$ the call of function $p$ will evaulate to a value $n$ (the return value, if any) according to
- Explain (in text) what an interpretation of your example should produce, do that by dry running your given example step by step. Relate back to the SOS rules. You may skip repetions to avoid cluttering.
- Explain (in text) what an interpretation of your example should produce, do that by dry running your given example step by step. Relate back to the SOS rules. You may skip repetions to avoid cluttering.
- Compare your solution to the requirements (as stated in the README.md). What are your contributions to the implementation.
The example program will be started by calling the main function. The first line is an assignment of variable `fib_20` which according to the SoS will be assigned the value of the function call `fibonacci(20)`. Thus the function call is evaluated and assigned to the variable. When the call is made the argument is `n = 20` which according to the SoS for equals (==) will evaluate to false for both the if- and elseif-condition inside the function body. Because of this the else-statement is executed and the recursion continues. After the recursion have reached the base case the function will return a `i32`. The variable `fib_20` now exists inside the scope of the main functions context.
## Your type checker
The condition of the next if-statement is now evaluated and since `fib_20 = 6765` in the scope the condition will evaluate to true according to the SoS and the print command inside the if-body is executed. Since `fib_20` not is greater than 7000 the else-statement is executed in the next if-else statement.
- Give an as complete as possible set of Type Checking Rules for your language (those rules look very much like the SOS rules, but over types not values).
After this a new mutable variable `and_res` is declared. The variable is assigned the value of the function call. The while-condition is then checked and since `and_res = true` the body is executed according to the SoS. In the body the variable is assigned a new value, which is fine since it's declared as mutable, but since the new value is false the while-condition will not evaluate to true and the while-body is not executed again. The value of `and_res` is then printed out to ensure that the value of the mutable variable was changed.
- Demonstrate each "type rule" by an example.
This continues on to cover more of the EBNF but the operations is mostly repetition so I've skipped explaining them.
- Compare your solution to the requirements (as stated in the README.md). What are your contributions to the implementation.
- Compare your solution to the requirements (as stated in the README.md). What are your contributions to the implementation.
## Your borrrow checker
I implemented the interpreter myself and the interpreter executes programs according to the SOS defined above. Additionally it can also handle else-if statements, scoping and contexts (different variable values depending on the scope and context etc.).
- Give a specification for well versus ill formed borrows. (What are the rules the borrow checker should check).
The interpreter also panics when encountering a evaluation error. For example, if the program contains
```rust
letx:i32=true+false;
```
the interpreter will panic with the message:
```rust
"Left or right part of number expression not a number found: left = Value(Bool(true)) and right = Value(Bool(false))"
```
- Demonstrate the cases of ill formed borrows that your borrow checker is able to detect and reject.
In the following examples any integers and booleans could be replaced by arbitrary expression which evaluates to the same type and the result would be the same.
### Assignments
If variable `x` and `y` has been defined as a `i32`
```rust
x = 10 // ok
x = y // ok
x = true // error
```
### **let** assignment
```rust
let x: i32 = 10; // ok
let x: i32 = true; // error
let y: bool = true; // ok
let y: bool = 5; // error
```
### **while** statement
```rust
while(true) { // ok
...
}
while(5) { // error
...
}
```
### **if/elseif** statements
```rust
if (true) { // ok
...
}
if (5) { // error
...
}
if (false) {
...
} else if (true) { // ok
...
}
if (false) {
...
} else if (123) { // error
...
}
```
### Functions
```rust
fn int_func() -> i32 { // ok
return 4;
}
fn int_func() -> i32 { // error
return true;
}
fn bool_func() -> bool { // ok
return false;
}
fn bool_func() -> bool { // error
return 5;
}
```
#### Parameters and arguments
```rust
fn param_func(a: i32, b: bool) {
...
}
param_func(5, true); // ok
param_func(true, true); // error
param_func(5, 5); // error
param_func(true, 4); // error
param_func(4); // error
```
- Compare your solution to the requirements (as stated in the README.md). What are your contributions to the implementation.
- Compare your solution to the requirements (as stated in the README.md). What are your contributions to the implementation.
## Overal course goals and learning outcomes.
The type checker was fully implemented by me and it rejects ill-typed programs according to the above type checking rules.
It is also able to report multiple type errors while at the same time not report other errors originating from an earlier detected error. This was done by maintaining a vector containing all the detected type errors during the type checking of the program. Then every time a type error is detected the error is pushed onto the vector and the type checker determines, if possible, the type that would have resulted if the expression was correctly typed. To demonstrate this, consider the following code
```rust
let a: bool = 5 + 5;
let b: bool = a && true;
let c: i32 = b + 1;
```
running this code snippet will generate the following result
Comment on the alignment of the concrete course goals (taken from the course description) to the theory presented, work You have done and knowledge You have gained. (I have put some comments in [...]).
```
Error: Mismatched type for variable 'a'
Note: expected bool, found i32
Error: Binary operation '+' cannot be applied to type 'bool'
```
By this it can be seen that the type checker assumes that the type of ```a``` is correct when checking the assignment of ```b``` which indeed would be correctly typed if the former variable would have been correctly assigned a boolean value. Thus by following this pattern the type checker is able to continue type checking and reporting new errors, which can be seen when it reports that the assignment of ```c``` is incorrect (addition with boolean is not possible).
- Lexical analysis, syntax analysis, and translation into abstract syntax.
The type checker also makes sure that functions with a specified return type always returns. This is done by going through each function and making sure that a tailing return statement or a return inside an else-statement exists. If this is not the case the error is reported along with all other type errors found.
# Your borrow checker
- Give a specification for well versus ill formed borrows. (What are the rules the borrow checker should check).
- Regular expressions and grammars, context-free languages and grammars, lexer and parser generators. [Nom is lexer/parser library (and replaces the need for a generator, while lalr-pop is a classical parser generator)]
The borrow checker needs to make sure that a borrow lasts for a scope no greater than the owners scope by keeping track of lifetimes which describe the scope that a reference is valid for.
- Identifier handling and symbol table organization. Type-checking, logical inference systems. [SOS is a logical inference system]
It also needs to keep track of the kind of borrow that have been made to a resource, since different kinds of borrows of the same resource cannot be made at the same time. The two kinds of borrows are references (&T) and mutable references (&mut T). If a reference has been made one or more references are allowed, but if it is a mutable reference exactly one is allowed in order to assure that no data race occurs.
- Intermediate representations and transformations for different languages. [LLVM is a cross language compiler infrastructure]
**I did not implement a borrow checker for my language so the next rest of the questions regarding the borrow checker could not be answered.**
- Code optimization and register allocation. Machine code generation for common architectures. [LLVM is a cross target compiler infrastructure, doing the "dirty work" of optimazation/register allocation leveraging the SSA form of the LLVM-IR]
# Your LLVM backend
TODO
Comment on additional things that you have experienced and learned throughout the course.