Back to UNDERWARE
Link to the Compiler
N26 is a redesign of N19, and the seventh attempt to design and implement the Nectar programming language.
As a "better C", Nectar is designed to do low-level ergonomically, something that most self-proclaimed better Cs fail at. Ignorance is not excused; unless the programmer keeps to very high-level concepts only, he/she must still understand the intricacies of the target architecture.
A Nectar compilation unit is composed of separate modules, each in its own namespace. A module may define global functions or variables. By default, no definitions are exposed to the outside. To do so a definition must be "exported", which locks it to a given ABI. What is not exported however, has no guarantee of making it to the finished binary or looking the way it is written. Non-exported items should be seen as being in a wibbly-wobbly, high-energy state (later on these wobbles will be explained in detail).
The only standardized library Nectar has is fully compile-time, in the module N. Nectar does not expect any runtime environment such as POSIX or even libc, however compatibility should be trivial: to write a C-compatible main function, simply do:
export main: (i32 argc, u8[?]*[?]* argv) -> () @c {
return;
}
The main function must be exported to be visible to the C runtime. We specify the @c convention to use the system's C convention. In the Unix world, this will be the System V ABI. In Windows it is System V for IA-32, and x64 for AMD64.
When we get to low-level features such as the ABI, you will see a lot of @ symbols, which were chosen to prevent most words from being keywords.
In general, a Nectar function may have any number of inputs and outputs and need not be assigned an ABI, for example:
div: (i32 a, i32 b) -> (i32 q, u32 r) {
q = a / b;
r = a % b;
return;
}
An equivalent function body would be return a / b, a % b;.
Although Nectar is statically-typed, variable declarations implicitly use the expression's type. This usually works but sometimes we must be explicit using a cast:
foo = u32 5
We cast the 5 to u32, because otherwise Nectar doesn't know what to do with the 5.
If you wish to declare a variable without assigning a value to it, use the ? expression:
foo = u32 ?
Unlike most languages, strings are nothing more than compile-time byte arrays, so if we, for example, have a 4-char string, we can assign it to a u32:
foo = u32 '\x7FELF'
Nectar comments also differ, in that they are note statements:
note This is a comment;
note {
This is a block comment.
}
note foo {
This is a named block comment.
}
Nectar has only if and loop statements for control flow. While loops and for loops can be simulated using these two:
i = u32 0;
loop {
if i >= 10 {
break;
}
note DO SOMETHING;
i = i + 1;
}
Compilation begins with a main module specified by the programmer. A module may use other another with use:
use C;
Symbols defined in other modules may be used with ., for example C.errno.
All symbols are visible to user modules, and the export specifier concerns only outside the Nectar compilation unit.
N26 supports the following types:
These are of the form u8 or i16. Any bit size is supported.
An object elsewhere in memory can be referred to by these types.
Example: i32*, u8**, u8[?]*.
Any type may form an array: u8[5]. The length can be a constant, or ? for unknown. The latter having an unknown size, can only be used through a pointer.
Structure types in N26 are a combination of product types and C-style unions.
Most simply, they are defined by parentheses, with fields separated by commas: (u8, u16*). Bare parentheses () define an empty structure.
The above syntax only defines the semantics of the type but cannot control the memory layout. For that, use the record syntax.
record SockAddrIPv4 {
u16 family;
u16 port;
u8[4] address;
u32 zero0;
u32 zero1;
}
Exact field offsets may be specified:
record SockAddrIPv4 {
0: u16 family;
2: u16 port;
4: u8[4] address;
8: u32 zero0;
12: u32 zero1;
}
Unions are achieved by overlapping fields offsets.
A function type takes one structure as a parameter, and one structure as the output. Unlike most C-likes, Nectar can give the programmer more ergonomic multiple-value returns.
The div function from above has the type (i32 a, i32 b) -> (i32 q, u32 r).
The calling convention is part of the function type, so a function with a C calling convention would be suffixed: (i32 a, i32 b) -> (i32 q, u32 r) @c
Nectar does not have an explicit type for some constructs, and must be told what those values are. For example:
? must be immediately casted into any type.Additionally, structures are beholden to implicit casts:
(u8) to u8)u8 to (u8))0 can be casted into any structure type as a shortcut for zero-initialization.All symbols and records may be generic by using square brackets after the symbol name:
foo[T]: (T a, T b) -> (T c) @c {
c = a + b;
return;
}
Said square brackets insert "generic types" into the scope. A symbol without generic types is called specified and/or concrete.
A generic symbol or type is made concrete again with square brackets: foo[u32](1, 2). Alternatively, types may be specified in a key-value manner: foo[T = u32](1, 2).
As exported symbols must be concrete, they may not be generic.
Within the bounds of a compilation unit, the following wobbles are guaranteed:
x * 15 may actually be computed as x << 4 - x.Any of the above are disabled if they are to interfere with the specified ABI.
The following are considered too risque for a compiler to perform without consent of the programmer.
@bitpack boosts structure packing to the bit-level (for example two u3s and a u1 will all fit into one u8). Fields may still be reordered to minimize crossings of byte boundaries.@inline enforces function call inlining. When used in a function call, applies only to said call. When used in a function definition, all correponsing function calls are inlined.To achieve high portability, Nectar does not require a specific ABI. Each function can be locked to a given calling convention as long as it is formally specified.
There are many ways calling conventions can differ:
The above is too much complexity to handle in a oneliner.