This repository contains the source code for the Very Simple Language (VSL) compiler, a 64-bit version of the language from the book "Introduction to Compiling Techniques" by Jeremy P. Bennett.
The compiler is a semester project for the Compiler Construction (TDT4205) course at NTNU.
Using GitHub Codespaces, checking out the compiler is straightforward. It provides an environment with all necessary tools installed, and the code ready to be built and run.
If you prefer to build the compiler locally, follow the instructions below.
To build the compiler locally, you will need:
- CMake v. 3.21
- Python v. 3.10
- gcc
- flex v. 2.6
- bison v. 3.5
- GNU make and Ninja
Create the build files with Ninja. This step is required only once.
cmake -B build -GNinja
Then, compile the code. Run this command whenever you make changes to the code.
cmake --build build
The tests can be run with the following command from the tests/
directory:
make check-all
The final binary can be found at build/vslc
. Use the --help
option for more details on available commands.
Input is passed to stdin
, output is printed to stdout
.
To pass a file to the compiler, for example:
build/vslc -c < tests/codegen/sieve.vsl
To write the output to a file and execute it:
build/vslc -c < tests/codegen/sieve.vsl > sieve.s
gcc -o sieve sieve.s
./sieve 100
Note that 100
is the argument for the sieve program, representing the upper limit for finding prime numbers. gcc
is used to compile the generated assembly code into an executable binary.
The compiler supports the following features:
- Variables and arrays
- Global and local variables
- Arithmetic and logical expressions
- if-then, if-then-else, and if-then-else-if-else statements
- While loops and break statements
- Nested if, while, and break statements
- print statements with variables and strings
- Functions with parameters and return values
VSL (Very Simple Language) is an imperative language with a straightforward syntax. Below is a detailed description of its key components with concise examples.
- Variables: Use the var keyword.
- Arrays: Declare arrays with a specified size.
var x, y, z
var array[10]
- Declaration: Introducing a variable or array without assigning it a value.
- Binding: Assigning a value to a declared variable using the
:=
operator. - A variable must be declared before it can be bound to a value.
var x // Declaration
x := 10 // Binding
- Global Variables: Declared outside of any function.
- Local Variables: Declared within functions or blocks.
var global_var, global_array[5]
func example() begin
var local_var, local_array[3]
end
- Arithmetic Operators:
+
,-
,*
,/
- Bitwise Operators:
<<
(left shift),>>
(right shift)
func main() begin
var x, y, z
x := y + z
y := 5 + z * 3
z := z * z - 3
x := (x + y) / z
x := y << 1
y := x >> 1
end
- Assignment: Use the
:=
operator to bind values to variables. Reassigning a value is allowed. - Arithmetic: Standard arithmetic operations are supported.
x := y + z
y := 5 + z * 3
z := z * z - 3
x := (x + y) / z
- Use the
print
statement to output variables and strings tostdout
. - Separate variables and strings with commas.
print "Hello, World!"
print x, y, z
print "Value of x is: ", x
- Single-line comments start with
//
.
// This is a comment
var x // This is an inline comment
if condition then begin
// code
end
if condition then
// code
else
// code
if condition1 then begin
// code
// some more code
end
else if condition2 then
// code
else
// code
Note: Scope is defined by the begin
and end
keywords. If an if
statement has only one line of code, the begin
and end
keywords are optional.
- Use the
while
keyword to create a loop. Break statements can be used to exit the loop.
while condition do begin
// code
if another_condition then
break
end
- Functions: Defined using the func keyword.
- Parameters: Specified in parentheses. Any number of parameters can be passed.
- Body: Enclosed in begin and end blocks.
- Return: Functions can return values using the return statement.
func add(a, b) begin
return a + b
end
func main() begin
print add(40, 2)
end
Examples of VSL code can be found in the tests/codegen folder.
The compiler consists of three main parts: the front-end, middle-end, and back-end.
The front-end comprises two components:
- Scanner: Converts a stream of characters into tokens (implemented using Flex)
- Parser: Converts tokens into a parse tree (implemented using Bison)
This part applies optimizations to the syntax tree. Constant folding and peephole optimization are implemented.
The backend generates x86-64 assembly code from the syntax tree.
This compiler is a school semester project and as such has several significant limitations:
- Only integers are supported; floating-point numbers are not.
- Types are not supported; all variables are integers, arrays of integers, or strings.
- No liveness analysis or register allocation is performed, resulting in inefficient generated code.
- Primarily supports Linux; experimental macOS support is available but tested only with GitHub Actions CI on Apple Silicon runners. Windows is not supported.
Despite these limitations, the compiler can compile and run various simple programs. Please do not use it for critical tasks, as no warranty is provided or implied.