Rawr overview

This document is intended for programmers who want a quick overview of Rawr and are already familiar with system programming languages like C.

Hello world

Create a file called hello.rawr containing the following:

#start {
    std.print("Rawr!\n")
}

You can run it directly with the rawr run command by specifying the path to the hello.rawr file.

$ rawr run hello.rawr
Rawr!

The rawr build command creates an executable that can be run later.

$ rawr build hello.rawr
$ ./hello.exe
Rawr!

Comments

There are two forms of comments:

// This is a line comment.

/* This is
   a multiline comment. */

/* /* Nested comments are allowed. */ */

Variable

The syntax for a variable declaration is name : type. It means we declare a variable named name of type type.

a: i32    // a i32
b: ^i32   // a pointer to a i32
c: [5]f32 // an array of five f32

We write types just like we read them, from left to right. A “pointer to a pointer to an array of five i32” is written ^^[5]i32.

If the type is omited, it will be inferred from the type of the expression.

a := 123   // a i32
b := 3.14  // a f32
c := false // a bool

Function

Function declaration syntax is name fn(arguments) -> return_type. The arrow and the return type can be omitted if the function returns nothing.

This function is named sum, accepts two i32 as parameters and returns an i32. Its type is fn(i32, i32) -> i32.

sum fn(left: i32, right: i32) -> i32 {
    return left + right
}

You can call a function with the () operator.

sum(1 , 3) // Returns 4

Type

Name   Description
-----------------------------------
u8     Unsigned 8-bit integer
u16    Unsigned 16-bit integer
u32    Unsigned 32-bit integer
u64    Unsigned 64-bit integer
i8     Signed 8-bit integer
i16    Signed 16-bit integer
i32    Signed 32-bit integer
i64    Signed 64-bit integer
f32    32-bit floating point number
f64    64-bit floating point number
bool   Boolean value

You can get the type of a variable with the .type field, who returns a Type.

a: i32
type : Type = a.type
#assert type == i32

Operators

Arithmetic

Name                   Syntax
-----------------------------
Sum                    a + b
Difference             a - b
Product                a * b
Quotient               a / b
Modulo                 a mod b

Comparison

Name                   Syntax
-----------------------------
Equal                  a == b
Not equal              a != b
Less                   a <  b
Less or equal          a <= b
Greater                a >  b  
Greater or equal       a <= b

Logical

Name                   Syntax
------------------------------
Logical and            a and b    
Logical or             a or  b
Logical negation       not a

Assignment

Name                        Syntax
----------------------------------
Assignment                  a = b
Addition assignment         a += b
Subtraction assignment      a -= b
Multiplication assignment   a *= b
Division assignment         a /= b
Increment                   a++
Decrement                   a--

Assignments are statements, not expression. They cannnot be used where an expression is expected, like in an array subscript.

a[i++] = 1          // Error
while a = next() {  // Error
}

Address-of and dereference

The operator ^ takes the address of a variable and returns a pointer. The operator @ dereferences a pointer.

a: i32       = 1
pointer: ^32 = a^ // Take the address of 'a'
pointer@     = 2  // Dereference 'pointer'

#assert a == 2
#assert a == pointer@

Optional

The optional type represents an optional value, there might be something or nothing in it. It is denoted with the ? symbol and can be applied to any types.

a: ?i32     // an optional i32
b: ?^f32    // an optional pointer to a f32
c: ?Vec3    // an optional Vec3 (a structure)

You can assign something to an optional variable or set it to nothing with None.

a : ?i32
a = 1         // There is something in the optional, the value 1
a = None      // There is nothing in the optional, it is empty

Since an optinal value may be empty you always have to explicity check it and unwrap its value if there is one.

TODO

You cannot assign None to a pointer, there is no NULL pointers like in C. You should use an optional pointer instead.

a : ^i32 = None    // Illegal
b : ?^i32 = None   // Legal

Array

Arrays begin at 1 and are bound checked by default.

a: [3]i8 = [1, 2, 3]

#assert a[0] = 1  // Error, arrays start at 1
#assert a[1] = 1
#assert a[2] = 2
#assert a[3] = 3

Arrays can have multiple dimensions.

