Id, type, and raw
As we already established, id
, type
, and raw
are primitive types. These types (and values of these types) cannot be compiled - you can only use them in evaluated code.
Just like with any other type, you can declare symbols of these types, reassign them with new values, and pass them as arguments to functions.
Id
id
represents an identifier. Identifier literals are of this type.
Normally, processing an identifier literal looks up what it refers to. To suppress this behaviour, you can escape the identifier (eg. \foo
). Then, you will get the value of type id
.
You can combine two id
values into a new one with +
. Unsigned, boolean, and type values can be cast into id
.
eval (sym (baseName \foo));
eval (sym (compositeName (+ baseName (cast id i32))));
To perform a lookup on an id
value, surround it in parenthesis.
import "std/io.orb";
fnc main () () {
sym (x 0) (y 1);
eval (sym (name \x));
std.println (name); # prints 0
= name \y;
std.println (name); # prints 1
};
Type
type
represents a type. You can use it wherever a type is required, eg. in sym
instructions.
fnc main () () {
eval (sym (ty i32));
sym x:ty;
sym a:(ty 4);
};
Raw
raw
is a resizable list that can contain almost anything that can be expressed in Orb. Most commonly, it represents unprocessed Orb code.
As we’ve learned by now, the code we write consists of raw
values. However, they quickly get processed into something else by the compiler. To suppress this behavour, you need to escape them.
# the escaped code will not be executed
# it represents a raw value
eval (sym (r \(std.println myVar)));
()
(or {}
) represents an empty raw
value, ie. a raw
with no elements. Escaping it is not required.
Values inside raw
retain their non-value node properties (being ref or non-ref, their attributes, etc).
It is very important to remember that, regardless of their elements, raw
values are considered non-owning and their elements will not be dropped! Bad usage of raw
values can result in automatic cleanup not happening, which may manifest as memory leaks or worse.
Just as hazardous is the fact that their careless usage may result in the same value being dropped multiple times. This code will compile, but will result in undefined behaviour:
import "base.orb";
import "std/One.orb";
fnc main () () {
# creates a raw containing a single owning non-ref value
sym (r::evaluated \( ,(std.makeOne i32) ));
# non-ref value is dropped, memory is freed
[] r 0;
# non-ref value is dropped again, resulting in double free
[] r 0;
};
Great caution must be exercised when using raw
values! Really, their main intended usage is to write macros.
std.One is a set of smart pointer types - pointers that automatically deallocate used memory. Read the standard library reference to find out more about this and other useful definitions.