- Faul-Lang compiler
A small compiler that translates faul-lang into MIPS assembly.
faul-compiler -h
usage: faul-compiler <file> [options]
-d,--debug Create parse tree and abstract sytntax tree
-h,--help print this message
-o,--out <arg> Output file
chmod +x ./src/test/resources/Mars4_5.jar
mvn clean package
./target/faul-compiler
java -jar ./target/faul-compiler
./target/faul-compiler examples/fibonacci.faul
java -jar ./src/test/resources/Mars4_5.jar examples/fibonacci.asm
Initially, there should be two data types: integer (int) and boolean (bool).
This should make it possible to formulate simple statements:
int n = readI();
if(n <= 1) {
print(false);
exit();
}
int i = 2;
while(i<n){
if(n % i == 0){
print(false);
exit();
}
i = i +1;
}
print(true);Statements are terminated by a semicolon.
Backus-Naur Form (BNF) is used to denote the rules.
More specifically, the syntax defined here is used: BNF Playground.
<program> ::= <ows> <statement>*
<statement> ::= "int" <ws> <ident> <ows> "=" <ows> <expression> <ows> <semi> <ows>
| "bool" <ws> <ident> <ows> "=" <ows> <expression> <ows> <semi> <ows>
| "if" <ows> "(" <ows> <expression> <ows> ")" <ows> "{" <ows> <statement>* <ows> "}" <ows> ("else" <ows> "{" <ows> <statement>* <ows> "}" <ows>)?
| "while" <ows> "(" <ows> <expression> <ows> ")" <ows> "{" <ows> <statement>* <ows> "}" <ows>
| <ident> <ows> "=" <ows> <expression> <ows> <semi> <ows>
| <ident> <ows> <function_call> <ows> <semi> <ows>
<expression> ::= <equality> ( <ows> ("&&" | "||" | "&" | "|") <ows> <expression> )?
<equality> ::= <comparision> ( <ows> ("!=" | "==") <ows> <equality> )?
<comparision> ::= <arithmeticExpr> ( <ows> (">" | ">=" | "<" | "<=") <ows> <comparision> )?
<arithmeticExpr> ::= <term> ( <ows> ("+" | "-") <ows> <arithmeticExpr> )?
<term> ::= <unary> ( <ows> ("*" | "/" | "%") <ows> <term> )?
<unary> ::= ("!" | "-") <ows> <unary>
| <primary>
<primary> ::= <vbool>
| <vint>
| <ident>
| "(" <ows> <expression> <ows> ")"
<vbool> ::= "true" | "false"
<vint> ::= [1-9] [0-9]*
| "0"
<ident> ::= ("_" | [a-z]) ("_" | [a-z] | [0-9])* (<ows> <function_call>)?
<function_call> ::= "(" <ows> (<expression> <ows> ("," <ows> <expression> <ows>)*)? ")"
<semi> ::= ";" (<ows> ";")*
<ws> ::= (" " | "\t" | "\n" | "\r")+
<ows> ::= (" " | "\t" | "\n" | "\r")*
- Function calls
- Parentheses
-!*/+->>=<<=&&|||&
Tokens of Faul-Lang.
| Name | Description |
|---|---|
| EOF | End of input |
| SEMICOLON | Statement terminator |
| V_INT | Integer |
| V_BOOL | Boolean truth value |
| OPEN_PARENTHESES | { |
| CLOSE_PARENTHESES | } |
| IDENT | Variable name |
| Name | Description |
|---|---|
| INT | To declare an integer |
| BOOL | To declare a boolean |
| IF | Starts an If-Statement |
| ELSE | Starts an ELSE-Statement |
| WHILE | Starts a While-Statement |
| Name | Description |
|---|---|
| EQ | = |
| PLUS | + |
| MINUS | - |
| ASTERISK | * |
| SLASH | / |
| OR | |
| AND | & (Bitwise AND) |
| EQEQ | == |
| NOTEQ | != |
| GT | > |
| GTEQ | >= |
| LT | < |
| LTEQ | <= |
| LOR | |
| LAND | && (Logical AND) |
| NOT | ! |
| OPEN_BRACKET | ( |
| CLOSE_BRACKET | ) |
int n = readI();
if(n <= 1) {
print(false);
exit();
}
int i = 2;
while(i<n){
if(n % i == 0){
print(false);
exit();
}
i = i +1;
}
print(true);The lexer generates a stream of Tokens from the Input.
The parser processes the Token-Stream and outputs a parse tree. During this process, it also checks for syntax errors.
This can be printed out using the toString method, for example like this:
Parse Tree (Syntax Tree)
PROGRAM
├── STATEMENT
│ ├── IDENT: n
│ ├── EQ: =
│ ├── EXPRESSION
│ │ └── EQUALITY
│ │ └── COMPARISON
│ │ └── ARITHMETIC_EXPR
│ │ └── TERM
│ │ └── UNARY
│ │ └── PRIMARY
│ │ ├── IDENT: readI
│ │ └── FUNC_CALL
│ └── SEMICOLON: ;
... (omitted for brevity, assume full output follows identically)
(Note: I kept the code block structure identical here to match your original)
Often, a parse tree is not created at all, but rather an Abstract Syntax Tree (AST) directly. This compiler does not do this for reasons of simplicity. The AbstractSyntaxTreeFactory processes the Parse Tree into an AST.
This can also be output with the toString() method as follows:
Abstract Syntax Tree
PROGRAM, Type: T: VOID; rT: null, Position: 0:0
├── DECLARATION, Type: T: VOID; rT: VOID, Position: 1:1
│ ├── IDENT: n, Type: T: VAR; rT: INTEGER, Position: 1:5
... (omitted for brevity, assume full output follows identically)
Currently, there are no major optimizations. Empty "else" statements are ignored.
However, possible optimizations could include:
- Ignoring plus and minus
0 - Setting terms to
0if they are multiplied by zero - Not branching or removing the branch entirely for expressions that evaluate statically to
trueorfalse - Replacing static variables with their respective values
Code generation occurs naively by traversing the AST. For Expressions, a modified depth-first search (DFS) is used to evaluate them in the correct order.
The DFS is adapted so that instead of always visiting the left or right branch of the binary tree first, it chooses the branch that is deeper. This ensures that no more than two intermediate result registers are ever needed.
The generated code is MIPS32 Assembly. MIPS32 Instruction Set Quick Reference
To run it, you can use the MARS MIPS simulator - Missouri State University. This also supports some pseudo-instructions, such as li.
By convention, the registers $s0 - $s7 are used to store values across procedure calls over a longer period of time. Procedures (functions) must (or should) restore these before returning. Therefore, loaded variables are stored there.
Of course, a program can have more than eight variables, which is why there must be a strategy to decide when which variable is loaded into which register and when it is written to memory.
When a variable is needed, is not already in a register, and all registers are occupied, the variable that has not been used for the longest time is moved to memory. After that, a register is free for the new variable.
main:
# initializing s0 with address 16
move $s0, $0
jal readI
nop
# Assgining reg s0 with address 4
move $s0, $v0
# If start
# Loading value 1
li $t0, 1
# Less than equals
sle $t0, $s0, $t0
beqz $t0, label_0
...
---
Title: Integer Parser
---
stateDiagram-v2
[*] --> s0
s0 --> s1:1,2,3,4,5,6,7,8,9
s1 --> s1:0,1,2,3,4,5,6,7,8,9
s1 --> s2: +,-.*,/,<,>,!,),#semi;
s2 --> [*]
---
Title: Word Parser
---
stateDiagram-v2
[*] --> s0
s0 --> s1: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,\nA,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,Y,Z,_
s1 --> s1:0,1,2,3,4,5,6,7,8,9,\na,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,\nA,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,Y,Z,_
s1 --> s2: WHITESPACE, +,-.*,/,<,>,!,),,(,{,#semi;
s2 --> [*]
-
Lexer
-
Parser
-
Abstract Syntax Tree (AST)
-
Semantic Analysis
-
Typechecking
-
Optimization -
Code generation