Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • home_exam
  • master
2 results

Target

Select target project
No results found
Select Git revision
  • crane_lift
  • expression
  • fallible
  • generics_and_traits
  • master
  • rust_syntax
  • spans
  • stacked_borrows
  • type_check
9 results
Show changes

Commits on Source 69

22 files
+ 1910
563
Compare changes
  • Side-by-side
  • Inline

Files

.vscode/settings.json

deleted100644 → 0
+0 −10
Original line number Diff line number Diff line
{
    "cSpell.ignoreWords": [
        "intrinsics",
        "lalrpop",
        "lalrpop mod",
        "llvm",
        "mod"
    ],
    "rust-analyzer.inlayHints.enable": true
}
 No newline at end of file
+2 −27
Original line number Diff line number Diff line
# Changelog for the repository

## 2020-10-18

- Update with HOME_EXAM.md

## 2020-09-03-2020-10-18

- Added various branches

  - generics_and_traits, showcasing how specialization (monomorphization) is possible for generics and traits in Rust/Rust like languages.

  - rust_syntax, a succinct grammar for a subset of Rust, loosely based on [Chapter 8](https://doc.rust-lang.org/reference/statements-and-expressions.htm)

  - type_check, showcasing how you can use matching over algebraic data types

  - spans, showcasing how you extract span information

  - fallible, fallible iterators allowing to iterate while condition holds

## 2020-09-03

- Updated ast with Display

## 2020-09-03

- Added src/comment 
    - single and multiline comments

- Added src/ast
    - abstract syntax tree example

## 2020-08-31

- Initial README.md
- Setup simple example
 No newline at end of file
+2 −2
Original line number Diff line number Diff line
@@ -293,9 +293,9 @@ dependencies = [

[[package]]
name = "lalrpop-util"
version = "0.19.0"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7e88f15a7d31dfa8fb607986819039127f0161058a3b248a146142d276cbd28"
checksum = "6771161eff561647fad8bb7e745e002c304864fb8f436b52b30acda51fca4408"
dependencies = [
 "regex",
]
+6 −6
Original line number Diff line number Diff line
@@ -17,13 +17,13 @@ regex = "1.3.9"
# lalrpop = {version = "0.19.0", features = ["lexer"] }

[[bin]]
name = "minimal"
path = "src/minimal/main.rs"
name = "compiler"
path = "src/compiler.rs"

[[bin]]
name = "comments"
path = "src/comments/main.rs"
name = "interpreter"
path = "src/interpreter.rs"

[[bin]]
name = "ast"
path = "src/ast/main.rs"
name = "typechecker"
path = "src/typechecker.rs"
+627 −42
Original line number Diff line number Diff line
# Home Exam D7050E

- Fork this repo and put your answers (or links to answers) in THIS file.

## Your repo

- Link to your repo here:
- Link to your repo here: https://gitlab.henriktjader.com/soderda/d7050e_2020/-/tree/home_exam

## Your syntax

- Give an as complete as possible EBNF grammar for your language.

- Give an example that showcases all rules of your EBNF. The program should "do" something as used in the next exercise.

- For your implementation, show that your compiler successfully accepts the input program.

- Give a set of examples that are syntactically illegal, and rejected by your compiler.

- Compare your solution to the requirements (as stated in the README.md). What are your contributions to the implementation.
> - Give an as complete as possible EBNF grammar for your language.

Program
```ebnf
    : Func Program
    | Func
    ;
```
Func
```ebnf
    : "fn" Id Params "->" Type Block
    | "fn" Id Params Block
    ; 
```
Params
```ebnf
    : "()"
    | "(" (Param, ",")+ ")"
    ;
```
Param
```ebnf
    : "mut" Id ":" Type
    | Id ":" Type
    ;
```
Block
```ebnf
    : "{" StmtSeq* Stmt "}"
    | "{" StmtSeq* "}"
    ;
```
StmtSeq
```ebnf
    : ";"
    | Stmt ";"
    | StmtBlock
    ;
```
Stmt
```ebnf
    : "let" "mut" Id ":" Type "=" Expr
    : "let" "mut" Id "=" Expr
    : "let" "mut" Id ":" Type
    : "let" "mut" Id
    : "let" Id ":" Type "=" Expr
    : "let" Id "=" Expr
    : "let" Id ":" Type
    : "let" Id
    : Expr "=" Expr
    : Expr1
    ;
```
StmtBlock
```ebnf
    : "while" Expr Block
    | "if" Expr Block "else" Block
    | "if" Expr Block 
    | Block
    ;
```
Expr
```ebnf
    : ExprBlock
    | Expr1
    ;
```
ExprBlock
```ebnf
    : "if" Expr Block "else" Block
    | Block
    ;
```
Expr1
```ebnf
    : Expr1 LoCode Expr2
    | Expr1 ArCode Expr2
    | Expr2
    ;
```
Expr2
```ebnf
    : Expr2 FactorCode Lit
    | Lit
    ;
```
Lit
```ebnf
    : Id Exprs
    | ArCode Lit
    | "!" Lit
    | Bool
    | Num
    | Id
    | "&" Lit
    | "&mut" Lit
    | "*" Lit
    | "(" Expr ")"
    ;
```
Exprs
```ebnf
    : "()"
    | "(" (Expr1 "," )+ ")"
    ;
```
ArCode
```ebnf
    : "+"
    | "-"
    ;
```
FactorCode
```ebnf
    : "*"
    | "/"
    ;
```
LoCode
```ebnf
    : "&&"
    | "||"
    | "=="
    | "!="
    | ">"
    | "<"
    | ">="
    | "<="
    ;
```
Type
```ebnf
    : "bool"
    | "i32"
    | "()"
    | "&" Type
    | "&mut" Type
    ;
```
Bool
```ebnf
    : "true"
    | "false"
    ;
```
Num
```ebnf
    : "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" 
```
Id
```ebnf
    : (LowerL | UpperL | "_")+ (LetterLower | LetterUpper | Num | "_")*
    ;
```
LowerL
```ebnf
    : "a" | "b" | "c" | "d" | "e" | "f" | "g" 
    | "h" | "i" | "j" | "k" | "l" | "m" | "n"
    | "o" | "p" | "q" | "r" | "s" | "t" | "u"
    | "v" | "w" | "x" | "y" | "z" 
```

UpperL
```ebnf
    : "A" | "B" | "C" | "D" | "E" | "F" | "G"
    | "H" | "I" | "J" | "K" | "L" | "M" | "N"
    | "O" | "P" | "Q" | "R" | "S" | "T" | "U"
    | "V" | "W" | "X" | "Y" | "Z" |

```

> - Give an example that showcases all rules of your EBNF. The program should "do" something as used in the next exercise.
> - For your implementation, show that your compiler successfully accepts the input program.
Showcase of EBNF:
### Accpeted code
```rust
    fn func(x:i32, y:i32) -> i32 {
        x+y
    }
    
    fn test() -> bool {
        let mut a = 3;
        let b = &mut a;
        *b = 0;
    
        while a < 10 {
            *b = func(*b, 1);
        }
    
        let c = a / 3;
        if (c > 0) {
            true
        } 
        else {
            false
        }
    }
    
    fn main() {
        test();
    }
```

> - Give a set of examples that are syntactically illegal, and rejected by your compiler.

### Bad code

Different kinds of syntax errors
``` rust
f func(x:i32, y:i32) -> i32 {
    x-y
}
```
``` rust
lte a = 1;
```
```rust
mut let a = 0;
wihle a < 10 {
    // do something
}
```

Mixed positions
``` rust
let a mut:i32 = 0;
```

Not yet implemented
``` rust
fn func(){
    fn func2(){
        fn func3(){
            //...
        }
    }
}
```

> - Compare your solution to the requirements (as stated in the README.md). What are your contributions to the implementation.

##### Our syntax support the most basic subset of Rust:
- Primitive types such as i32 and boolean
- Functions with the primitive return types
- let, if, if else, while, assign
- Precedence of operands by dividing them into arithmetic, conditional and logical operands. This is because of left to right properties
- Possbility for ArCode (addition and subtraction) to have precedence over FactorCode(multiplication and division) by parenthesized precedence

##### Future implementation:
- Better error handling with location information
- More types
- Nestled functions
- Global variables
- Pretty printing

## Your semantics

- Give a (simplified) Structural Operational Semantics (SOS) for your language. You don't need to detail rules that are similar (follow the same pattern). Regarding variable environment (store) you may omit details as long as the presentation is easy to follow.

- 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 repetitions to avoid cluttering.

- For your implementation, give a program (or set of test programs) that cover all the semantics of your language that you have successfully implemented. (Maybe a subset of the input language accepted by the grammar.)

- Compare your solution to the requirements (as stated in the README.md). What are your contributions to the implementation.
> - Give a (simplified) Structural Operational Semantics (SOS) for your language. You don't need to detail rules that are similar (follow the same pattern). Regarding variable environment (store) you may omit details as long as the presentation is easy to follow.

> - 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 repetitions 

##### Symbols: 
- σ, state
- σ', changed state
- →, evaluates
- c, command
- v, variable
- mv, mutable variable
- e, expression

##### Expression can be either a
- b, boolean 
- n, number
- f, function 

### Command sequence
```math
\frac{<c_1,σ> → σ' <c_2,σ'> → σ''}{<c_1;c_2,σ> → σ''}
```

``` rust
let a = 1;
let b = 2;
```

### Operands for integer expressions
```math
\frac{<e_1,σ> → <n_1, σ'> <e_2, σ'> → <n_2, σ''>}{<e_1 - e2, σ> → <n_1 \text{ sub } n_2, σ''>}
```
Above is a subtraction operation of two integers, following integer expressions can be described in the same way:
- +, Addition
- *, Multiplication
- /, Division
- ">", Greater than
- ">=", Greater than or equals
- "<", Less than
- "<=", Less than or equals
- "==", Equal
- "!=", Not equal
``` rust
(3+5)*(4-2);
5 >= 2;
2 == 2;
```

### Operands for boolean expressions
```math
\frac{<e_1,σ> → <b_1, σ'> <e_2, σ> → <b_2, σ''>}{<e_1 == e_2, σ> → <b_1 \text{ equal } b_2, σ''>}
```
Above is an equals operation of two expessions, following boolean expressions can be described in the same way:
- "!=", Not equal
- "&&", And
- "||", Or
``` rust
false != true;
true || false;
```

### Reference and Dereference
```math
\frac{<e,σ> → v}{<\&e,σ> → \text{ ref } v}
```
A reference to some expression
``` rust
&a;
```

```math
\frac{<e,σ> → mv}{<\&\text{ mut }e,σ> → \text{ ref } mv}
```
A mutable reference to some expression
``` rust
&mut a;
```

```math
\frac{<e,σ> → \text{ ref } mv}{<*e,σ> → mv}
```
Dereference of a referenced expression
``` rust
*a;
```

### Function call expression
```math
\frac{<e_1, σ> → <n_1, σ^1> ... <e_n, σ^{n-1}> → <n_n, σ^n> <f_{call}, σ^n[p_1=n_1, ..., p_n=n_n]> → <n, σ'>}{<f(e_1, ..., e_n), σ> → <n, σ'>}
```


``` rust
fn func() -> i32 {
    1+3
}

fn main() {
    let a = func();
}
```

### Let expression
```math
\frac{<e, σ> → <n, σ'>}{<v = e,σ> → σ'[v=n]}
```
Where the expression also can be a boolean or function.

``` rust
let a = 2;
```

### Assign expression
```math
\frac{<e, σ> → n}{<e, σ> → σ'[mv=n]}
```
Where the expression also can be a boolean or function.

``` rust
a = 0;
```

### If true/false expression
```math
\frac{<b, σ> → true <c_1, σ> → σ'}{<\text{if } b \text{ then }c_1 \text{ else } c_2,σ> → σ'}
```
```math
\frac{<b, σ> → false <c_2, σ> → σ''}{<\text{if } b \text{ then }c_1 \text{ else } c_2,σ> → σ''}
```

``` rust
if 3 > 5 {
    func();
}
else {
    anotherfunc();
}
```

### While expression
```math
\frac{<b, σ> → true <c, σ> → σ' <\text{while } b \text{ do } c, σ'> → σ''}{<\text{while } b \text{ do } c, σ> → σ''}
```
```math
\frac{<b, σ> → false}{<\text{while } b \text{ do } c, σ> → σ}
```

``` rust
let mut a = 0;
while a < 10 {
    // something that should loop 10 times
    a = a + 1;
}
```

> - For your implementation, give a program (or set of test programs) that cover all the semantics of your language that you have successfully implemented. (Maybe a subset of the input language accepted by the grammar.)

```rust
fn func(x:i32, y:i32) -> i32 {
        x+y
    }
    
    fn test() -> bool {
        let mut a = 3;
        let b = &mut a;
        *b = 0;
    
        while a < 10 {
            *b = func(*b, 1);
        }
    
        let c = a / 3;
        if (c > 0) {
            true
        } 
        else {
            false
        }
    }
    
    fn main() {
        test();
    }
```

## Your type checker

- Give a simplified set of Type Checking Rules for your language (those rules look very much like the SOS rules, but over types not values). Also here you don't need to detail rules that are similar (follow the same pattern).

- Demonstrate each "type rule" by an example. You may use one or several "programs" to showcase where rules successfully apply.

- For your implementation, give a set of programs demonstrating that ill-typed programs are rejected, connect back to the Type Checking Rules to argue why these are illegal and thus should be rejected.

- Compare your solution to the requirements (as stated in the README.md). What are your contributions to the implementation.
> - Give a simplified set of Type Checking Rules for your language (those rules look very much like the SOS rules, but over types not values). Also here you don't need to detail rules that are similar (follow the same pattern).
> - Demonstrate each "type rule" by an example. You may use one or several "programs" to showcase where rules successfully apply.
> - For your implementation, give a set of programs demonstrating that ill-typed programs are rejected, connect back to the Type Checking Rules to argue why these are illegal and thus should be rejected.

##### Symbols: 
- σ, type state
- σ', changed type state
- →, evaluates
- c, command
- v, variable
- mv, mutable variable
- e, expression
- t, type

##### Expression can be either a
- b, boolean 
- n, number
- f, function 

#### Types
- i32
- bool
- ()
- &t
- *t
- ()
-Unknown

### Integer type operands
```math
\frac{<e_1,σ> → \text{i32} <e_2, σ> → \text{i32}}{<e_1 - e2, σ> → \text{i32}}
```
Above is a subtraction operation of two integers, these will evaluate to i32. Following operands can also be used:
- +, Addition
- *, Multiplication
- /, Division

```rust
(3+5)*(4-2);
1 / false; // Type missmatch
```

### Boolean type operands
```math
\frac{<e_1,σ> → \text{bool} <e_2, σ> → \text{bool}}{<e_1 != e2, σ> → \text{bool}}
```
Above is a not equals operation of two expessions resulting in a bool, following boolean expressions can be described in the same way:
- "==", Equal
- "&&", And
- "||", Or

These operands can also be used with the type i32 as follows:
- ">", Greater than
- ">=", Greater than or equals
- "<", Less than
- "<=", Less than or equals
- "==", Equal
- "!=", Not equal
```math
\frac{<e_1,σ> → \text{i32} <e_2, σ> → \text{i32}}{<e_1 <= e2, σ> → \text{bool}}
```

```rust
true != false;
3 < 2;
false == 5; // Type missmatch
false < 2; // Type missmatch
```

### Reference and Dereference
```math
\frac{<e,σ> → \&t}{<*e,σ> → t}
```

```math
\frac{<e,σ> → \&\text{mut } t}{<*e,σ> → \text{mut} t}
```

```math
\frac{<*e,σ> → t}{<e,σ> → \&t}
```

```rust
let a = 0;
let b = &a;
let c = *b;
let d:bool = *b; // <-- Type missmatch
```

#### Function call
```math
\frac{<e_1, σ> → <t_1, σ^1> ... <e_n, σ^{n-1}> → <t_n, σ^n> <f_{call}, σ^n[p_1=t_1, ..., p_n=t_n]> → <t_{res}, σ'>}{<f(e_1, ..., e_n), σ> → <f_t, σ'>}
```

``` rust
fn example1() -> i32 {
    1+3     // This is okay return type
}

fn example2() -> i32 {
    true    // This is a type missmatch with what type is expected to return
}
```

### Let expression
```math
\frac{<e, σ> → t}{<{/text{let } v = e,σ> →  <(), σ[v=t]>}
```
``` rust
let a:i32 = 1;
let b:bool = 1; // Type missmatch
```

### Assign expression
```math
\frac{<v, σ> → \text{mut }t <e, σ> → t}{<v=t, σ>}
```
``` rust
let mut a:bool = false;
a = 0; // Type missmatch
let b:i32 = 1;
b = false; // Non mutable
```

### If true/false expression
```math
\frac{<e, σ> → bool <c_1, σ> → t <c_2, σ> → t}{<\text{if } e \text{ then }c_1 \text{ else } c_2,σ> → t}
```

``` rust
if 3 > 5 {
    func();
}
else {
    anotherfunc();
}

if 3 - 5 { // Type missmatch
    func(); 
}
else {
    anotherfunc();
}
```

### While expression
```math
\frac{<e, σ> → bool <c, σ> → σ'}{<\text{while } e \text{ do } c, σ> → ()}
```
```math
\frac{<b, σ> → false}{<\text{while } b \text{ do } c, σ> → σ}
```

``` rust
let mut a = 0;
while a < 10 {
    // something that should loop 10 times
    a = a + 1;
}

let mut a = 0;
while a * 10 { // Type missmatch
    // something that should loop 10 times
    a = a + 1;
}
```

> - Compare your solution to the requirements (as stated in the README.md). What are your contributions to the implementation.

Our type checker rejects bad programs according to our rules and depending on what the error is you will get an error custom message.

## Your borrrow checker

- Give a specification for well versus ill formed borrows. (What are the rules the borrow checker should check).
> - Give a specification for well versus ill formed borrows. (What are the rules the borrow checker should check).

- Demonstrate the cases of ill formed borrows that your borrow checker is able to detect and reject.
> - Demonstrate the cases of ill formed borrows that your borrow checker is able to detect and reject.

- 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 LLVM/Crane-Lift backend (optional)
I have not implemented borrow checking.

- Let your backend produce LLVM-IR/Crane Lift IR for an example program (covering the translations implemented).

- Describe the translation process, and connect back to the generated IR.
## Your LLVM/Crane-Lift backend (optional)

- Compare your solution to the requirements (as stated in the README.md). What are your contributions to the implementation.
- Not implemented

## Overall course goals and learning outcomes.

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 [...]).

- Lexical analysis, syntax analysis, and translation into abstract syntax.
> - 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 [...]).

