Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

This book is the main documentation for the Orn programming language.

Note

For bugs or missing content in this book please open an issue in the GitHub repo.

Warning

Orn is being designed and developed. Some of the syntax in this book may not be implemented yet.

What is Orn?

Orn is a systems language where every type is a range. Instead of choosing between int, u8 or bool, a value is described by the integers it can hold, written lo..hi with both ends inclusive.

age : 0..150 = 30;

The compiler keeps two ranges for every value:

  • The declared range fixes how the value is stored. It never changes.
  • The known range is what the compiler can prove at each point of the program. Comparisons make it smaller.
classify :: (i: 0..1000) 0..2 {
    if i < 10 {
        // known range of i: 0..9
        0
    } else if i < 100 {
        // known range of i: 10..99
        1
    } else {
        // known range of i: 100..1000
        2
    }
};

Types are aliases

The usual type names are ordinary constant bindings in the standard library:

u8   :: 0..255;
i32  :: -2147483648..2147483647;
bool :: 0..1;

u8 and 0..255 are the same thing. A value can be passed wherever the expected range contains its own.

No silent overflow

Arithmetic follows the ranges of its operands. Adding two u8 gives 0..510, and storing that in a u8 is a compile error. Wrapping or saturating has to be asked for explicitly.

When a value comes from outside and its range is unknown, in checks it at runtime and narrows it:

age :: (n: -32768..32767) 0..150 {
    n in 0..150 else { ret 0; }
};

Limits

Ranges are checked with interval arithmetic, which treats operands as independent. Some correct programs are rejected: x * x with x : -3..3 is -9..9, not 0..9. The examples/errors folder in the repository collects these cases.

Grammar

This is the grammar of the Orn MVP. Everything in Orn is a range: there are no built-in types, only sets of integer values written as lo..hi (both ends inclusive). Names like u8 or bool are ordinary constant bindings.

program        = { stmt } ;

stmt           = ( binding | return | if_stmt | loop_stmt | block | expr ) ";" ;
block          = "{" { stmt } "}" ;
return         = "ret" [ expr ] ;
if_stmt        = "if" expr block [ "else" ( if_stmt | block ) ] ;
loop_stmt      = "loop" ( ID "in" expr | expr ) block ;

binding        = ID "::" expr
               | ID ":=" expr
               | ID ":" expr ( "=" | ":" ) expr ;

expr           = expr binop expr
               | prefix_op expr
               | expr "(" [ args ] ")"
               | primary ;
binop          = "||" | "&&"
               | "==" | "!=" | "<" | ">" | "<=" | ">="
               | ".." | "+" | "-" | "*" | "/" | "%" ;
prefix_op      = "-" | "!" ;
args           = expr { "," expr } ;

primary        = literal | ID | "(" expr ")" | fn_expr ;
fn_expr        = "(" [ params ] ")" [ expr ] block ;
params         = param { "," param } ;
param          = ID ":" expr ;

literal        = NUMBER | HEX | OCTAL | BINARY | "true" | "false" ;

Statements

Every statement ends in ;, including if and blocks. Blocks have no value: values leave a function only through ret.

Loops

loop i in a..b { ... } runs the block once for each value of the range, in order. Inside the block, i has the range of the loop:

loop i in 0..9 {
    // i : 0..9
};

The range is any expression, so an alias works too: loop i in u8 { ... }.

loop cond { ... } runs the block while cond holds. The parser tells both forms apart by looking past loop: ID in starts a range loop, anything else is a condition.

Bindings

FormMeaning
x :: ecompile-time constant (aliases, functions)
x := eruntime binding, range inferred
x : T = eruntime binding, range declared
x : T : ecompile-time constant, range declared

T is an expression evaluated at compile time, so a range or an alias both work: x : 0..255 = 3, x : u8 = 3. Bindings are immutable in the MVP.

Functions

A function is an expression; its name comes from the binding it is assigned to. If it declares a return range, every path must end in ret.

fn_expr and a parenthesized expression both start with (. The parser tells them apart by looking past it: () or ( ID : starts a function, anything else is grouping.

Expressions

The expr rule is ambiguous on purpose: the parser is a Pratt parser, and how operators group comes from this table, from lowest to highest precedence:

OperatorsAssociativity
||left
&&left
== != < > <= >=none
..none
+ -left
* / %left
- ! (prefix)right
call f(x)left

Non-associative operators do not chain: a < b < c and a..b..c are errors. Since ranges bind tighter than comparisons, x < 0..10 is x < (0..10), and since they bind looser than arithmetic, 0..n + 1 is 0..(n + 1).

In if and loop, the expression ends where the block starts: { cannot continue an expression.

Comparisons narrow ranges

Inside each branch of an if, the comparisons in the condition narrow the ranges of the values they test:

u8 :: 0..255;

classify :: (i: 0..1000) 0..2 {
    if i < 10 {
        ret 0;      // i : 0..9
    } else if i < 100 {
        ret 1;      // i : 10..99
    } else {
        ret 2;      // i : 100..1000
    };
};

Tokens

  • NUMBER: decimal integer, [0-9]+
  • HEX: 0x[0-9a-fA-F]+
  • OCTAL: 0o[0-7]+
  • BINARY: 0b[01]+
  • ID: [a-zA-Z_][a-zA-Z0-9_]*, except keywords

Keywords: if, else, loop, in, ret, true, false.

Reserved for later: break, continue, obj, enum, import.

Not in the MVP

Block values and if as an expression, runtime bounds (x in 0..9 else { … }), reassignment, arrays, open ranges (..9), unions of ranges (0..3 | 10..12), distinct, floats, strings and pointers.