Welcome to the tutorial of the Pier programming language. For now, this is just a short description of the language's features and syntax, as well as how to compile and run Pier programs.


Basic experience with using the command line is suggested.
Moderate to advanced experience in C is expected.
Linux is the only officially supported platform right now. Have also compiled on FreeBSD without issue.

Getting Started

Before we can begin, you will need the compiler, and the Pier VM. The source code can be obtained from my GitHub, github.com/gabecampb. They will all need to be compiled from source. In your terminal, run the commands:

git clone https://github.com/gabecampb/pier git clone https://github.com/gabecampb/pier-vm git clone https://github.com/gabecampb/pier-asm git clone https://github.com/gabecampb/pier-libraries

The first command will clone the compiler repo, the second the VM repo, the third the assembler repo, and the fourth is the repo of some libraries we will be using shortly. The first three can then be compiled by:

cd pier-vm cc vm.c -o pvm -lGL -lglfw -lm cd ../pier-asm cc assembler.c -o pasm cd ../pier cc compiler.c -o pc

And that's it! We now have a copy of the compiler, assembler, and VM.

To use the compiler, the kernel.asm file found within the compiler's repo needs to be present in the working directory that the compiler is run from. This is a temporary requirement until the file is merged into the compiler, because the compiler reads kernel.asm for some important code to insert into compiled programs (it contains program loader code, and the implementation of system functions).

Let's test this build of Pier by creating our first program. Of course, this will be a classic "Hello, World!". Create a file named hello.pi in the compiler's directory, with the following contents:

-- pier/hello.pi use "stdlib.pi"; fn main { print("Hello, World!\n"); }

Now, we will proceed to compile the program, after we copy stdlib.pi from pier-libraries to the compiler's directory (the 'use' statement in this file is given as relative to the working directory the compiler is run from). This can be done by the below commands:

cp ../pier-libraries/stdlib.pi . ./pc hello.pi stdlib.pi

The compiler generates an out.asm file, which will be run through the assembler.

Note that while stdlib.pi, being the "standard library", should ship with the compiler, it resides in a separate repo for now alongside other libraries I've created for the language since everything's still in development anyway.

To run the generated out.asm through the assembler and then execute the resulting program, simply run the following:

../pier-asm/pasm out.asm ../pier-vm/pvm out.bin

A program file named out.bin is created, and "Hello, World!" is printed to the console!

Language Overview

Pier is a language that is very similar to C, and C programmers should be comfortable with this language immediately. Keywords are short in interest of keeping code compact. Built-in data types include single-precision (32-bit) floating-point f32 and double-precision (64-bit) floating-point f64, as well as unsigned (prefix u) and signed (prefix i) integers with sizes (suffix) 8-, 16-, 32-, or 64-bit (u8, u16, u32, u64, i8, i16, i32, i64).

Control structures include while loops and conditionals - for loops aren't supported yet. Keywords if, elif, and else are used for conditionals, and while for while loops. Return statements use the keyword ret. Identifiers (for functions, variables, structure names) can contain characters from the alphabet, numbers 0-9, and underscores, but cannot start with a number.

Floating-point numbers are guaranteed to be represented by IEEE-754, and signed integers are guaranteed to be represented by two's complement. Floating-point literals are f32 by default, but can be made f64 by suffixing the literal with d or D. The endianness of the platform is little-endian. The size of arrays must be given as integer literals, and global variable initializations must be constant expressions. The initial value of local variables, unless they're initialized, is undefined; its value will just be whatever was previously on the stack at the variable's address. All of main memory is initialized to 0 when the VM starts.

Variables can be defined as arrays with [size] following their identifier, and variable definitions (regardless of whether occuring in global scope, in a function, or in a struct definition) can have a list of definitions. Pointers are supported; the * denoting that a variable is a pointer must follow the type name, and for the 2nd or later variable names in a multi-variable definition, must in front of the identifier. Structures can be defined as struct struct_name { ..field_defs.. }. Here's an example of struct and variable definitions:

i32 x, y=5; i32** z, *w[2]; struct my_type_t { f32 a[4]; u8 b[2]; } my_type_t objects[10]; f32 floats[2] = { 2.5, 1. };

Function definitions are of the form:
fn [type] identifier (param_list).
The type following fn is an optional return type specifier. The parameters list can be omitted entirely (optionally omitting the parentheses as well) for defining functions with no parameters. The parameter list is comma-separated, with each parameter being specified as: type identifier.
A few examples of valid function definitions:

fn u64 my_function(f32 x, i32* y) { ; } fn my_function2() { ; } fn my_function3 { ; }

Below is a list of all currently reserved keywords.

use fn ret if elif else while break continue i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 sizeof struct extern


Pier supports a variety of different unary and binary operators, which each have some precedence level and either left-to-right or right-to-left associativity. Below is a list of all of the currently available operators, including their precedence level, associativity, and arity:

The Standard Library

The pier-libraries repo provides stdlib.pi, which is intended to eventually ship alongside the compiler to serve as the standard library. Being unfinished, it's still subject to changes, but it's a small enough library that maintaining your own copy of it for each program you write is easy. Additionally, the libraries are dedicated to the public domain, so there's no limitations on how you use them.

Functions provided include file I/O, memory allocation functions, printing functions, rand() and srand() for pseudo-random numbers, math functions, and more. Each system call is submitted to the kernel using the __syscall() function, which writes an 8-bit syscall ID and 64-bit syscall parameter to the location in memory which corresponds to the current thread's syscall request (this region is checked periodically by the kernel).

More will be added to this tutorial soon.