All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
No unreleased changes yet
Added stable LIR frontend with type checker and polymorphic types.
Expanded standard instruction set.
Added methods to types, associated constants, and expanded support for foreign functions.
Changed type system to accommodate methods.
Fixed bug where unary -
operator did not type-check on Float
values.
Support for typechecking expressions in parallel. Support for SIMD vector instructions.
All the VM instructions now take an optional "size" constant parameter which determines the size of the vector they're operating on. The register is now a vector, not a scalar.
Removed sage-os
and x86
targets. x86
will be added back later, but likely using the C
target as an intermediate step to produced better optimized x86
.
New VM instructions:
Inc
Dec
Offset
And
Or
Not
BitwiseAnd
BitwiseOr
BitwiseXor
BitwiseNot
Generated code size is now up to 7x smaller than before.
Sage has just gotten a major upgrade, with a new module system📦 parser📝 and the Sage Lisp Preprocessor🛸!!!
// Define some code that modifies the AST of the expression it's called on
#![(defun square_sage_const(x) {
// Get a constant integer
(define n x@"ConstExpr"@"Int")
// Return the constant integer squared
["ConstExpr" ["Float" n * n]]
})]
// Call the compile time macro on 5.
// This will cause the compiler to perceive this code as `println(25)`
println(#[square_sage_const] 5);
- Module system
// Import a module from a file named `from_file.sg`
mod from_file;
// Define a module `io` with `getchar`
mod io {
fun getchar(): Char {
let mut ch = '\0';
input(&mut ch);
return ch;
}
}
// A module to test importing from other modules
mod testing {
mod internals {
// Import getchar from two modules up
from io import getchar;
// Use it in a function
fun get_two(): (Char, Char) { (getchar(), getchar()) }
}
// Get two chars and print them
fun get_two_then_print() {
// Import from a submodule, which imports from a supermodule
from internals import get_two;
let (a, b) = get_two();
print(a, b);
}
}
from testing import get_two_then_print;
get_two_then_print();
// Get two values and print them
for let mut i=0; i<6; i+=1; {
// We can also use the full path name
testing.get_two_then_print();
}
- Standard library
from std.fallible import Option, Result;
enum Error {
DivideByZero { numerator: Int },
Custom(&Char)
}
fun divide(n: Int, d: Int): Option<Int> {
match d {
0 => Option<Int> of Nothing,
_ => Option<Int> of Some(n / d)
}
}
fun main(): Result<(), Error> {
println(divide(5, 2));
println(divide(5, 0));
return Result<(), Error> of Ok(());
}
println(main());
- Better constant evaluation
from module.submodule import x, y as alias_for_y;
import statementsfrom module.submodule import *
import statements
- The
def
keyword has been changed in favor offun
. for
loops now have an extra semicolon on the last statement.i += 1 {
becomesi += 1; {
.
The compiler is significantly faster -- about 20 times faster at compiling the AES example. This is mainly due to the much faster parser implemented with Nom instead of Pest. There are also several optimizations with constant evaluation I added, along with optimizations for how declarations are typechecked.
Added const
generics, so constant parameters can be passed along with types through templates. This allows the typechecker to be applied to many more aspects of code, including examples like typechecking dimensions of matrix multiplications at compile time.
struct Matrix<T, const Rows: Int, const Cols: Int> {
arr: [[T * Cols] * Rows]
}
impl Matrix<T, Rows, Cols> {
fun new(x: T): Matrix<T, Rows, Cols> {
return {arr=[[x] * Cols] * Rows};
}
fun get(&self, row: Int, col: Int): &T {
return &self.arr[row][col];
}
fun mul<const NewCols: Int>(
&self,
other: &Matrix<T, Cols, NewCols>,
zero: T,
add: fun(T, T) -> T,
mul: fun(T, T) -> T
): Matrix<T, Rows, NewCols> {
let mut result = Matrix.new<T, Rows, NewCols>(zero);
for let mut j=0; j<NewCols; j+=1; {
for let mut i=0; i<Rows; i+=1; {
let mut sum = zero;
for let mut k=0; k<Cols; k+=1; {
sum = add(sum, mul(self.arr[i][k], other.arr[k][j]));
}
result.arr[i][j] = sum;
}
}
result
}
}
let mut x = Matrix.new<Int, 4, 4>(10);
let mut y = Matrix.new<Int, 4, 4>(5);
println(x);
println(y);
fun add_ints(a: Int, b: Int): Int = a + b;
fun mul_ints(a: Int, b: Int): Int = a * b;
let z = y.mul<4>(&x, 0, add_ints, mul_ints);
println(z);
Changed type system to accommodate const generics. This was done by adding a ConstParam
variant to types.
Improved parser speed by a significant amount during the process.