This project implements a super simple compiler for a custom programming language called 'Sprs' using Rust and LLVM via the Inkwell library. The compiler translates Sprs source code into LLVM IR, which is then compiled into machine code for execution. The compiler is dynamic type checking and easy to use and clear for the base of the language design.
- Inkwell - LLVM bindings for Rust
- logos - Lexer generator for Rust
- lalrpop - LR(1) parser generator for Rust
- Rust - The programming language used to implement the compiler
- Clang/LLVM - Used for linking and generating executables
- cargo-rdme - For generating README from doc comments
- serde - Serialization framework for Rust
- toml - TOML parsing library for Rust
attention: This is still under development and may change in the future and currently didn't work interpreter system.
For this language development environment setup is WSL2(Ubuntu) + VSCode is recommended.
- Install Rust and WSL2(Ubuntu).
sudo apt update && sudo apt install -y lsb-release wget software-properties-common gnupgwget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh && sudo ./llvm.sh 18 allsudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-18 100 && sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-18 100 && sudo update-alternatives --install /usr/bin/llvm-config llvm-config /usr/bin/llvm-config-18 100 && sudo update-alternatives --install /usr/bin/llvm-as llvm-as /usr/bin/llvm-as-18 100 && sudo update-alternatives --install /usr/bin/llc llc /usr/bin/llc-18 100sudo apt-get install zlib1g-dev libzstd-dev && sudo apt-get install libncurses5-dev libxml2-dev- Clone this repository and open it in VSCode.
- Install the Rust extension for VSCode.
- Build and run the project using
cargo buildandcargo run
- Int (i64)
- Float (f64) : return type semantic use this word 'fp'
- Bool
- Str
- List(128) (dynamic array)
- Range
- Unit
- Enum
- Struct
- i8 (only for cast! macro)
- u8 (only for cast! macro)
- i16 (only for cast! macro)
- u16 (only for cast! macro)
- i32 (only for cast! macro)
- u32 (only for cast! macro)
- i64 (only for cast! macro)
- u64 (only for cast! macro)
- f16 (only for cast! macro)
- f32 (only for cast! macro)
- f64 (only for cast! macro)
- Variables and assignments
# Comments start with a hash symbol
var x = 10;
var name = "sprs";
var is_valid = true;
var numbers = [1, 2, 3];
# Not initialized variable
var y; # y is initialized to Unit type
# Re-assignment
var y;
y = 20;
y = "now a string"; # y is now a string
- Functions
fn add(a, b) {
return a + b;
}
fn main() {
result = add(5, 10);
println!(result);
}if a function is not marked as 'pub', it is private function. the function can call in same module.
if when need to use a return type for function, use '>>' syntax.
fn add(a, b) >> int {
return a + b;
}-
runtime functions
Function Name Description __list_new for creating a new list __list_get for getting an element from a list by index __list_push for pushing an element to the end of a list __range_new for creating a new range __println for printing values to the console __strlen for getting the length of a string __malloc for allocating memory __drop for dropping a value __clone for cloning a value __panic for handling panic situations -
enum
pub enum Animal {
Dog,
Cat,
}
fn main() {
println!(Animal.Dog);
}- struct
pub struct Point {
x >> i64,
y >> i64
}
fn main() {
var p = Point {
x = 10,
y = 20
};
println!(p.x); # prints 10
println!(p.y); # prints 20
}- Control flow
if x > 5 then {
println!("x is greater than 5");
} else {
println!("x is 5 or less");
}
while x < 10 {
println(x);
i++;
}- Arithmetic:
+,-,*,/,% - Comparison:
==,!=,<,>,<=,>= - Increment/Decrement:
++,--(only for postfix) - Range creation:
..(e.g.,1..10) - indexing:
list[index]
println!(value): Print value to the console examples:
println!(y[1]);list_push!(list, value): Push value to the end of the list examples:
list_push!(y, z);clone!(value): Clone the value examples:
var a = "hello";
println!(clone!(a));cast!(value, type): Cast the value to the specified type examples:
var a = 100; # default is i64
var b = cast!(a, i8); # cast to i8
println!(b); # prints 100 as i8Note: cast! macro is more faster then normal int type, because it use i8 and u8 llvm type directly. examples:
var i = 0; # default is i64
while i < 5 {
println!(i); ## this is too slow for embedded and system programming environment, because it use dynamic type checking.
i = i + 1;
}but with cast! macro
var i = cast!(0, i8); # i is i8 type
while i < cast!(5, i8) {
println!(i); ## this is faster for embedded system, because it use i8 llvm type directly.
i = i + cast!(1, i8);
}#definefor defining macros Currently this language has#define Windowsor#define Linuxfor OS detection- 'pkg' for module definition
- 'import' for module importing
examples:
import test;
#define Windows
fn main() {
var x = test.test();
var y = [];
var z = 20;
var alpha = "test";
var beta = true;
println!(x);
list_push!(y, z);
list_push!(y, alpha);
println!(y[1]);
var result = (x + 10) * 2;
println!(result);
var i = cast!(0, i8);
while i <= 5 {
println!(i);
i = i + 1;
}
var m = 10 % 3;
println!(m);
}pkg test;
fn test() {
var a = 5 - 1;
var b = 10;
var c = "hello" + " world";
println!(c);
if a == 3 then {
return a;
}
if a != 3 then {
return a++;
} else {
return a + 2;
}
return b;
}To build and run a Sprs program, use the following commands:
# To build the project
sprs build
# To run the project
sprs runTo initialize a new Sprs project, use the following command:
sprs init --name <project_name>This command creates a new directory structure with a default sprs.toml configuration file and a sample main.sprs source file.
The Sprs has a simple runtime move system.
Example:
fn main() {
test();
}
fn test() {
var test = "Hello, Sprs!"; # set a string to variable
var a = test; # move the value from test to a, test is now invalid
return println!(a); # function call with a, a is now invalid after this line
println!(clone!(a)); # a is still valid after this line
}