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:
- Single-line comments start with the sequence //and stop at the end of the line.
- Multi-line comments start with a /*and stop with a paring sequence*/. They can be nested.
// 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