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.

$ 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. */ */

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

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(a: i32, b: i32) -> i32 {
    return a + b
}

You can call a function with the () operator.

sum(1 , 3) // Returns 4

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

Bitwise

Name                   Syntax
----------------------------------
Logical AND            a bit_and b    
Logical OR             a bit_or  b
Left shift             a shl b
Right shift            a shr b

Assignment

Name                        Syntax
----------------------------------
Basic 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 can’t be used where an expression is expected, like in an array subscript.

a(i++) = 1        // Error

while a = next()  // Error

Array and slice

Arrays begin at 0.

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

#assert a(0) == 1
#assert a(2) == 3

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

You can slice an array to reference it.

array: [4]i32 = [0, 1, 2, 3]

slice := array(..)   // Slice the whole array. [0, 1, 2, 3]
slice := array(1..4) // Slice from (1) to (3). [1, 2, 3]
slice := array(2..)  // Slice from (2) to the rest of the array. [2, 3]
slice := array(..3)  // Slice from the start of the array to (2). [0, 1]
slice := array(2..2) // Empty slice. []

slice := array(1..)
slice(1) = 42 // Changes (2) of the array to 42. The array is now equal to [0, 1, 42, 3]

Indexing and slicing are bound checked by default.

TODO pointer slicing

Conversion

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

a: f32 = 1.0
b: i32 = 2

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

Address-of and dereference

TODO

Precedence table

Category           Operator         
---------------------------------
Postfix	           () . ^ @ as !
Unary              - not
Multiplicative     * / mod bit_and shl shr
Additive           + - bit_or
Comparison         == != >= < <=
Logical AND        and
Logical OR         or

Flow control

Condition

if a {

} else if b {

} else {

}

While

while foo() {

}

Do While

do {

} while foo()

For

for i := 1; i < 5; i++ {
}

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:

Rectangle struct {
  width: f32
  height: f32
}

Structures are initialized with the () operator, just like a regular function call. All the fields must be initialized at the same time to ensure than the structure is always in a valid state. Fields names must be specified in the same order of their declaration in the structure.

rectangle := Rectangle(width = 30, height = 50)

Structure’s members can be accessed with ., the dot operator. The operator also works through a pointer.

rectangle := Rectangle(width = 30, height = 50)
area := rectangle.width * rectangle.height

Associated Function

Associated functions are functions that live in the namespace of the structure. An instance of the structure is not needed to call them. They are declared in a impl block.

Vec2 struct {
  x: f32
  y: f32
}

Vec2 impl {
    zero fn() -> Vec2 {
        return Vec2(x = 0.0, y = 0.0)
    }
}

#start {
    position := Vec2.zero()
}

Methods

Methods are functions that are called on an instance of a structure. They are declared in a impl block with a self as a first argument. Inside the method, fields are always accessed through the self variable.

Vec2 struct {
    x: f32
    y: f32
}

Vec2 impl {
    magnitude fn(self) -> f32 {
        return #sqrtf32(self.x * self.x + self.y * self.y)
    }
}

#start {
    vec := Vec2(x = 10, y = 20)
    magnitude := vec.magnitude()
}

Enumeration

An enum declaration:

Fruit enum {
  Banana
  Apple
  Cherry
}

Enums are typed. An enum can be implicitly casted to an integer but the other way requires an explicit conversion with the as operator.

a: Fruit = Fruit.Banana // OK
b: Fruit = 1            // Error
c: Fruit = 1 as Fruit   // OK

d: i32   = Fruit.Cherry     // OK
e: i32   = Fruit.Apple + 3  // OK

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 {
    foo()
}

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

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

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

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 try to 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

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

printf fn(str: ^c_char, ...) -> c_int #extern
GetLastError fn() -> c_ulong #extern