b: [2][2] = [[1, 2], [3, 4]]
#assert b[2][1] = 3

You can access the length of an array with the .length field.

array : [20]i32
#assert array.length == 20

Slice

Arrays or only a part of them can be sliced. Slicing is bound checked by default.

array: [4]i32 = [1, 3, 4, 5]

slice1: []i32 = array[2..3] // Slice from the second element to the third element included.	
slice2: []i32 = array[..2]  // Slice from the first element to the second element included.
slice3: []i32 = array[1..]  // Slice from the first element to the last element included.
slice4: []i32 = array[..]   // Slice the whole array.

slice1[1] = 42 // Changes the second element of the array to 42. The array is now equal to [1, 42, 3, 4]

You can access the length of a slice with the .length field.

array : [20]i32
slice : []i32 = array[5..10]
#assert slice.length == 5

Conversion

There is no implicit conversion between unsigned and signed integer, between integer and float or between integer and boolean. Theses operations need to be done explicitly with the cast operator as.

a: f32 = 1.0
b: i32 = 2

a + b          // Error, mismatch type
a + b as f32   // OK

Condition

The control flow of the program can be manipuled with the if and else statements.

if foo {
    std.print("foo is true")
} else if bar {
    std.print("foo is false and bar is true")
} else {
    std.print("Neither foo or bar are true")
}

The parenthesis around the condition can be ommited but the brace is mandatory.

Loops

While

while foo() {
    std.print("Still looping\n")
}

Do While

do {
    std.print("Still looping\n")
} while foo()

For

for i := 1; i <= 5; i++ {
    std.print("Still looping\n")
}

Short For

for has a short form to loop on every elements of an array or a slice. Two variables will be implicity declared in the scope: it who contain the current element and it_index who is the current index of the loop.

array := [10, 20, 30]
for array {
    // it and it_index are accessible in this scope.
}

Constant

Constants are values that will never change so they can be inlined at compile time.

a const = 1
b const = 2.0
c const = "hello"

Structure

Structure declarations look like that:

Vec3 struct {
  x: f32
  y: f32
  z: f32
}

Structure’s members can be acceded with the dot operator .. The operator works on structure and pointer to structures.

v: Vec3 = {1.0, 2.0, 3.0}
#assert ptr.x == 1.0
ptr : ^Vec3 = v^ // We take a pointer of v
#assert ptr.y == 2.0

Enumeration

An enum declaration:

Fruit enum {
  Banana
  Apple
  Cherry
}

Enumeration variants are typed and have their own namespace.

a: Fruit = Fruit.Banana

Alias

Aliases create an additional name for a type.

GLint alias_of i32

Module

Scope

A module in Rawr is just a directory. Every .rawr files in the same directory are implicitly part of the same namespace and can reference each other without importing anything.

For exemple, with a hierarchy like this:

my_module
├── a.rawr
└── b.rawr

a.rawr has access to b.rawr’s declarations…

// my_module/a.rawr
#start {
    b()
}

a fn() {
    std.print("This is a.rawr\n")
}

…and b.rawr to a.rawr’s declarations

// my_module/b.rawr
b fn() {
    a()
    std.print("This is b.rawr\n")
}

Compiling

To compile a module, its path need to be provided to the compiler.

$ rawr run my_module
This is a.rawr
This is b.rawr

If no path if provided, the compiler will compile the current working directory.

$ cd my_module
$ rawr run
This is a.rawr
This is b.rawr

Foreign interface

C types are available since their sizes and signedness can vary depending of the architecture and operating system.

Name      C Equivalent
-----------------------------------
c_char    char
c_uchar   unsigned char
c_schar   signed char
c_short   short
c_ushort  unsigned short
c_int     int
c_uint    unsigned int
c_long    long
c_ulong   unsigned long
f32       float 
f64       double
cstring   char*

To call a C function, its prototype must first be declared with the #extern keyword.

puts fn(str: cstring) -> c_int #extern

Precedence table

Category           Operator         
---------------------------------
Postfix	           () [] [..] . ^ @ as     
Unary              - not
Multiplicative     / *
Additive           + -
Comparison         == != >= < <=
Logical and        and
Logical or         or