Language Specification Overview
This language specification will contain information about the language itself. For example, keywords, syntax of the language, as well as the unique features Occult has over other languages.
About Occult
Occult began in 2023 as a C transpiler Occult Source -> C Code -> C Compiler. This allowed for rapid development, and a working programming language pretty much eliminating the need for a custom backend, leveraging the power of C.
In 2024, the transpiler had became too buggy, and very restrictive for Occult to grow as a language, as a result, the project shifted from the C tranpsiler to a complete compiler rewrite which is and won't be reliant on any 3rd party backends.
Current Status
- Handles somewhat complex data structures (structs, arrays, pointers, basic nesting/composition).
- Still very buggy overall, expect crashes, incomplete features, and breaking changes.
- Single-file programs only, the user may include other
*.occfiles.
Keywords
Occult has 30 keywords in use right now, 16 reserved words and 14 words for raw datatypes.
Reserved Words
if
elseif
else
loop
when
do
break
continue
while
for
true
false
return
elseif can also be written as else if and it will have the same behavior.Datatypes
i64
i32
i16
i8
u64
u32
u16
u8
f32
f64
array
string
bool
char
char and bool behave the same as i8, this will most likely change in the future as more type safety is implemented.Unique
shellcode
shellcode keyword allows for the user to directly write shellcode in a function definition.Misc
include
struct
These are self explanatory, custom data structures, and including other files.
Comments
Comments are the same as C.
// Single line
/*
Multi
line
comment
*/
Literals
Everything in this section will cover the types of literals Occult has.
Integral & Floating Point
There are integral and floating point literals.
Occult supports hexadecimal, binary, and octal, as well as the normal base 10 format for normal integers.
There is also support for scientific floating point literals.
Examples
// Binary
0b1010
// Hexadecimal
0xDEADBEEF
// Octal (Same notation as C++)
010 // 8
// Normal base 10
123
// Normal floating point number
3.141
// Scientific floating point
2e10
Strings & Characters
Occult has string and character literals as well.
Examples
"Hello, I'm a string literal!\n"
// Character literals
'H'
'i'
'\n'
Operators
For precedence, Occult uses a highly modified version of a shunting-yard algorithm.
| Precedence | Operator(s) | Type | Associativity | Placement |
|---|---|---|---|---|
| 2 | +x -x ~x !x |
Unary | Right | Pushed to stack |
| 2 | @x $x |
Unary ref | Right | Output / stack* |
| 3 | * / % |
Binary | Left | Stack (drained) |
| 4 | + - |
Binary | Left | Stack (drained) |
| 5 | << >> |
Bitwise | Left | Stack (drained) |
| 8 | > < >= <= |
Comparison | Left | Stack (drained) |
| 9 | == != |
Equality | Left | Stack (drained) |
| 10 | & |
Bitwise AND | Left | Stack (drained) |
| 11 | ^ |
Bitwise XOR | Left | Stack (drained) |
| 12 | | |
Bitwise OR | Left | Stack (drained) |
| 13 | && |
Logical AND | Left | Stack (drained) |
| 14 | || |
Logical OR | Left | Stack (drained) |
| 15 | = |
Assignment | Left** | Stack (drained) |
| 17 | ( ) |
Grouping | N/A | Stack / drain stop |
Reference and dereference @ and $ are special-cased among unary operators. @ (reference) is emitted directly to output rather than pushed to the stack. $ (dereference) is pushed to the stack with a count, allowing multiple consecutive dereferences e.g. $$x to be collapsed and resolved together after a closing parenthesis.
Assignment is left-associative in Occult, and chained assignment is not supported. Writing a = b = c is undefined behavior or a compile error. Assign values one at a time.
Unary !x is parsed as a prefix operator but cannot be used as a standalone condition. Write x == false instead of !x. This applies to any singular boolean expression, Occult requires explicit binary comparisons. You can still negate grouped expressions, e.g. !(x == y).
+= or -= (etc.) as of now.
There also isn't ++ or --, so if you want to get the same behavior i = i + 1 would be it.Control Flow, Functions, and Expressions
Occult has many types of expressions, and 2 different ways to declare functions.
Declaring a Function
Declaring a function in occult is fairly simple, the return type is optional, by default a functions return type will be i64 (64-bit integer).
Functions also implicitly have return 0 at the end if nothing is specified.
fn main() /*return type*/ { }
So, this main function is simply just returning 0 as an i64.
Alternatively, you can have a function which uses shellcode (machine code) instead of normal code, it functions similarly to inline assembly in other languages.
fn main() shellcode i64 { 0x48 0xC7 0xC0 0x0A 0x00 0x00 0x00 0xC3 }
/*
Shellcode:
mov rax, 10
ret
*/
This shellcode is just returning 10 in the main function.
Variables
As of right now, Occult only supports local variables, and there is no scope implemented in the compiler yet.
For example, here's a simple variable declaration then we return it in a function.
fn main() {
i64 v = 30;
return v;
}
This code will just return 30.
Pretty much variable declarations work like they would in any other C-like language.
Calling a Function
Calling functions is also the same as it would be in any other C-like language.
fn add(i64 x, i64 y) i64 {
return x + y;
}
fn main() {
i64 result = add(5, 5); // assigns 10 to result
return result; // returns 10
}
Alternatively, you can also return add if you want to get the same behavior.
Control Flow
There are 4 ways for control flow in Occult.
If Statements
This is a basic example of if statements and some conditional behavior.
Syntax
if /*condition*/ { /*body*/ }
else if /*condition*/ { /*body*/ }
else { /*body*/ }
elseif is valid as well.fn main() {
i64 to_compare = 1;
if to_compare == 1 {
return 1;
}
else {
return 0;
}
}
Loop
Just like Rust, there is a loop keyword, and it behaves exactly the same.
Syntax
loop { /*body*/ }
Example
fn main() {
i64 x = 0;
loop {
x = x + 1;
} // Infintely add x by 1
}
While
While loops behave as you'd expect.
Syntax
while /*condition*/ { /*body*/ }
Example
fn main() {
i64 i = 0;
while i < 5 {
i = i + 1;
} // Loop will run 5 times
}
For
For loops have a unique syntax, using the when keyword as well as do to make it easier for beginners to understand what's going on.
Syntax
for /*variable decl*/ when /*condition*/ do /*iterator */ { /*body*/ }
Example
fn main() {
for i64 = 0 when i < 10 do i = i + 1 {
// loop body
} // loop will run 10 times
}
Arrays
Arrays have a completely unique syntax in Occult, but behave similarly to other C-like languages.
Syntax
array[/*size*/] /*datatype*/ name = { 1, "String", 1.43, x, /*etc.*/ };
Example
fn main() {
array[5] i64 arr = {1, 2, 3, 4, 5};
i64 sum = 0;
for i64 i = 0 when i < 5 do i = i + 1 {
sum = sum + arr[i];
}
return sum; // returns 10 (sum of the array)
}
Structs
Structs have a familiar syntax, which isn't hard to learn.
Take a node datatype for a linkedlist which stores i64.
struct node_i64 {
node_i64* next;
i64 data;
}
The syntax is fairly straightforward.
Accessing members
Using our node datatype, we can create a new node and then access the members like this.
struct node_i64 {
node_i64* next;
i64 data;
}
fn main() {
node_i64 new_node;
new_node.next = 0; // doesn't point to a new node
new_node.data = 10; // current node holds the value of 10
return new_node.data; // return our node's value (10)
}
-> syntax like C, there is only .Memory
This is one of the more unique parts about Occult, it uses a more unique syntax for pointers, and doesn't inherently behave the same as other languages.
Occult has a unique syntax for referencing, and dereferencing.
@ is used for referencing / address of, similar to C/C++'s &.
$ is used for dereferencing, same to doing *ptr in C/C++.
Simple Example
fn main() {
i64 v = 10;
i64* p = @v; // assign the address of `v` to `p`
$p = 30; // value of `v` is now 30
return v; // returns 30
}
More Complex Example
Allocates 16 bytes, stores two integers at different offsets (0 and 8), dereferences both to sum them (300), and frees the memory.
fn main() {
i64 v = alloc(16);
$v = 100;
$(v + 8) = 200;
i64 total = $(v + 8) + $v; // 300
print_integer(total); // prints 300
print_string("\n");
del(v);
}
sizeof operator and doesn't automatically size to types like C does.Reference Parameters
Occult does have reference parameters similar to C++.
fn add(i64@ x_ref, i64 y_ref) {
$x_ref = $x_ref + y_ref; // you have to explicitly dereference the value to access it
}
fn main() {
i64 x = 10;
add(x, 30); // x is now 40 (x_ref has the address of x)
return x; // returns 40
}
Strings in Memory
There's a unique feature with strings, the size is stored statically in memory, so to write a strlen function we can literally just do this.
fn strlen_fast(string str) { // uses prefix for string already builtin
i64 len = $(str - 8);
return len;
}
The string length is stored 8 bytes before the string starts.
This is the end of the language specification for now, maybe check out the examples section to learn more!