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