Skip to main content

About The Sage Programming Language

·1423 words·7 mins
Documentation - This article is part of a series.
Part 1: This Article

Table Of Contents
#

  1. Overview
  2. Sage’s History
  3. What Can I Use Sage For?
  4. Community
  5. Code Examples And Syntax
  6. Learn More
  7. About The Author

Overview
#

Sage is a small programming language with a unique blend of features from Rust and Python. It’s statically typed, portable, and easy-to-use.

Why Sage? The Sage programming language was created as an alternative to languages like C, but with a nicer type system and development experience. Users are able to print out functions, types, and any runtime value. enums are more powerful, and can be used for better error management. Immutability is by default, with enforced typechecking and mutability rules. Sage also includes a module system for organizing code.

Sage can be used for anything from operating systems development, to web development.

Sage’s History
#

Check out the video below to learn more about the history of Sage’s development! It visualizes all the modifications made to the compiler since its creation. Sage began in 2022, and it’s come a long way since then!

History: Sage was originally born out of a SIMD-extended Brainf$%&@ compiler, before it was developed into a full-fledged programming language.

What Can I Use Sage For?
#

Sage can be used in the web, or for low-level systems programming. Here’s an operating system with its entire userspace written in Sage!

A Shell Written In Sage For Sage-OS
#

Here’s a shell written in Sage!

Sage OS Shell #1

It lets users manipulate the filesystem, run programs, and interact with the operating system.

Sage OS Shell #2

A PowerPoint App Written In Sage For Sage-OS
#

Here’s a PowerPoint app written in Sage for Sage-OS. It loads images from the filesystem and uses the device drivers to receive keyboard commands and render the images to the screen!

Sage OS

Community
#

Check out our community page to get in contact with other users and developers! Let us know if you have any thoughts or comments about the language!

Code Examples And Syntax
#

Without further ado, let’s see what Sage’s syntax looks like!

Hello World!
#

Sage code is like Python – you can just start writing without a main function!

To print something, just use print on whatever arguments you want!

println("Hello world!");
Output:
Hello world!

print also works on custom data structures.

println("Hello! ", {x = 5, y = 6, z = 7}, " ", [1, 2, 3, 4]);
Output:
Hello! {x=5, y=6, z=7} [1, 2, 3, 4]

Functions
#

You can write functions in Sage using the fun keyword. Here’s an example of the gcd function implemented in Sage!

// Calculate the greatest common divisor of two numbers
// using Euclid's algorithm
fun gcd(a: Int, b: Int): Int {
    if b == 0 {
        return a;
    }
    return gcd(b, a % b);
}

println(gcd(12, 15));
Output:
3

Standard Library Imports
#

You can get started with the standard library by importing some modules!

// Import some stuff!
from std.fallible import Option, Result, panic;
from std.collections import Vec, HashMap, List;
from std.io import *;

// Create a vector and push stuff onto it!
let mut v = Vec.make<Int>();
v.push(1);
v.push(2);
v.push(3);

v.print();
Output:
[1, 2, 3]

Structs And Methods
#

Here’s how we can define custom struct types, and imbue them with methods we can call!

// Create a structure named Point, with members `x` and `y`
struct Point {
    x: Int,
    y: Int
}

impl Point {
    // A method for creating a new point
    fun new(x: Int, y: Int): Point {
        return {x = x, y = y};
    }

    // Shift this point by a given amount in the X
    // and Y directions
    fun translate(&mut self, dx: Int, dy: Int) {
        self.x += dx;
        self.y += dy;
    }
}

// Create a mutable point at (4, 5)
let mut p = Point.new(4, 5);
// Print the point
println(p);
// Translate the X by -5, and the Y by 10
p.translate(-5, 10);
// Print the translated point
println(p);
Output:
{x=4, y=5}
{x=-1, y=15}

Sum Types
#

Sage supports generic algebraic datatypes, and typechecks them with structural equality! This lets you define types linked-lists in a much more canonical way than most languages.

enum List<Elem> {
    Cons {
        data: Elem, 
        next: &List<Elem>
    },
    Nil
}

let l = List<Int> of Cons {
    data=5,
    next=new List<Int> of Nil
};

match l {
    of Cons {data, next} => {
        println("l is cons with data=", data, " and next=", next);
    },
    of Nil => {
        println("Got nil");
    }
}
Output:
l is cons with data=5 and next=&(38999)

