NULLC Language reference
- Basics
- Basic types
- Variable alignment
- Type inference
- Expressions
- import expression
- typeof expression
- sizeof expression
- new expression
- break expression
- continue expression
- return expression
- typedef expression
- Statements
- if statement
- for statement
- while statement
- do...while statement
- switch statement
- Declarations
- User classes
- Variables
- Functions
- Local functions
- Member functions
- Function Literals
- Function overloading
- Operator overloading
- Arrays with implicit size
- Appendix
- Rules applied to value types in a binary operation
- Operator priority
- Implicit conversions
1. Basics
| Name | Size | Default alignment | Extra information |
| void | 0 bytes | no alignment | only allowed as a function return value (returns nothing) |
| char | 1 byte | no alignment | values: 0..255 |
| short | 2 bytes | no alignment | values: -32768..32767 |
| int | 4 bytes | 4 bytes | values: -2147483648..2147483647 |
| long | 8 bytes | no alignment | values: -9223372036854775808..9223372036854775807 |
| float | 4 bytes | 4 bytes | values: as per IEEE 754 |
| double | 8 bytes | 8 bytes | values: as per IEEE 754 |
Default type alignment can be changed using two statements:
noalign before type name will disable any default type alignment:
noalign type name;
align(bytes) before type name will force specified alignment:
align(8) type name;
Alignment must not exceed 16 bytes.
When defining a variable, type name can be replaced with a keyword "auto".
In this case, type will be inferred from r-value type.
Here are some examples:
auto i = 5;
auto n = &i;
int[10] arr;
auto copy = arr;
Alignment of automatic types works just like with explicitly specified types.
auto can be used as a function return type. If a function has different exit points returning different type, an error occurs.
auto can be used as a function argument type as long as this arguments has a default value.
2. Expressions
import expressions allows code to import functions, classes and variables from other files.
import expressions must be placed at the beginning of a file before any other expression or definition.
A file name without extension must be specified after import.
Also, folder name is accepted after import keyword, then a point '.' and another folder\file name expected.
Expression examples:
import a;
import d.e;
typeof(expression) allows to get type of an expression (expression will not be evaluated at run time).
typeof(4) is equal to specifying int.
typeof(4 * 0.2) is equal to specifying double.
sizeof(type) returns size of type.
sizeof(expression) returns size of the expression type (it is equal to "sizeof(typeof(expression))")
new expression allows to allocate memory from global heap.
There are two versions of this expression - one is used to allocate classes and the second one is used to allocate arrays.
Return type of "new type" is 'type ref'.
Return type of "new type[N]", where N is an expression that results in a number is 'type[]'.
break expression allows to end execution of a cycle.
break; exits current cycle.
break N; where N is number, known at compile time exits from N cycles beginning from where it is written.
break 1; is equal to break;
continue expression allows to skip to the next iteration of a cycle.
continue; skips to the end of current cycle.
continue N; where N is number, known at compile time exits from (N-1) cycles beginning from where it is written and skips to the next iteration of cycle it ends up.
continue 1; is equal to continue;
return expression allows to return a value or exit from a function, or to end execution of a program if placed in global scope.
return; exits function, returning void type (nothing).
return expr; exits function or ends program execution, returning result of "expr".
return that is placed globally accepts only basic build-un types and cannot return void.
When returning value from function, it is converted to functions return type if conversion is possible.
If function return type is auto, function return type is set to the type of "expr".
typedef expression allows to create one-word aliases to other types.
typedef doesn't create new type, it just allows its target type to have different names.
Examples:
typedef int[4] fourInts;
typedef int ref(int, int) drawCallback;
3. Statements
if(expr)
trueBody
else
falseBody
if statement evaluates result of expression "expr", and if result isn't equal to 0, evaluates expressions put in "trueBody". Otherwise, it evaluates expressions put in "falseBody".
"trueBody" and "falseBody" may consist of a single expression or a block of expressions.
else and "falseBody", may be omitted. In such case, if result of "expr" is zero, nothing is evaluated.
for(initexpr; condexpr; iterexpr)
body
for statement is a cycle that evaluates "initexpr", and executes "body" while "condexpr" evaluates to a non-zero value.
"iterexpr" is evaluated at the end of every cycle.
"body", "initexpr" and "iterexpr" may consist of a single expression or a block of expressions.
"condexpr" must be a single expression with result type of int\long\double.
while(condexpr)
body
while statement is a cycle that executes "body" while "condexpr" evaluates to a non-zero value.
"body" may consist of a single expression or a block of expressions.
"condexpr" must be a single expression with result type of int\long\double.
do
body
while(condexpr)
do statement is a cycle that executes "body" while "condexpr" evaluates to a non-zero value.
Difference from while statement is that do statement evaluates body at least once, since condition is placed after the body.
"body" may consist of a single expression or a block of expressions.
"condexpr" must be a single expression with result type of int\long\double.
switch(expr)
{
case A:
caseBody;
default:
defaultBody;
}
switch statement evaluates result of "expr" and compares result with case label values. If result matches case value, a jump to a matched case label is made and all expressions after it are evaluated.
If no case label value is equal to result, a jump to default label is made.
break; expression can be used inside switch statement.
default label can be omitted.
switch without case labels, or without any expressions at all is legal.
4. Declarations
class Name
{
type name, name, ...;
type function(type arg, type arg, ...){ }
...
}
Class consists of variables and functions in any order, but keep in mind, functions can only access member variables that are defined before them.
There is no default alignment by default. To specify alignment, put "noalign" of "align(bytes)" before "class" keyword.
Specifying "noalign" is superfluous. Alignment must not exceed 16 bytes.
Simple variable definition syntax is:
type name;
Arrays are defined like this:
type[N] name;
or
type[] name;
If array size is specified, it must be a number known at compile type.
Second declaration is described in section "Arrays with implicit size".
Pointers are defined by using "ref" keyword:
type ref name;
Pointers to functions are defined by using "ref" keyword, followed by a list of function argument types in parenthesis:
type ref(type, type) name;
More than one variable can be defined in one statement, by specifying variable names after a comma.
All variables defined in one statement have equal type.
Different type modificators can be mixed together to form types like, for example:
int[4][2] name;
int ref(int, int)[4] name;
A value can be assigned to a variable in place of its definition, by writing "= value" after its name.
If array element is assigned to a type, then it will be a default value of all array elements.
Global functions, nested (local) functions, member functions and function literals are supported.
Function definition starts with a function return type, followed by name and a list of arguments in parenthesis.
List of arguments starts with a type that is followed by one or more parameter names, separated by comma. After a comma, a different type can be selected as well.
Function body must be enclosed in { } block.
type name(type a, b, type c, d){ }
Function return type can be any type expression including auto.
Function argument type can be auto only if it has default argument value.
Function default argument value is specified by writing "= value" directly after argument name:
auto function(int a = 5, int b = 5.0){ }
After first argument with a default value, the following arguments must all have a default value.
Functions can be defined within other functions.
Pointers to such functions remain valid after parent function ends.
This enables to make closures in NULLC.
Functions can be defined not only inside class definitions, but also added to a class later.
The following syntax is used to define class member function outside class definition:
type type:function(){ }
Type before a column must be a direct type name or an alias defined with typedef expression.
By using aliases it is possible to add member functions to array, reference and function types.
Adding member function to array types with explicit size is pointless, because such variables are converted to arrays with implicit size before function call.
Functions can be written inside expressions, this could be used to define an anonymous function as a function argument, or to return function from function:
int generateFrom(int a)
{
return int gen(){ return a++; };
}
Function name can be omitted if function return type is set to auto:
auto a = auto(int b){ return -b; };
int b = a(5);
Functions can be overloaded. Better matching function will be selected during overloaded function call.
Operator overloading allows user to define operators that work with any type.
Following operators can be overloaded:
+ - * / % ** < <= << > >= >> == != & | ^ && || ^^ = += -= *= /= **= []
For operators = += -= *= /= **= and [], first argument should be a reference to a type, but this is not necessary.
Array with implicit type is defined like this:
type[] name;
Any array of the same type can be assigned to an array with implicit size.
int[7] arr1;
int[4] arr2;
int[] arr1a = arr1;
arr1a = arr2;
int[7][3] arr1;
int[4][3] arr2;
int[][3] arr1a = arr1;
arr1a = arr2;
type[] size is 8 bytes (it consists of pointer to an array and a size field).
To get array size, use its "size" field:
int count = arr1a.size;
5. Appendix
Rule A:
Operations are made on three types: double, long and int.
Because of this, different types are converted to these three types:
char -> int
short -> int
float -> double
Rule B:
If a binary operation is done on two different types, arguments are converted in a following fashion:
int * double -> double * double
long * double -> double * double
int * long -> long * long
This is done after applying "Rule A"
Rule C:
In variable modification expression (+=, -=, *=, /=, **=) binary operation is performed as described in "Rule B",
And result is converted to type of l-value.
int a = 2;
a *= 2.5;
| # | Evaluation | Operation |
| 1 | left-to-right | * (unary) |
| 2 | left-to-right | . [] |
| 3 | left-to-right | & (unary) + (unary) - (unary) ~ ! ++ (prefix) -- (prefix) |
| 4 | left-to-right | ++ (postfix) -- (postfix) |
| 5 | left-to-right | ** |
| 6 | left-to-right | / * % |
| 7 | left-to-right | + - |
| 8 | left-to-right | << >> |
| 9 | left-to-right | < <= > >= |
| 10 | left-to-right | == != |
| 11 | left-to-right | & |
| 12 | left-to-right | ^ |
| 13 | left-to-right | | |
| 14 | left-to-right | && |
| 15 | left-to-right | ^^ |
| 16 | left-to-right | || |
| 17 | left-to-right | ?: |
| 18 | right-to-left | = += -= *= /= **= |
Apart from implicit conversions from one basic type to the other, there are following conversions between complex types:
type[size] -> type[]
type ref -> auto ref
auto ref -> type ref
Characters are eclosed in single quotes ''.
No more that one character can be inside quotes (after processing eqcape sequences).
Strings are enclosed in doublequotes "".
Type of string is array of (length + 1) character.
Strings are 0-terminated.
Arrays can be defined by writing all array elements, separated by commas in a { } block.
Multidimensional array can be also defined by using this.
For arrays of complex type, all array elements must have the same type.
It is impossible at the moment, to define array of char, short and float types.
Escape sequences can be used inside character and string expressions.
Following escape sequences are supported:
| Sequence | Meaning |
| \n | CR (Carriage return) |
| \r | LF (Line feed) |
| \t | Tab |
| \0 | 0 |
| \' | single quote - ' |
| \" | double quote - " |
| \\ | backslash - \ |
Numbers can be written in binary, and must be followed by letter 'b':
101b == 5
Numbers can be written in base 8, and must start with number 0:
077 == 63
Numbers can be written in base 16, and must start with '0x':
0x80 == 128