- Regular expressions and grammars, context-free languages and grammars, lexer and parser generators. [lalr-pop is a classical parser generator, it auto generated the lexer for you based on regular expressions but allows for you to define the lexer yourself for more control]
#### Lexical analysis, syntax analysis, and translation into abstract syntax.
I have learned to better understand lexical and syntax analysis while working with LALRPOP to create a parser. The start was the most difficult part of creating a parser because of all the ambiguitys you would run into, this lead to re-writing the code several times. During all the times we had to re-do our code I got more and more knowledge of lexical and syntax anaylsis, the LALRPOP documentetation was of great help. Abstract syntax I learned from the lectures.

- Identifier handling and symbol table organization. Type-checking, logical inference systems. [SOS is a logical inference system]
#### Regular expressions and grammars, context-free languages and grammars, lexer and parser generators. [lalr-pop is a classical parser generator, it auto generated the lexer for you based on regular expressions but allows for you to define the lexer yourself for more control]
I have learned that LALRPOP uses regular expressions to generate a lexer that can tokenize input to create terminals. This was very interesting and I had no idea it worked this way before taking the course. 

- Intermediate representations and transformations for different languages. [If you attended, Recall lectures relating LLVM/Crane-lift, discussions on SSA (Single Static Assignment) used in LLVM/Crane-lift, and discussions/examples on high [level optimization](https://gitlab.henriktjader.com/pln/d7050e_2020/-/tree/generics_and_traits/examples)]
#### Identifier handling and symbol table organization. Type-checking, logical inference systems. [SOS is a logical inference system]
SOS has given me a better insight in how our compiler (interpreter and typechecker) works, but I find it difficult to completely understand how to actually define SOS's of different pars of the compiler. I would like to learn SOS even better.
Another thing I learned while we created our compiler is how type checking works and that it actually is not as complicated as I thought it would be.

- Code optimization and register allocation. Machine code generation for common architectures. [Both LLVM/Crane-Lift does the "dirty work" of backend optimization/register allocation leveraging the SSA form of the LLVM-IR]
#### Intermediate representations and transformations for different languages. [If you attended, Recall lectures relating LLVM/Crane-lift, discussions on SSA (Single Static Assignment) used in LLVM/Crane-lift, and discussions/examples on high [level optimization](https://gitlab.henriktjader.com/pln/d7050e_2020/-/tree/generics_and_traits/examples)]
I have not implemented this or anything regarding this, so I have little to no knowledge within this area.

Comment on additional things that you have experienced and learned throughout the course.
#### Code optimization and register allocation. Machine code generation for common architectures. [Both LLVM/Crane-Lift does the "dirty work" of backend optimization/register allocation leveraging the SSA form of the LLVM-IR]
I have not implemented this or anything regarding this, so I have little to no knowledge within this area.
 No newline at end of file

README.md

deleted100644 → 0
+0 −190
Original line number Diff line number Diff line
# Repo for the D7050E course 2020

The repo will be updated througout the course and includes a draft outline of the course and hints towards reaching the learning goals.

# https://discord.gg/JtcjBfG

## Course Aim

Fundamental theories about computation and different models of computation. Construction of compilers. Lexical analysis, syntax analysis, and translation into abstract syntax.Regular expressions and grammars, context-free languages and grammars, lexer and parser generators. Identifier handling and symbol table organization. Type-checking, logical inference systems. Intermediate representations and transformations for different languages. Code optimization and register allocation. Machine code generation for common architectures.

In the course you will learn and develop your skills through hands on implementation work building your own complier from scratch. In this way theoretical aspects such as formal grammars, Structural Operational Semantics (SOS), and type rule formalizations becomes tangible. We will even touch upon memory safety and how guarantees can be achieved through static (compile time) borrow checking. Compiler backend (code optimization etc.) will be discussed in context of LLVM, which you will optionally interface as a library for code generation.

## Draft outline

### W1 The big picture, parsing, semantic analysis, code generation.

Theory
- From input language to executable
  - Lexing/Parsing to Abstract Syntax Tree (AST)
  - Semantic Analysis (type checking/well formedness)
  - Interpretation and high level optimizations
  - Code generation
  - Linking and run-time system

Practical assignment:

- We start with a minimal subset of Rust, comprising only 
  - Function definitions
  - Commands/statements (let, assignment, if then (else), while)
  - Expressions (including function calls)
  - Primitive types (boolean, i32) and their literals
  - Explicit types everywhere
  - Explicit return(s)

- Write a parser for expressions in Rust using [lalrpop](https://github.com/lalrpop/lalrpop) (which generates a parser implementation from a grammar specification) .

### W2 Regular expressions, automata and grammars 

Theory:

- Regular expressions and automata
- EBNF grammars
  
Practical assignment:

- Formulate an EBNF for your language (optional)
- Continue on the parser implementation, statements, functions, whole programs 

### W3 Context Free Grammars, Push Down Automata and Type Checking

Theory:

- DFA/NFA (regular expressions)
- Push Down Automata (PDA) for Context Free Grammars (CFG)
- Parsing strategies, pros and cons. L(1), LALR, parsing combinators, Parsing Expression Grammars (PEG), etc.

- Typing Rules and their Derivations

Practical assignment:
- Formulate typing rules for your language (optional)
- Finish parser

### W4 Structural Operational Semantics

Theory:

- Structural Operational Semantics (SOS)

Practical assignment:

- Formulate SOS rules for your language (optional)
- Implement interpreter 

### W6 Mutability and Memory References

Theory:

- Mutability and memory references
- Linear types and memory safety
- The Rust borrow model

Practical assignment

- Formalize type rules for your language (optional)
- Implement simple borrow checker
- Extend parser/AST/interpreter to support `&` and `&mut`. 

### W7 Type system extensions

Theory:

- Structured data (structs, enums, algebraic data types, arrays, slices, generics)
- Access methods
- Traits (type classes)

Practical assignment

- Extend parser/AST/type rules accordingly (optional)
- Extend formalizations, EBNF, typing rules and SOS (optional)

### W8 LLVM Backend, linking and run-time system

Theory:

- SSA form
- Concept of `unique`
- Code optimization techniques (performed by LLVM)
- LLVM API (a minimal subset)
- Linking and run-time system support

Practical assignment

- Use LLVM as library for code generation (optional)

---

### Home Exam

You will get the home exam to work on during the exam week. This may imply further theoretical exercises and experiments on your compiler.

### Examination

You will each be scheduled 30 minutes to present Your home exam to us, based on which Your final grade will be determined. Schedule will be agreed on later using Doodle.

---

## Your parser

- You are NOT required to account for operator precedence in expressions, however you MUST support parenthesized sub expressions. (+ for precedence towards higher grades)
- You are NOT required to account for location information (spans), but your error messages will be better if you do. (+ for spans, towards higher grades)
- Error recovery is NOT required (+ for recovery towards higher grades)
- You or NOT required to account for comments (neither single nor multiline). Doing

## Your type checker

- Your type checker should reject ill-typed programs according to your typing rules.
- (+ for higher grades)
  - span information in type errors
  - multiple error reporting
  - type inference (relaxing explicit typing where possible)

## Your interpreter

- Your interpreter should be able to correctly execute programs according to your SOS.
- Your interpreter should panic (with an appropriate error message) when encountering an evaluation error (e.g., 1 + false). Notice, such panics should never occur given that your type checker is correctly implemented.


## Your borrow checker

- Your borrow checker should reject borrow errors according to lexical scoping
- (+ for higher grades)
  - Introduce life time annotations and extend the borrow checker accordingly
  - Non Lexical Lifetimes (likely hard)

## Your type system

- Should support i32, bool, and their references at a minimum
- (+ for higher grades)
  - structs, enums, algebraic data types
- (++ for even higher grades)
  - generics
  - traits/subclasses

## Your LLVM bindings (Optional)

Implement for higher grades
- Basic code generation.
- Pass `noalias` where possible allowing for better optimization (assuming your borrow checker prevents aliasing).
- Other attributes, intrinsics, etc. that enables further LLVM optimizations.
- Bindings to external code, (this could allow for )

---

## Your Ambition, Your Work

Building a compiler from scratch is challenging, expect some trial and error until you end upp with nice/suitable abstractions and implementations. Also Rust as a language per se maybe new to you, and implies some learning in its own right. To that end the [Rust book](https://doc.rust-lang.org/book/) is a great asset. 

The standard library and `crates` that you will use, typically comes with great documentation (executable examples etc.) 

Rust also comes with a built in test framework, which allows to make unit tests directly in code and make integration tests in separate files/folders. The use of tests allows you to effectively partition large problems into smaller ones and validate the functionality along the way. Later those tests helps you keep your code base free of regressions (when you revise your codebase.) 

The `vscode` integration is excellent. Using the [rust analyser](https://github.com/rust-analyzer/rust-analyzer) you get type inference, code completion, cross-referencing, and access to documentation for free. You can even run tests directly in the editor, which is great during development.

Using git to manage your code base brings additional benefits, to share code, give feedback and collaborate. We encourage collaboration and sharing code is fine. However, in order for You to be awarded for your contributions towards the final grade You need to show us what parts you have originated/contributed to. 

For higher grades, there are optional assignments/extensions. Some of those are introduced later on in the course, making it hard to implement those in time for the examination. E.g., type system extensions and LLVM based code generation. If you feel that you want to dig deeper into those, we will help You to start earlier (already from day one.) 

You can choose to share a repo with your fellow students, keep your work in separate branches and merge into a common master. Git will track your contributions. In this way, your final compiler will benefits from contributions made by your fellow students. If some of you focus on e.g. code generation, and some of you on type system extensions, you will need to collaborate and decide on common data structures. Like wise, if some of you are interested in introducing spans for the tokens (location information), this will affect the common data structures. So there is a trade off, doing everything yourself may impose a higher workload but less of "synchronization" efforts, and the other way around. A sensible trade-off may be to share a repo between 2-4 students. Keeping the number of contributors down, makes it easier for You to pinpoint your individual contributions, but even for a group of 15 students there will be plenty of opportunities for each of you to shine.

src/.gitkeep

0 → 100644
+0 −0
Original line number Diff line number Diff line

src/ast.rs

0 → 100644
+87 −0
Original line number Diff line number Diff line
use std::fmt;

#[derive(Debug, Clone)]
pub struct Program {
    pub funcs: Vec<Func>,
}

#[derive(Debug, Clone)]
pub struct Func {
    pub name: String,
    pub params: Params,
    pub ret_type: Type,
    pub body: Vec<Stmt>,
}

#[derive(Debug, Clone)]
pub struct Params(pub Vec<Param>);

#[derive(Debug, Clone)]
pub struct Param {
    pub name: String,
    pub mutable: bool,
    pub _type: Type,
}

#[derive(Debug, Clone)]
pub enum Stmt {
    Let(bool, String, Option<Type>, Option<Box<Expr>>),
    If(Box<Expr>, Vec<Stmt>, Option<Vec<Stmt>>),
    While(Box<Expr>, Vec<Stmt>),
    Assign(Box<Expr>, Box<Expr>),
    Expr(Box<Expr>),
    Block(Vec<Stmt>),
    Return(Box<Expr>),
    Semi,
}

#[derive(Debug, Clone)]
pub struct Exprs(pub Vec<Box<Expr>>);

#[derive(Debug, Clone)]
pub enum Expr {
    Infix(Box<Expr>, OpCode, Box<Expr>),
    Prefix(OpCode, Box<Expr>),
    Call(String, Exprs),
    Term(Lit),
    Block(Vec<Stmt>),
    Ref(Box<Expr>),
    RefMut(Box<Expr>),
    DeRef(Box<Expr>),
    Stmt(Stmt),
}

#[derive(Debug, Clone)]
pub enum Lit {
    Num(i32),
    Id(String),
    Bool(bool),
    None,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Type {
    Int,
    Bool,
    Unit,
    Ref(Box<Type>),
    Mut(Box<Type>),
    Unknown,
}

#[derive(Debug, Clone)]
pub enum OpCode{
    Add,
    Sub,
    Mul,
    Div,
    And,
    Or, 
    Not,
    Equal,
    NotEqual,
    Greater,
    Lesser,
    GreaterOrEqual,
    LesserOrEqual,
}

src/ast/ast.rs

deleted100644 → 0
+0 −21
Original line number Diff line number Diff line
use std::fmt;

// ast

// println!("{:?}", ..)
#[derive(Debug)]
pub enum NumOrId {
    Num(usize),
    Id(String),
}

// println!("{}", ..)
impl fmt::Display for NumOrId {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            NumOrId::Num(i) => write!(f, "{}", i)?,
            NumOrId::Id(s) => write!(f, "{}", s)?,
        };
        Ok(())
    }
}

src/ast/main.rs

deleted100644 → 0
+0 −28
Original line number Diff line number Diff line
use lalrpop_util::lalrpop_mod;

lalrpop_mod!(pub parser, "/ast/parser.rs");

use parser::*;

pub mod ast;

fn main() {
    println!("minimal");
    println!("{:?}", NumOrIdParser::new().parse("123"));
    println!("{:?}", NumOrIdParser::new().parse("a1_a"));

    println!("{}", NumOrIdParser::new().parse("123").unwrap());
    println!("{}", NumOrIdParser::new().parse("a1_a").unwrap());
}

#[test]
fn parse_num_or_id() {
    assert_eq!(
        format!("{}", NumOrIdParser::new().parse("123").unwrap()),
        "123"
    );
    assert_eq!(
        format!("{}", NumOrIdParser::new().parse("a1_a").unwrap()),
        "a1_a"
    );
}

src/ast/parser.lalrpop

deleted100644 → 0
+0 −18
Original line number Diff line number Diff line
use std::str::FromStr;

use crate::ast::*;

grammar;

pub NumOrId: NumOrId = {
    Num => NumOrId::Num(<>),
    Id => NumOrId::Id(<>),
}
 
pub Num: usize = {
    r"[0-9]+" => usize::from_str(<>).unwrap(),
};

pub Id: String = {
    r"([a-z]|[A-Z])([a-z]|[A-Z]|[0-9]|_)*" => String::from_str(<>).unwrap(),
};

src/comments/main.rs

deleted100644 → 0
+0 −38
Original line number Diff line number Diff line
use lalrpop_util::lalrpop_mod;

lalrpop_mod!(pub parser, "/comments/parser.rs");

use parser::*;

fn main() {
    println!("comments");
}

#[test]
fn parse_id() {
    println!(
        "{:?}",
        IdParser::new().parse(
            "

            //a1_a //

            /* 
            a1_a
            */
            a1_a
            "
        )
    );
}

#[test]
fn parse_num() {
    println!("{:?}", NumParser::new().parse("123"));
}

#[test]
fn parse_num_or_id() {
    println!("{:?}", NumOrIdParser::new().parse("123"));
    println!("{:?}", NumOrIdParser::new().parse("a1_a"));
}

src/comments/parser.lalrpop

deleted100644 → 0
+0 −55
Original line number Diff line number Diff line
use std::str::FromStr;

grammar;

match {
    // The default whitespace skipping is disabled an `ignore pattern` is specified
    r"\s*" => { }, 
    // Skip `// comments`
    r"//[^\n\r]*" => { }, 
    // Skip `/* comments */`
    r"/\*([^\*]*\*+[^\*/])*([^\*]*\*+|[^\*])*\*/" => { },  
    _
}

pub NumOrId = {
    Num => <>.to_string(),
    Id,
}

pub Num: usize = {
    r"[0-9]+" => usize::from_str(<>).unwrap(),
};

pub Id: String = {
    r"([a-z]|[A-Z])([a-z]|[A-Z]|[0-9]|_)*" => String::from_str(<>).unwrap(),
};

// lets try to break down the multi-line comment
//  
//  should allow patterns like
//  /*
//  ...
//  */
//  and
//  /* /*
//  */
//  */
//  but reject patterns like
//  /* /*
//  */
//  and
//  /* */ */
//  
//  Rather tricky right...
//  
//  /\*([^\*]*\*+[^\*/])*([^\*]*\*+|[^\*])*\*/
//  the outer  "/\* ... \*/" requires start and end "/*" ... "*/"
//  so let's look closer at "([^\*]*\*+[^\*/])*([^\*]*\*+|[^\*])*"
//  the trailing "*", just means any number of 
//  "[^\*]*\*+[^\*/])*([^\*]*\*+|[^\*]"
//
//  "[^\*]*\*+[^\*/])*"
//  "[^\*]*"" any number or Not("*"), e.g. 0123
//  "\*" one or many "*", e.g. *, or ****
//  .... ehhh I gave up, regexp is haaaaaaarrrrrrrd, another day perhaps
 No newline at end of file

src/environment.rs

0 → 100644
+213 −0
Original line number Diff line number Diff line
use std::fmt;
use crate::ast::*;
use std::collections::{HashMap, VecDeque};

pub type FnEnv<'a> = HashMap<&'a str, &'a Func>;

impl Func {
    pub fn get_id(&self) -> &str {
        &self.name
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum Val {
    Num(i32),
    Bool(bool),
    Unit,
    Uninitialized,
    RefMut(String),
    Ref(String),
}

#[derive(Debug)]
pub enum Bc {
    Free,
    Shared,
    Mut,
    Ref(Box<Bc>),
}

pub fn progam_to_env(prog: &Program) -> FnEnv {
    let program_funcs = &prog.funcs;
    program_funcs.into_iter().map(|f| (f.get_id(), f)).collect()
}

#[derive(Debug)]
pub struct VarMem(VecDeque<HashMap<String, Type>>);

impl VarMem {
    pub fn new() -> Self {
        VarMem(VecDeque::<HashMap<String, Type>>::new())
    }

    pub fn get_type(&self, id: String) -> Option<&Type> {
        self.0.iter().find_map(|hm| {
            hm.get(id.as_str())
                .map(|t| (t))
        })
    }

    pub fn get_mut_type(&mut self, id: String) -> Option<&mut Type> {
        self.0.iter_mut().find_map(|hm| hm.get_mut(id.as_str()))
    }

    pub fn new_id(&mut self, id: String, ty: Type) {
        let hm = self.0.front_mut().unwrap();
        println!("[VarMem] Inserted id {:?} into scope {:?}", &id, hm);

        hm.insert(id, ty);
    }

    pub fn update(&mut self, id: String, ty: Type) {
        println!("[VarMem] Updating environment variable {:?}", id);
        match self.get_mut_type(id.clone()) {
            Some(ot) => match ot {
                Type::Unknown => {
                    (*ot) = ty;
                }
                t => match (*t).eq(&ty) {
                    true => println!("[VarMem] Updated environment variable {:?} to type {:?}", id, ty),
                    false => panic!("[VarMem] Update types are different {:?} <> {:?}", t, ty),
                },
            },
            None => panic!("[VarMem] Error variable {:?} is not found", id),
        }
    }

    pub fn push_empty_scope(&mut self) {
        self.0.push_front(HashMap::new());
    }

    pub fn push_param_scope(&mut self, args: HashMap<String, Type>) {
        self.0.push_front(args)
    }

    pub fn pop_scope(&mut self) {
        self.0.pop_front();
    }
}


#[derive(Debug)]
pub struct BcMem(VecDeque<HashMap<String, Bc>>);

impl BcMem {
    pub fn new() -> Self {
        BcMem(VecDeque::<HashMap<String, Bc>>::new())
    }

    pub fn get(&self, id: String) -> Option<&Bc> {
        self.0.iter().find_map(|hm| {
            hm.get(id.as_str())
                .map(|bc| (bc))
        })
    }

    pub fn get_mut(&mut self, id: String) -> Option<&mut Bc> {
        self.0.iter_mut().find_map(|hm| hm.get_mut(id.as_str()))
    }

    pub fn new_id(&mut self, id: String, bc: Bc) {
        let hm = self.0.front_mut().unwrap();
        println!("[BcMem] Inserted id {:?} into scope {:?}", &id, hm);

        hm.insert(id, bc);
    }

    pub fn update(&mut self, id: String, bc: Bc) {
        println!("[BcMem] Updating environment variable {:?}", id);
        match self.get_mut(id.clone()) {
            Some(b) => match b {
                Bc::Free => {
                    *b = bc;
                }
                Bc::Shared => match bc {
                    Bc::Shared => println!("[BcMem] Updated environment variable {:?} to bc {:?}", id, bc),
                    Bc::Mut => panic!("[BcMem] Error tried update editable variable {:?}", id),
                    _ => panic!("[BcMem] Error unimplemented borrow state in Bc::Shared"),
                },
                _ => panic!("[BcMem] Error unimplemented borrow state"),
            },
            None => panic!("[BcMem] Error variable {:?} is not found", id),
        }
    }

    pub fn push_empty_scope(&mut self) {
        self.0.push_front(HashMap::new());
    }

    pub fn push_param_scope(&mut self, args: HashMap<String, Bc>) {
        self.0.push_front(args)
    }

    pub fn pop_scope(&mut self) {
        self.0.pop_front();
    }
}

#[derive(Debug)]
pub struct Mem(VecDeque<HashMap<String, (bool, Val)>>);

impl Mem {
    pub fn new() -> Self {
        Mem(VecDeque::<HashMap<String, (bool, Val)>>::new())
    }

    pub fn get(&self, id: String) -> Option<&Val> {
        self.0.iter().find_map(|hm| match hm.get(id.as_str()) {
            Some((_, v)) => Some(v),
            _ => None,
        })
    }

    pub fn get_mut(&mut self, id: String) -> Option<&mut Val> {
        self.0
            .iter_mut()
            .find_map(|hm| match hm.get_mut(id.as_str()) {
                Some((true, v)) => Some(v),
                Some((_, v)) => {
                    match v {
                        Val::Uninitialized => Some(v),
                        Val::RefMut(_) => {
                            println!("[Mem] Gotten &mut {:?}", v);
                            Some(v)
                        }
                        _ => panic!("[Mem] Error cannot access {:?} as a mutable", id),
                    }
                }
                _ => None,
            })
    }

    pub fn new_id(&mut self, id: String, is_mut: bool) {
        let hm = self.0.front_mut().unwrap();
        println!("[Mem] Inserted id {:?}, in scope {:?}", id, hm);

        hm.insert(id, (is_mut, Val::Uninitialized));
    }

    pub fn update(&mut self, id: String, val: Val) {
        match self.get_mut(id.clone()) {
            Some(v_ref) => {
                println!("[Mem] Variable {:?} was found", id);
                *v_ref = val;
            }
            None => {
                panic!("[Mem] Error variable {:?} was not found", id);
            }
        };
    }

    pub fn push_empty_scope(&mut self) {
        self.0.push_front(HashMap::new());
    }

    pub fn push_param_scope(&mut self, args: HashMap<String, (bool, Val)>) {
        self.0.push_front(args)
    }

    pub fn pop_scope(&mut self) {
        self.0.pop_front();
    }
}

src/grammar.lalrpop

deleted100644 → 0
+0 −7
Original line number Diff line number Diff line
use std::str::FromStr;

grammar;

pub Id: String = {
    r"([a-z]|[A-Z])([a-z]|[A-Z]|[0-9]|_)*" => String::from_str(<>).unwrap(),
};

src/interpreter.rs

0 → 100644
+372 −0
Original line number Diff line number Diff line
use std::collections::{HashMap, VecDeque};

use lalrpop_util::lalrpop_mod;
lalrpop_mod!(pub parser, "/parser.rs");
use parser::*;

use crate::ast::*;
use crate::environment::*;

pub mod ast;
pub mod environment;


#[derive(Debug, Clone)]
pub enum Val {
    Num(i32),
    Bool(bool),
    Unit,
    Uninitialized,
    RefMut(String),
    Ref(String),
}


// EVALUATING FUNCTIONS
fn eval_infix(l: Val, oc: &OpCode, r: Val) -> Val {
    match (l, oc, r) {
        (Val::Num(l), oc, Val::Num(r)) => match oc {
            OpCode::Add => Val::Num(l + r),
            OpCode::Sub => Val::Num(l - r),
            OpCode::Mul => Val::Num(l * r),
            OpCode::Div => Val::Num(l / r),
            OpCode::Equal => Val::Bool(l == r),
            OpCode::NotEqual => Val::Bool(l != r),
            OpCode::Lesser => Val::Bool(l < r),
            OpCode::LesserOrEqual => Val::Bool(l <= r),
            OpCode::Greater => Val::Bool(l > r),
            OpCode::GreaterOrEqual => Val::Bool(l >= r),
            _ => panic!("[Infix] Error evaluating num"),
        },
        (Val::Bool(l), oc, Val::Bool(r)) => Val::Bool(match oc {
            OpCode::And => l && r,
            OpCode::Or => l || r,
            OpCode::Equal => l == r,
			OpCode::NotEqual => l != r,
            _ => panic!("[Infix] Error evaluating bool"),
        }),
        _ => panic!("[Infix] Error infix doesnt match anything"),
    }
}

fn eval_prefix(oc: &OpCode, r: Val) -> Val {
    match (oc, r) {
        (oc, Val::Num(r)) => match oc {
            OpCode::Sub => Val::Num(-r),
            OpCode::Add => Val::Num(r),
            _ => panic!("[Prefix] Error evaluating num"),
        },
        (oc, Val::Bool(r)) => match oc {
            OpCode::Not => Val::Bool(!r),
            _ => panic!("[Prefix] Error evaluating bool"),
        },
        _ => panic!("[Prefix] Error prefix doesnt match anything"),
    }
}

fn eval_expr(e: &Expr, m: &mut Mem, fn_env: &FnEnv) -> Val {
    
    println!("[Expr] Evaluating expr: {:?}", e);
    match e {
        Expr::Term(l) => match l {
            Lit::Num(i) => Val::Num(*i),
            Lit::Bool(b) => Val::Bool(*b),
            Lit::Id(id)  => match m.get(id.to_owned()) {
                Some(v) => v.clone(),
                None => panic!("[Expr] Identifier not found {:?}", id),
            },
            _ => panic!("[Expr] Error evaluating term"),
        },
        Expr::Infix(l, op, r) => {
            let l = eval_expr(l, m, fn_env);
            let r = eval_expr(r, m, fn_env);
            eval_infix(l, op, r)
        }
        Expr::Prefix(op, r) => {
            let r = eval_expr(r, m, fn_env);
            eval_prefix(op, r)
        },
        Expr::Ref(e) => match &**e {
            Expr::Term(l) => match l {
                    Lit::Id(id) => match m.get(id.to_owned()){
                        Some(_) => Val::Ref(id.to_owned()),
                        None => panic!("[Expr] Error identifier not found {:?}", id),
                    },
                    _ => panic!("[Expr] Error Ref on non identifier"),
            },
            _ => panic!("[Expr] Error Ref on non term"),
        },
        Expr::RefMut(e) => match &**e {
            Expr::Term(l) => match l {
                    Lit::Id(id) => match m.get(id.to_owned()){
                        Some(_) => Val::RefMut(id.to_owned()),
                        None => panic!("[Expr] Error identifier not found {:?}", id),
                    },
                    _ => panic!("[Expr] Error RefMut on non identifier"),
            },
            _ => panic!("[Expr] Error RefMut on non term"),
        },
        Expr::DeRef(e) => {
            let ev = eval_expr(e, m, fn_env);
            println!("[Expr] Dereferencing {:?}", ev);

            match ev {
                Val::Ref(id) => eval_expr(&Expr::Term(Lit::Id(id)), m, fn_env),
                Val::RefMut(id) => eval_expr(&Expr::Term(Lit::Id(id)), m, fn_env),
                _ => panic!("[Expr] Error dereference failed"),
            }
        },

        Expr::Stmt(s) => {
            println!("[Expr] Interpreting stmt: {:?}", s);
            eval_stmts(&vec![s.to_owned()], m, fn_env)
        },

        Expr::Call(name, exprs) => {
            println!("[Expr] Calling function named {:?} with arguments {:?}", name, exprs);
            let args: Vec<Val> = exprs.0.iter().map(|expr| eval_expr(expr, m, fn_env)).collect();

            eval_fn(name, &args, m, fn_env)

        }

        _ => unimplemented!("[Expr] Unimplemented expr"),
    }
}

fn eval_recursive_id(e: &Expr, m: &Mem, fn_env: &FnEnv) -> String {
    println!("[RecId] Attemting to recursively solve {:?}", e);
    match e {
        Expr::Term(Lit::Id(id)) => id.to_owned(),

        Expr::DeRef(e) => {
            println!("[RecId] Dereference expr: {:?}", e);

            let ev = eval_recursive_id(e, m, fn_env);
            println!("[RecId] Dereferenced id {:?}", ev);

            match m.get(ev) {
                Some(Val::Ref(id)) => {
                    println!("[RecId] Ref {:?}", id);
                    id.clone()
                }
                Some(Val::RefMut(id)) => {
                    println!("[RecId] RefMut {:?}", id);
                    id.clone()
                }
                _ => panic!("[RecId] Failed to dereference"),
            }
        }
        _ => panic!("[RecId] Error id is not a term nor a dereference"),
    }
}

fn eval_stmts(stmts: &Vec<Stmt>, m: &mut Mem, fn_env: &FnEnv) -> Val {
    let ss = stmts;

    m.push_empty_scope();

    let mut stmt_val = Val::Unit;

    for s in ss {
        println!("[Stmt] Stmt: {:?}", s);
        println!("[Stmt] Mem: {:?}", m);
        stmt_val = match s {
            Stmt::Let(b, id, _, e_orig) => {
                println!("[Stmt] Let");
                m.new_id(id.to_owned(), *b);
                match e_orig {
                    Some(e) => {
                        let r = eval_expr(e, m, fn_env);
                        println!("r {:?}", r);
                        m.update(id.to_owned(), r);
                        Val::Unit
                    },
                    _ => Val::Unit,
                }

            },
            Stmt::Assign(l_expr, r_expr) => {
                println!("[Stmt] Assign");
                match &**l_expr {
                    Expr::Term(Lit::Id(id)) => {
                        let v = eval_expr(r_expr, m, fn_env);
                        m.update(id.to_owned(), v)
                    }
                    _ => {
                        let lv = eval_recursive_id(l_expr, m, fn_env);
                        println!("lv {:?}", lv);
                        let v = eval_expr(r_expr, m, fn_env);
                        m.update(lv, v)
                    }
                };
                Val::Unit
            },
            Stmt::If(expr,s,es) => {
                println!("[Stmt] If");
                
                match (eval_expr(expr, m, fn_env), es) {
                    (Val::Bool(true), _) => eval_stmts(s, m, fn_env),
                    (Val::Bool(false), Some(es)) => eval_stmts(es, m, fn_env),
                    (Val::Bool(_), _) => Val::Uninitialized,
                    _ => panic!("[Stmt] Error if resulted in non boolean condition"),
                }
            },
            Stmt::While(expr,block) => {
                println!("[Stmt] While");
                while match eval_expr(expr, m, fn_env)  {
                    Val::Bool(b) => b,
                    _ => panic!("[Stmt] Error while resulted in non boolean condition"),
                } {
                    eval_stmts(block, m, fn_env);
                }

                Val::Unit
            },

            Stmt::Expr(e) => {
                println!("[Stmt] Expr");
                eval_expr(e, m, fn_env)
            },

            Stmt::Block(block) => {
                println!("[Stmt] Block");
                eval_stmts(block, m, fn_env)
            },

            Stmt::Return(e) => {
                let ret_val = eval_expr(e, m, fn_env);
                println!("[Stmt] Returned with value: {:?}", ret_val);
                ret_val
            } 

            Stmt::Semi => Val::Unit,
            
            _ => unimplemented!(),
        }
    }

    m.pop_scope();
    stmt_val
}

pub fn eval_fn(name: &str, params: &Vec<Val>, m: &mut Mem, fn_env: &FnEnv) -> Val {
    if let Some(decl) = fn_env.get(name) {
        println!("[Func] Evaluating function {:?} with params {:?}", name, params);
        let id: &Vec<(bool, String)> = &decl.params.0.iter().
            map(|param| (param.mutable, param.name.to_owned())).collect();

        let raw_params: Vec<(&(bool, String), &Val)> = id.iter().zip(params).collect();
        let vars: HashMap<String, (bool, Val)> = raw_params.into_iter().map(|(s,v )| (s.1.clone(), (s.0, v.clone()))).collect();
        
        println!("[Func] Converted params into variables: {:?}", &vars);
        m.push_param_scope(vars);
        let ret_val = eval_stmts(&decl.body, m, fn_env);
        println!("[Func] Function {:?} returned as {:?} and containing the memory \n\t{:?}", name, ret_val, m);
        m.pop_scope();
        ret_val

    } else {
        panic!("[Func] Error function named {:?} not found.");
    }

}
// END OF EVALUATING FUNCTIONS

#[derive(Debug)]
pub struct Mem(VecDeque<HashMap<String, (bool, Val)>>);

impl Mem {
    pub fn new() -> Self {
        Mem(VecDeque::<HashMap<String, (bool, Val)>>::new())
    }

    pub fn get(&self, id: String) -> Option<&Val> {
        self.0.iter().find_map(|hm| match hm.get(id.as_str()) {
            Some((_, v)) => Some(v),
            _ => None,
        })
    }

    pub fn get_mut(&mut self, id: String) -> Option<&mut Val> {
        self.0
            .iter_mut()
            .find_map(|hm| match hm.get_mut(id.as_str()) {
                Some((true, v)) => Some(v),
                Some((_, v)) => {
                    match v {
                        Val::Uninitialized => Some(v),
                        Val::RefMut(_) => {
                            println!("[Mem] Gotten &mut {:?}", v);
                            Some(v)
                        }
                        _ => panic!("[Mem] Error cannot access {:?} as a mutable", id),
                    }
                }
                _ => None,
            })
    }

    pub fn new_id(&mut self, id: String, is_mut: bool) {
        let hm = self.0.front_mut().unwrap();
        println!("[Mem] Inserted id {:?}, in scope {:?}", id, hm);

        hm.insert(id, (is_mut, Val::Uninitialized));
    }

    pub fn update(&mut self, id: String, val: Val) {
        match self.get_mut(id.clone()) {
            Some(v_ref) => {
                println!("[Mem] Variable {:?} was found", id);
                *v_ref = val;
            }
            None => {
                panic!("[Mem] Error variable {:?} was not found", id);
            }
        };
    }

    pub fn push_empty_scope(&mut self) {
        self.0.push_front(HashMap::new());
    }

    pub fn push_param_scope(&mut self, args: HashMap<String, (bool, Val)>) {
        self.0.push_front(args)
    }

    pub fn pop_scope(&mut self) {
        self.0.pop_front();
    }
}

fn main() {
    let mut m = Mem::new();
    let program = &ProgramParser::new().parse("
    fn func(x:i32, y:i32) -> i32 {
        x+y
    }
    
    fn test() -> bool {
        let mut a = 3;
        let b = &mut a;
        *b = 0;
    
        while a < 10 {
            *b = func(*b, 1);
        }
    
        let c = a / 3;
        if (c > 0) {
            true
        }
    }
    
    fn main() {
        test();
    }

    ").unwrap();
    let fn_env = progam_to_env(program);
    let args: Vec<Val> = Vec::new();
    println!("{:?}", eval_fn("main", &args, &mut m, &fn_env));
}
 No newline at end of file

src/main.rs

deleted100644 → 0
+0 −14
Original line number Diff line number Diff line
use lalrpop_util::lalrpop_mod;

lalrpop_mod!(pub grammar);

use grammar::*;

fn main() {
    println!("Parse an Id {:?}", IdParser::new().parse("abcd"));
}

#[test]
fn hello() {
    println!("Parse an Id {:?}", IdParser::new().parse("abcd"));
}

src/minimal/main.rs

deleted100644 → 0
+0 −17
Original line number Diff line number Diff line
use lalrpop_util::lalrpop_mod;

lalrpop_mod!(pub parser, "/minimal/parser.rs");

use parser::*;

fn main() {
    println!("minimal");
    println!("{:?}", NumOrIdParser::new().parse("123"));
    println!("{:?}", NumOrIdParser::new().parse("a1_a"));
}

#[test]
fn parse_num_or_id() {
    println!("{:?}", NumOrIdParser::new().parse("123"));
    println!("{:?}", NumOrIdParser::new().parse("a1_a"));
}

src/minimal/parser.lalrpop

deleted100644 → 0
+0 −16
Original line number Diff line number Diff line
use std::str::FromStr;

grammar;

pub NumOrId = {
    Num => <>.to_string(),
    Id,
}

pub Num: usize = {
    r"[0-9]+" => usize::from_str(<>).unwrap(),
};

pub Id: String = {
    r"([a-z]|[A-Z])([a-z]|[A-Z]|[0-9]|_)*" => String::from_str(<>).unwrap(),
};

src/parser.lalrpop

0 → 100644
+189 −0
Original line number Diff line number Diff line
use std::str::FromStr;

use crate::ast::*;

grammar;

match {
    // The default whitespace skipping is disabled an `ignore pattern` is specified
    r"\s*" => { }, 
    // Skip `// comments`
    r"//[^\n\r]*" => { }, 
    // Skip `/* comments */`
    r"/\*([^\*]*\*+[^\*/])*([^\*]*\*+|[^\*])*\*/" => { },  
    _
}


Comma<T>: Vec<T> = {
    <mut v:(<T> ",")*> <e:T?> => match e {
        None => v,
        Some(e) => { v.push(e); v},
    }
}

CommaNoTrail<T>: Vec<T> = {
    <mut v:(<T> ",")*> <e:T> => { v.push(e); v }
}

ParCNT<T>: Vec<T> = {
    "()" => Vec::new(),
    "(" <CommaNoTrail<T>> ")" => <>,
}

pub Program: Program = {
    <f: Func> <prog: Program> => {
        let mut vf = prog.funcs;
        vf.push(f);
        Program {funcs: vf}
    },
    <f: Func> => {
        let mut vf = Vec::new();
        vf.push(f);
        Program { funcs: vf }
    },
}

pub Func: Func = {
    "fn" <name:Id> <params: Params> <ret: ("->" <Type>)?> <body: Block> =>
    Func {
        name,
        params,
        ret_type: match ret {
                None => Type::Unit,
                Some(r) => r
            },
        body
    },
}

Params: Params = {
    ParCNT<Param> => Params(<>),
}

Param: Param = {
    <_mut: "mut"?> <name: Id> ":" <_type: Type> =>
    Param {
        name: name,
        mutable: _mut.is_some(),
        _type: _type,
    },
}

pub Block: Vec<Stmt> = {
    "{" <stmts: StmtSeq*> <stmt: Stmt?> "}" => {
        let mut stmts: Vec<Stmt> = stmts.into_iter().flatten().collect();
        if let Some(stmt) = stmt {
            stmts.push(Stmt::Return(Box::new(Expr::Stmt(stmt))));
        };

        stmts
    }
}

pub StmtSeq: Vec<Stmt> = {
    ";" => vec![Stmt::Semi],
    <Stmt> ";" => vec![<>, Stmt::Semi],
    StmtBlock => vec![<>],
}

StmtBlock: Stmt = {
    "while" <Expr> <Block> => Stmt::While(<>),
    "if" <Expr> <Block> <("else" <Block>)?> => Stmt::If(<>),
    Block => Stmt::Block(<>),
}

Stmt: Stmt = {
    "let" <mutable:"mut"?> <id:Id> <_type:(":"<Type>)?> <expr:("=" <Expr>)?> => 
        Stmt::Let(
            if let Some(_) = mutable {true} else {false},
            id,
            _type,
            expr,
        ),
    <Expr> "=" <Expr> => Stmt::Assign(<>),
    <Expr1> => Stmt::Expr(<>),
}

pub Exprs: Exprs = {
    ParCNT<Expr1> => Exprs(<>),
}

pub Expr = {
    ExprBlock,
    Expr1,
}

pub ExprBlock: Box<Expr> = {
    "if" <expr: Expr> <block: Block> "else" <el_block: Block> => {
        let stmt = Stmt::If(expr, block, Some(el_block));
        Box::new(Expr::Stmt(stmt))
    },
    Block => Box::new(Expr::Block(<>)),
}

pub Expr1: Box<Expr> = {
    Expr1 LoCode Expr2 => Box::new(Expr::Infix(<>)),
    Expr1 ArCode Expr2 => Box::new(Expr::Infix(<>)),
    Expr2,
}

pub Expr2: Box<Expr> = {
    Expr2 FactorCode Lit => Box::new(Expr::Infix(<>)),
    Lit,
}

ArCode: OpCode = {
    "+" => OpCode::Add,
    "-" => OpCode::Sub,
}

FactorCode: OpCode = {
    "*" => OpCode::Mul,
    "/" => OpCode::Div,
}

LoCode: OpCode = {
    "&&" => OpCode::And,
    "||" => OpCode::Or,
    "==" => OpCode::Equal,
    "!=" => OpCode::NotEqual,
    ">" => OpCode::Greater,
    "<" => OpCode::Lesser,
    ">=" => OpCode::GreaterOrEqual,
    "<=" => OpCode::LesserOrEqual,
}

Lit: Box<Expr> = {
    <Id> <Exprs> => Box::new(Expr::Call(<>)),
    ArCode Lit => Box::new(Expr::Prefix(<>)),
    "!" <Lit> => Box::new(Expr::Prefix(OpCode::Not, <>)),
    Bool => Box::new(Expr::Term(Lit::Bool(<>))),
    Num => Box::new(Expr::Term(Lit::Num(<>))),
    Id => Box::new(Expr::Term(Lit::Id(<>))),
    "&" <Lit> => Box::new(Expr::Ref(<>)),
    "&" "mut" <Lit> => Box::new(Expr::RefMut(<>)),
    "*" <Lit> => Box::new(Expr::DeRef(<>)),
    "(" <Expr> ")",
}

Type: Type = {
	"bool" => Type::Bool,
    "i32" => Type::Int,
    "()" => Type::Unit,
    "&" <Type> => Type::Ref(Box::new(<>)),
    "&" "mut" <Type> => Type::Ref(Box::new(Type::Mut(Box::new(<>)))),
}

Bool: bool = {
    "true" => bool::from_str(<>).unwrap(),
    "false" => bool::from_str(<>).unwrap(),
}

Num: i32 = {
    r"[0-9]+" => i32::from_str(<>).unwrap(),
};

Id: String = {
    r"([a-z]|[A-Z]|_)([a-z]|[A-Z]|[0-9]|_)*" => String::from_str(<>).unwrap(),
}
 No newline at end of file

src/typechecker.rs

0 → 100644
+412 −0
Original line number Diff line number Diff line

use crate::ast::*;
use crate::environment::*;
use std::collections::HashMap;

pub mod ast;
pub mod environment;

// For testing

use lalrpop_util::lalrpop_mod;
lalrpop_mod!(pub parser, "/parser.rs");
use parser::*;

// End of testing

fn check_expr(e: &Expr, fn_env: &FnEnv, var_mem: &mut VarMem) -> Result<Type, String> {
    println!("[ExprCheck] {:?}", e);
    println!("[VarMem] {:?}", var_mem);

    match e {
        Expr::Term(l) => match l {
            Lit::Num(_) => Ok(Type::Int),
            Lit::Bool(_) => Ok(Type::Bool),
            Lit::Id(id) => match var_mem.get_type(id.to_string()) {
                Some(t) => Ok(t.clone()),
                None => Err(format!("[ExprCheck] Error variable {:?} not found (<fn_name>)", id)),
            },
            Lit::None => Ok(Type::Unknown),
        }

        Expr::Infix(l, op, r) => {
            println!("[ExprCheck] Infix");

            let lt = check_expr(l, fn_env, var_mem)?;
            let lt = match lt {
                Type::Mut(t) => *t,
                _ => lt,
            };

            let rt = check_expr(r, fn_env, var_mem)?;
            let rt = match rt {
                Type::Mut(t) => *t,
                _ => rt,
            };

            match op {
                OpCode::Add | OpCode::Mul | OpCode::Div | OpCode::Sub => {
                    if lt == Type::Int && rt == Type::Int {
                        Ok(Type::Int)
                    } else {
                        Err(format!("[ExprCheck] Error type mismatch expected Type::Int, gotten {:?}, {:?} (<fn_name>)", lt, rt))
                    }
                }
                OpCode::And | OpCode::Or => {
                    if lt == Type::Bool && rt == Type::Bool {
                        Ok(Type::Bool)
                    } else {
                        Err(format!("[ExprCheck] Error type mismatch expected Type::Bool, gotten {:?}, {:?} (<fn_name>)", lt, rt))
                    }
                }
                OpCode::Equal | OpCode::NotEqual => {
                    if lt == rt {
                        Ok(Type::Bool)
                    } else {
                        Err(format!("[ExprCheck] Error type mismatch expected Type::Bool, gotten {:?}, {:?} (<fn_name>)", lt, rt))
                    }
                }
                OpCode::Lesser | OpCode::Greater | OpCode::LesserOrEqual | OpCode::GreaterOrEqual => {
                    if lt == Type::Int && rt == Type::Int {
                        Ok(Type::Bool)
                    } else {
                        Err(format!("[ExprCheck] Error type mismatch expected Type::Int, gotten {:?}, {:?} (<fn_name>)", lt, rt))
                    }
                }
                _ => panic!("[ExprCheck] Error unsupported operation (<fn_name>)"),
            }
        }

        Expr::Prefix(op, r) => {
            println!("[ExprCheck] Prefix");
            let rt = check_expr(r, fn_env, var_mem)?;

            let ty = match op {
                OpCode::Mul | OpCode::Div | OpCode::Add | OpCode::Sub   => Type::Int,
                _                                                       => Type::Bool,
            };

            if rt == ty {
                Ok(ty)
            }else{
                Err(format!("[ExprCheck] Error type mismatch gotten {:?} (<fn_name>)", rt))
            }
        }

        Expr::Call(s, args) => {
            println!("[ExprCheck] Call");

            let args: Vec<Type> = args.clone().0.into_iter().map(|e| check_expr(&*e, fn_env, var_mem)).collect::<Result<_, _>>()?;

            println!("[ExprCheck] Calling {:?} with types {:?}", s, args);
            let f = match fn_env.get(s.as_str()) {
                Some(f) => (*f).to_owned(),
                None => Err(format!("[ExprCheck] Error function not found {:?} (<fn_name>)", s))?,
            };

            let params: Vec<Type> = (f.params.0.clone()).into_iter().map(|p| (p._type.to_owned())).collect();
            let c_params = params.to_owned();

            let arg_param: Vec<(&Type, Type)> = args.iter().zip(params).collect();

            let mut found_mismatch = false;
            for ap in arg_param {
                let arg = ap.0.to_owned();
                let par = ap.1;
                if arg != par {
                    let strip_arg = match arg {
                        Type::Mut(t) => *t,
                        _ => arg,
                    };

                    if strip_arg != par {
                        found_mismatch = true;
                        break;
                    } 
                }
            }

            if found_mismatch {
                Err(format!("[ExprCheck] Error type mismatch expected {:?} gotten {:?} (<fn_name>)", args, c_params))
            }else{
                check_fn(s, &c_params, fn_env,var_mem)
            }
        }

        Expr::Ref(e) => {
            println!("[ExprCheck] Ref");
            let ty = check_expr(e, fn_env, var_mem)?;
            match ty {
                Type::Mut(_)    => Ok(Type::Ref(Box::new(ty))),
                _               => Ok(Type::Ref(Box::new(ty))),
            }
        }

        Expr::RefMut(e) => {
            println!("[ExprCheck] RefMut");
            let t = check_expr(e, fn_env, var_mem)?;
            match t {
                Type::Mut(_)    => Ok(Type::Ref(Box::new(t))),
                _               => Err(format!("[ExprCheck] Error not mutable {:?} (<fn_name>)", e)),
            }
        }

        Expr::DeRef(e) => {
            println!("[ExprCheck] DeRef");
            let mut ty = check_expr(e, fn_env, var_mem)?;

            ty = match ty {
                Type::Mut(t) => *t,
                _ => ty,
            };

            match ty {
                Type::Ref(t) => Ok(*t),
                _ => Err(format!("[ExprCheck] Error cannot dereference {:?} of type {:?} (<fn_name>)", e, ty)),
            }
        }

        Expr::Block(b) => {
            println!("[ExprCheck] Block");
            check_stmts(b, fn_env, var_mem)
        }

        Expr::Stmt(s) => {
            println!("[ExprCheck] Stmt");
            check_stmts(&vec![s.to_owned()], fn_env, var_mem)
        },
    }
}

fn check_recursive_id<'a>(e: &Expr, fn_env: &FnEnv, var_mem: &'a mut VarMem) -> Result<&'a mut Type, String> {
    match e {
        Expr::Term(Lit::Id(id)) => match var_mem.get_mut_type(id.to_string()) {
            Some(t) => Ok(t),
            None => Err(format!("[RecursiveCheck] Error variable {:?} not found (<fn_name>)", id)),
        },

        Expr::DeRef(e) => {
            let t = check_recursive_id(e, fn_env, var_mem)?;

            let t = match t {
                Type::Mut(t) => t,
                _ => t,
            };

            match t {
                Type::Ref(t) => Ok(t),
                _ => Err(format!("[RecursiveCheck] Error cannot dereference {:?} of type {:?}", e, t)),
            }
        }
        _ => Err(format!("[RecId] Error id is not a term nor a dereference")),
    }
}

fn check_stmts(stmts: &Vec<Stmt>, fn_env: &FnEnv, var_mem: &mut VarMem) -> Result<Type, String> {

    let ss = stmts;
    var_mem.push_empty_scope();
    let mut stmt_res: Result<Type, String> = Ok(Type::Unit);
    let mut i:i32 = 0;

    for s in ss {
        i += 1;
        println!("[StmtsCheck] Stmt: {:?}", s);
        println!("[VarMem] VarMem: {:?}", var_mem);
        stmt_res = match s {
            Stmt::Let(b, id, ty, e_orig) => {
                println!("[StmtsCheck] Let");
                let t: Type = match (ty, e_orig) {
                    (Some(t), Some(e)) => { // let a: i32 = 3;
                        let e_type = check_expr(&*e, fn_env, var_mem)?;
                        let e_type = match e_type {
                            Type::Mut(t) => *t,
                            _ => e_type,
                        };

                        let var_type = t.clone();
                        let var_type = match var_type {
                            Type::Mut(t) => *t,
                            _ => var_type,
                        };

                        match var_type == e_type {
                            true => var_type,
                            false => {
                                Err(format!("[StmtsCheck] Error type mismatch, expected {:?} gotten {:?} (line {:?}, <fn_name>)", var_type, e_type, i))?
                            }
                        }
                    }
                    (None, Some(e)) => { // let a = 3;
                        let e_type = check_expr(&*e, fn_env, var_mem)?;
                        match e_type {
                            Type::Mut(t) => *t,
                            _ => e_type,
                        }
                    }
                    (Some(t), None) => t.clone(), // let a:i32;
                    _ => Type::Unknown, // let a;
                };

                let t = match b {
                    true => Type::Mut(Box::new(t)),
                    false => t,
                };

                var_mem.new_id(id.clone(), t);
                Ok(Type::Unit)
            },

            Stmt::If(expr, s, es) => {
                println!("[StmtsCheck] If");
                match check_expr(&*expr, fn_env, var_mem)? {
                    Type::Bool => {
                        let s_type = check_stmts(&s, fn_env, var_mem)?;
                        match es {
                            None => Ok(s_type),
                            Some(es) => {
                                let es_type = check_stmts(&es, fn_env, var_mem)?;

                                match s_type == es_type {
                                    true => Ok(s_type),
                                    false => {
                                        Err(format!("[StmtsCheck] Error type mismatch, arms of the if stmt does not match. Expected {:?} gotten {:?} (line {:?}, <fn_name>)",s_type, es_type, i))
                                    }
                                }
                            }
                        }
                    }
                    _ => Err(format!("[StmtsCheck] Error condition not a Bool (line {:?}, <fn_name>)", i)),
                }
            },

            Stmt::While(expr, block) => {
                println!("[StmtsCheck] While");
                match check_expr(&*expr, fn_env, var_mem) {
                    Ok(Type::Bool) => {
                        let _ = check_stmts(&block, fn_env, var_mem)?;
                        Ok(Type::Unit)
                    }
                    _ => Err(format!("[StmtsCheck] Error condition not a Bool (line {:?}, <fn_name>)", i)),                
                }
            },

            Stmt::Assign(l_expr, r_expr) => {
                println!("[StmtsCheck] Assign");
                let r_type = check_expr(&*r_expr, fn_env, var_mem)?;
                let r_type = match r_type {
                    Type::Mut(t) => *t,
                    _ => r_type,
                };

                let l_type = check_recursive_id(l_expr, fn_env, var_mem)?;

                if match l_type {
                    Type::Unknown => {
                        *l_type = r_type.clone();
                        true
                    }
                    Type::Mut(t) => match **t {
                        Type::Unknown => {
                            *t = Box::new(r_type.clone());
                            true
                        }
                        _ => **t == r_type,
                    },

                    _ => Err(format!("[StmtsCheck] Error tried to assign to non mutable (line {:?}, <fn_name>)", i))?,
                } {
                    Ok(Type::Unit)
                } else {
                    Err(format!("[StmtsCheck] Error type mismatch, cannot assign {:?} to {:?} (line {:?}, <fn_name>)", &l_type, r_type, i))
                }
            },

            Stmt::Expr(e) => {
                println!("[StmtsCheck] Expr");
                check_expr(&*e, fn_env, var_mem)
            }

            Stmt::Block(b) => {
                println!("[StmtsCheck] Block");
                check_stmts(b, fn_env, var_mem)
            }

            Stmt::Return(e) => {
                println!("[StmtsCheck] Return");
                check_expr(&*e, fn_env, var_mem)
            }

            Stmt::Semi => Ok(Type::Unit),
        };
		if stmt_res.is_err(){
			break;
		}
    } 

    var_mem.pop_scope();
    stmt_res
}

fn check_fn(name: &str, params: &Vec<Type>, fn_env: &FnEnv, var_mem: &mut VarMem) -> Result<Type, String> {
    if let Some(decl) = fn_env.get(name) {
        println!("[FuncCheck] Typechecking function {:?} with params {:?}", name, params);
        let id: &Vec<String> = &decl.params.0.iter().
            map(|param| (param.name.to_owned())).collect();

        let raw_params: Vec<(&String, &Type)> = id.iter().zip(params).collect();
        let vars: HashMap<String, Type> = raw_params.into_iter().map(|(s,v )| (s.clone(), v.clone())).collect();
        
        println!("[FuncCheck] Converted params into variables: {:?}", &vars);
        var_mem.push_param_scope(vars);
        let ret_type = check_stmts(&decl.body, fn_env, var_mem);
        println!("[FuncCheck] Function {:?} returned as {:?} and containing the memory \n\t{:?}", name, ret_type, var_mem);
        var_mem.pop_scope();


        if ret_type.is_err() {
            Err(ret_type.unwrap_err().replace("<fn_name>", name))
        }else{
            if ret_type.to_owned().unwrap() == decl.ret_type {
                ret_type
            } else {
                Err(format!("[FuncCheck] Error type mismatch, function {:?} returned with {:?} expected {:?}", name, ret_type.to_owned().unwrap(), decl.ret_type))
            }
        }

    } else {
        panic!("[Func] Error function named {:?} not found.");
    }
}

fn main() {
    let mut var_mem = VarMem::new();
    let program = &ProgramParser::new().parse("
    fn func(x:i32, y:i32) -> i32 {
        x+y
    }
    
    fn test() -> bool {
        let mut a = 3;
        let b = &mut a;
        *b = 0;
    
        while a < 10 {
            *b = func(*b, 1);
        }
    
        let c = a / 3;
        if (c > 0) {
            true
        }
    }
    
    fn main() {
        test();
    }
    ").unwrap(); 
    let fn_env = progam_to_env(program);
    let args: Vec<Type> = Vec::new();
    println!("{:?}", check_fn("main", &args, &fn_env, &mut var_mem));
}

tests/w1_2.rs

deleted100644 → 0
+0 −72
Original line number Diff line number Diff line
// A set of small examples that your parser should accept and parse into an ast
// you should also be able to print the ast (debug ":?" or even prettier using display "{}")
//
// Notice that a sequence of statements may have an optional trailing ";"

fn _let_and_return() {
    // a function taking no arguments returning the unit type
    fn a() -> () {
        let _a: i32 = 5; // this returns a unit type
    }

    // a function taking two i32 arguments returning the i32 type
    fn b(_x: i32, _y: i32) -> i32 {
        3 // this returns 3 (as i32)
    }

    // a function taking two i32 arguments returning the i32 type
    // with some let statements
    fn c(x: i32, y: i32) -> i32 {
        let a: i32 = 5;
        let b: i32 = x + y; // this will be an infix operator "+""
        -a - (-b) * y // here we have prefix operator "-"
    }
}

// More advanced statements
fn _if_then_else_and_while() {
    // a function taking two bool arguments returning the bool type
    // with some let statements and function calls
    fn a(x: bool, y: bool) -> bool {
        if x && y {
            let a: bool = true;
            y || a
        } else {
            x && false
        }
    }

    // a function taking two bool arguments returning the i32 type
    // with some let statements and function calls
    fn b(x: bool, y: bool) -> i32 {
        let a: bool = a(x, y || false);
        let mut b: i32 = 0;
        if a && y {
            let a: bool = true; // shadowing
            if y || a {
                b = b + 1;
            };
        } else {
            if !(x && false) {
                b = b - 1;
            }
        };
        b + 3
    }

    // a function taking two bool arguments returning the i32 type
    // while
    fn c(x: bool, y: bool) -> i32 {
        let mut b: i32 = 0;
        let mut c: i32 = 1;
        while (b < 10) {
            c = c * 2;
        } // added ";" here... to make parsing easier, it is still valid Rust though
        c
    }
}

// optionally you may support other integer types, such as u8, u16, u32, u64, i8, i16, i64 and usize
// you may also support explicit local scopes
//
// later we will introduce references and user defined data types