Const-Generics
#

Sage’s typesystem is also equipped to handle types with const parameters. This can come in handy for many use-cases. For example, this feature allows you to typecheck matrix dimensions in matrix multiplications!

// Define a constant sized matrix with a generic element type
// and parameterized width and height.
struct Matrix<T, const Rows: Int, const Cols: Int> {
    arr: [[T * Cols] * Rows]
}

// Add some methods to our matrix
impl Matrix<T, Rows, Cols> {
    // Create a new matrix populated with initial values
    fun new(x: T): Matrix<T, Rows, Cols> {
        return {arr=[[x] * Cols] * Rows};
    }

    // Get a value from a matrix
    fun get(&self, row: Int, col: Int): &T {
        return &self.arr[row][col];
    }

    // Multiply with another matrix
    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);
        // Perform the actual matrix multiplication on the rows and cols
        // This uses the naive algorithm for matrix multiplication
        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
    }
}

Virtual Machine Instruction Set
#

Sage’s instruction set is tiny enough to port in an afternoon, but efficient enough to compile high level code efficiently. The virtual machine is a simple Turing-tape based architecture, not a stack or register based virtual machine. This is one of the contributing factors to Sage’s portability.

InstructionC Equivalent
whilewhile (reg[0]) {
ifif (reg[0]) {
else} else {
end}
set N_0, N_1, ..., N_Xreg[0] = N_0; reg[1] = N_1; ... reg[x] = N_X;
callfuns[reg[0]]();
retreturn;
load Nmemcpy(reg, tape_ptr, N * sizeof(cell));
store Nmemcpy(tape_ptr, reg, N * sizeof(cell));
move Ntape_ptr += N;
wherereg[0].p = tape_ptr;
derefpush(tape_ptr); tape_ptr = *tape_ptr;
refertape_ptr = pop();
index Nfor (int i=0; i<N; i++) reg[i].p += tape_ptr->i;
offset O, Nfor (int i=0; i<N; i++) reg[i].p += O;
swap Nfor (int i=0; i<N; i++) swap(reg + i, tape_ptr + i);
add Nfor (int i=0; i<N; i++) reg[i].i += tape_ptr[i].i;
sub Nfor (int i=0; i<N; i++) reg[i].i -= tape_ptr[i].i;
mul Nfor (int i=0; i<N; i++) reg[i].i *= tape_ptr[i].i;
div Nfor (int i=0; i<N; i++) reg[i].i /= tape_ptr[i].i;
rem Nfor (int i=0; i<N; i++) reg[i].i %= tape_ptr[i].i;
or Nfor (int i=0; i<N; i++) reg[i].i ||= tape_ptr[i].i;
and Nfor (int i=0; i<N; i++) reg[i].i &&= tape_ptr[i].i;
not Nfor (int i=0; i<N; i++) reg[i].i = !reg[i].i;
bitand Nfor (int i=0; i<N; i++) reg[i].i &= tape_ptr[i].i;
bitor Nfor (int i=0; i<N; i++) reg[i].i |= tape_ptr[i].i;
bitxor Nfor (int i=0; i<N; i++) reg[i].i ^= tape_ptr[i].i;
lsh Nfor (int i=0; i<N; i++) reg[i].i <<= tape_ptr[i].i;
l-rsh Nfor (int i=0; i<N; i++) reg[i].i = (uint64_t)reg[i].i >> tape_ptr[i].i;
a-rsh Nfor (int i=0; i<N; i++) reg[i].i >>= tape_ptr[i].i;
gez Nfor (int i=0; i<N; i++) reg[i].i = reg[i].i >= 0;

Learn More
#

Documentation: First, take a look at the docs!

You can read my blog post (~20 minute read) about the programming language to learn more about the implementation!

Here’s a 23 minute YouTube video that covers how compilers work, and delves into Sage!

Join the Discord server to chat about Sage!

How do I contribute?
#

If you want to contribute, you can open an issue or a pull request. Adding backends for other architectures is a great way to contribute! We also need a VSCode syntax highlighting extension!

About The Author
#

I’m a computer science PhD student at the University of Tennessee, Knoxville🍊. Rust is my favorite language, and I’ve written many other compilers. This is the last project I started as a teenager, and I was the only author to touch any of the code up to version v0.0.2-alpha (12/25/2023)!

Documentation - This article is part of a series.
Part 1: This Article