Red/System is a dialect (DSL) of the Red programming language. Its purpose is to provide:
low-level system programming capabilities
a tool to build Red's runtime low-level library
a tool to link code and produce executables
Red/System can be seen as a C-level language with memory pointer support and a very basic and limited set of datatypes.
Implementation note: It is currently provided with a complete tool-chain generating executables from source files. This is a temporary state as Red/System will live inside Red, so will be embedded in Red scripts.
2. Syntax
The syntax is almost the same as the one used by REBOL language, as the lexer (LOAD) is currently provided by REBOL during the bootstrapping phase. The REBOL syntax does not have a formal specification nor an exhaustive documentation, just a superficial description, but it is enough to work with. See:
A complete syntax specification for both Red and Red/System will be provided during the implementation of the Red language layer.
For now, Red/System uses 8-bit character encoding (ASCII). Once proper Unicode support will be provided by the Red language layer, Red/System will switch to UTF-8 source encoding.
Here are a few practical aspects of the language syntax:
2.1 Delimiters
String delimiters: double quotes
"this is a string"
{This is
a multiline
string.
}
Block of code delimiters: square brackets
if a > 0 [print "TRUE"]
either a > 0 [print "TRUE"][print "FALSE"]
while [a > 0][print "loop" a: a - 1]
Path separator: slash (denotes a hierarchical relation)
Red/System (and Red) inherits the free-form syntax of the REBOL language. The only syntactic constraints are putting a whitespace (in the large sense) between tokens and correctly pairing delimiters.
Examples of valid code:
while [a > 0][print "loop" a: a - 1]
while [a > 0]
[print "loop" a: a - 1]
while [
a > 0
][
print "loop"
a: a - 1
]
Code guidelines are not yet available. They will follow standard REBOL practices.
2.3 Comments
Inline comment:
;this is a commented line
print "hello world" ; this is another comment
Multiline comment:
comment {
This is a
multiline
comment
}
Usage rules:
Inline comments are allowed anywhere in the source code
Multi-line comments are allowed anywhere in the source code, except in expressions. Example:
a: 1 + comment {5} 4 ; this will produce a compilation error
3. Variables
Variables are labels used to represent a memory location. The labels (called identifiers from now) are formed by sequences of printable characters without any blank (space, newlines or tabulation). Printable characters are defined as any one-byte character in the 20h-7Eh range that can be printed out in system's console excepting the following ones (used as delimiters or reserved for some datatypes literals):
[ ] { } " ( ) / \ @ # $ % ^ , : ; < >
There is a restriction on the first character, the following characters are forbidden in the first position, but allowed at other positions:
0 1 2 3 4 5 6 7 8 9 '
Also there is a another restriction to avoid letting the compiler mistake an hex integer for a variable name. Variable names starting with A-F letters consisting of 2, 4 or 8 A-F and 0-9 characters and ending with h are not allowed.
All identifiers (variables and function names) are case-insensitive.
3.1 Setting a value
Variables can hold any value of the available datatypes. This can be the real value (like integer! or pointer!) or a reference to the real value as is the case for struct! or c-string!). To assign a value to a variable, use a colon character at the end of the variable identifier.
foo: 123
bar: "hello"
3.2 Getting a value
Just use the variable name without any decoration to get its value or to pass it as a function's argument.
bar: "hello"
print bar
will output:
hello
3.3 Typing
Variables do have a type. Variables do not need to be declared before being used, but they require to be initialized anyway. Function local variables require to be declared, but the type specification part can be skipped if the variable is properly initialized. For example:
foo: 123
bar: "hello"
size: length? bar
id: GetProcessID ;-- 'GetProcessID would return an integer!
compute: func [
a [integer!]
return: [integer!]
/local c ;-- 'c is declared without a type
][
c: 1 ;-- inferred type is integer!
a + c
]
are valid variable usages.
Initializations have to be done at root level of code, attempt to initialize from a block of code will result in a compilation error.
foo: 123 ;-- valid initialization
if a < b [foo: 123] ;-- invalid initialization
Note: A function body block is considered a root level.
decimal form : 1234
decimal negative form : -1234
hexadecimal form : 04D2h
The integer! datatype represents natural and negative natural numbers. The memory size of an integer is 32 bits, so the range of supported numbers is :
-2147483648 to 2147483647
Hexadecimal format
Hexadecimal integer representation is mostly used to represent memory addresses or binary data for bitwise operations. As for character, all hexadecimal literals found in sources are converted to their integer decimal value during lexical analysis. Allowed range is:
00000000h to FFFFFFFFh
Hex letters have to be written in uppercase and only 2, 4 and 8 characters are allowed (prefixing with leading zeros is allowed).
4.2 Byte!
The byte! datatype's purpose is to represent unsigned integers in the 0-255 range.
Casting is allowed to some extent (see section "4.9 Type Casting").
foo: as integer! #"a" ;-- foo holds 97
bar: as byte! foo ;-- bar holds #"a"
Note: trying to cast an integer value greater than 255 to a byte! will result in a data loss or data corruption. (The handling of this case might be changed in future revisions)
4.3 Float!
The float! datatype represents an IEEE-754 double precision floating point number. Float! memory size is 64-bit.
A maximum of 16 digits are accepted for literal float! values. If more are specified, they will be dropped.
For more information on double precision floating point numbers, see Wikipedia.
4.3.2 Casting
It is allowed to apply a type casting transformation on a float! value to convert it to a float32! value.
Examples:
pi: 3.14159265358979
pi-32: as float32! pi
print pi-32
will output
3.1415927
4.3.3 Math operations
All Red/System math operators (+, -, *, /, //, %) are supported. The default rounding method on results is "rounding to nearest". Both operands need to be of float! types (no implicit casting).
The modulo (//) and remainder (%) operators are not defined for float! values (use the C `fmod()` function if such feature is needed).
4.4 Float32!
The float32! datatype represents an IEEE-754 single precision floating point number. Float32! memory size is 32-bit.
4.4.1 Syntax
There is no literal form for float32! datatype values. To load a float32! constant, the method consists of providing a float! literal and prefixing it with a type casting to float32!.
Example:
pi32: as float32! 3.1415927
For more information on single precision floating point numbers, see Wikipedia.
4.4.2 Casting
It is allowed to apply a type casting transformation on a float32! value to convert it to a float! value. Type casting to integer! is also allowed mainly for bits manipulation purpose (this is not a float32! number to integer! number conversion).
Examples:
s: as float32! 3.1415927
print [
as float! s lf
as integer! s
]
will output
3.14159270000000
1518260631
4.4.3 Math operations
All Red/System math operators (+, -, *, /, //, %) are supported. The default rounding method on results is "rounding to nearest". Both operands need to be of float32! types (no implicit casting).
The modulo (//) and remainder (%) operators give the same results when used on float32! values.
4.5 Logic!
The logic! datatype represents boolean values: TRUE and FALSE. Logic variables are initialized using a literal logic value or the result of a conditional expression.
As a first class datatype, you can pass logic values and variables as function arguments or use them as function's return value.
4.5.1 Literal format
true
false
Using a literal to initialize a logic variable:
foo: true
either foo [print "true"][print "false"]
will output:
true
Using a conditional expression to initialize a logic variable:
bar: 2 > 5
either bar [print "true"][print "false"]
will output:
false
4.6 C-string!
A c-string! value is a sequence of non-null bytes terminated by a null byte. A c-string variable holds the memory address of the first byte of the c-string, so it can be viewed as an implicit pointer to a variable of the byte! datatype. A c-string having a null character as first byte is an empty c-string.
4.6.1 Syntax
Literal c-strings are defined using double quotes delimiters or a pair of matching curly braces:
foo: "I am a c-string"
bar: {I am
a multiline
c-string
}
4.6.2 C-string length
It is possible to retrieve the number of bytes (excluding the null end marker) in a c-string at runtime using the LENGTH? function:
a: length? "Hello" ;-- here length? will return 5
4.6.3 C-string arithmetic
It is possible to apply some simple math operations on c-string variables like additions and subtractions. C-string address would be increased or decreased by the integer argument.
Syntax
<c-string> + <n>
<c-string> - <n>
<c-string> : c-string variable
<n> : expression resulting in an integer! value
Example:
s: "hello" ;-- let's suppose s points to address 40000000h
s: s + 1 ;-- now s points to address 40000001h
print s ;-- "ello" would be printed
s: s + 1 ;-- now s points to address 40000002h
print s ;-- "llo" would be printed
4.6.4 Accessing bytes
It is possible to access individual bytes in a c-string using path notation:
<c-string>/integer! ;-- literal integer index provided
<c-string>/<index> ;-- index provided by a variable
<c-string> : a c-string variable
<index> : an integer variable
The returned value will be of the type byte!.
Examples:
foo: "I am a c-string"
foo/1 => #"I" ;-- byte! value (73)
foo/2 => #" " ;-- byte! value (32)
...
foo/15 => #"g" ;-- byte! value (103)
foo/16 => #"^(00)" ;-- byte! value (0) (end marker)
Example of a variable used as index:
c: 4
foo/c => #"m" ;-- byte! value (109)
A simple way to traverse a c-string would be:
foo: "I am a c-string"
bar: foo
until [
print bar/1
bar: bar + 1
bar/1 = null-byte
]
will output:
I am a c-string
Similarly, it is also possible to modify the c-string's bytes using path notation with an ending colon:
<c-string>/integer!: <value> ;-- literal integer index provided
<c-string>/<index>: <value> ;-- index provided by a variable
<c-string> : a c-string variable
<index> : an integer variable
<value> : a byte! value
For example:
foo: "I am a c-string"
foo/3: #"-"
c: 4
foo/c: #"-"
print foo
will output
I -- a c-string
4.7 Struct!
Struct! datatype is roughly equivalent to C struct type. It is a record of one or several values, each value having its own datatype. A struct variable holds the memory address of a struct value.
Implementation notes:
Struct! values members are padded in memory in order to preserve optimal alignment for each target (for example, it is aligned to 4 bytes for IA32 target). Size? function will return the size of the struct! value in memory including the padding bytes.
Structs declared as values do not hold any pointer as their content is inlined, so their memory location is fixed and cannot be changed by the user.
4.7.1 Declaration
Declaring a struct! value is achieved by using the DECLARE STRUCT! sequence followed by a specification block. That block defines struct! value members using pairs of name and datatype definition.
The returned value of DECLARE STRUCT! is the memory address of the newly created struct! value on the heap. Struct values declared as local variables in functions have their memory slots pre-reserved.
Note: Struct members are all initialized to zero when a new literal struct! is declared.
4.7.2 Usage
s: declare struct! [
a [integer!]
b [c-string!]
c [struct! [d [integer!] e [float!]]]
]
In this example, the struct value has 3 members: a, b, c, each with a different datatype. The c member is a struct! value pointer, it needs to be assigned to a struct value to be used. So a correct initialization for the c member would be:
s/c: declare struct! [d [integer!]]
It is possible to nest struct values (and not just pointers to struct values) by adding the value keyword at the tail of a nested struct type specification:
s2: declare struct! [
a [integer!]
b [c-string!]
c [struct! [d [integer!] e [float!]] value]
]
In this case, the nested struct c storage space is reserved when allocating the space for the parent struct s2. The size of struct s2 is 20 bytes, while the size of s is 12 bytes.
Struct pointers and struct values can be arbitrarily nested and mixed together. But the assignment rules differ whether it is a struct pointer or a struct value:
A struct! variable addressed by pointer can be changed to point to a different location, even if it was referring to a memory location reserved using `declare`.
A struct! variable addressed by value (contains `value` keyword) copies data on assignment from the argument location.
4.7.3 Accessing members
Member access is achieved using path notation. Syntax is:
<struct>/<member> ;-- read access
<struct>/<member>: <value> ;-- write access
<struct> : a valid struct variable
<member> : a valid member identifier in <struct>
<value> : a value of same datatype as <member>
From last example, that would give:
foo: s/a ;-- reading member 'a in struct 's
s/a: 123 ;-- writing 123 in member 'a in struct 's
s/b: "hello"
bar: s/c/d ;-- deep read/write access is also possible
Note: Accessing a function! pointer member will result in dereferencing the pointer.
It is also possible to acquire a pointer on a struct member using the get-path notation:
:<struct>/<member>
<struct> : a valid struct variable
<member> : a valid member identifier in <struct>
The returned type is always `pointer! [integer!]` in such case. It can be freely type-casted to other pointer types.
p: :s/a
p/value ;-- returns the value of s/a
p/value: 456 ;-- sets a new value in s/a
4.7.4 Struct arithmetic
It is possible to apply some simple math operations on struct variables, like additions and subtractions. Struct address would be increased or decreased by the size of the pointed struct value multiplied by the integer argument.
p: declare struct! [ ;-- let suppose p = 40000000h
a [integer!]
b [pointer! [integer!]]
] ;-- struct memory size would be 8 bytes
p: p + 1 ;-- now p = 40000008h
Note: The struct value size is target and alignment dependent. In the above example, it is supposed to run on a 32-bit system with a struct alignment to 4 bytes.
4.7.5 Aliases
Struct! values definitions tend to be quite long, so in some cases, when struct! definitions are required to be inserted in other struct! definitions or in functions specification block, it is possible to use an alias name to reference a struct! definition through the source code. It also allows the self-referencing case to be quite simply solved.
Notes:
An alias is not a value, it doesn't take any space in memory, it can be seen as a virtual datatype. So, by convention, alias names should end with an exclamation mark, in order to distinguish them more easily from variables in the source code.
Aliased names live in their own namespace, so they cannot interfere with variable names.
Aliasing syntax:
<name>: alias struct! [
<member> [<datatype>]
...
]
<name> : the name to use as alias
<member> : a valid identifier
<datatype> : integer! | byte! | pointer! [integer! | byte! | float! | float32! | pointer!] | logic! |
float! | float32! | c-string! | struct! [<members>] |
struct! [<members>] value | function! [<spec>]
Struct value declaration using an aliased definition:
<variable>: declare <alias>
<variable> : a struct variable
<alias> : a previously declared alias name
Struct usage example:
book!: alias struct! [ ;-- defines a new aliased type
title [c-string!]
author [c-string!]
year [integer!]
last-book [book!] ;-- self-referenced definition
]
gift: declare struct! [
first [book!] ;-- reference to a book! struct
second [book!] ;-- reference to a book! struct
]
gift/first: declare book! ;-- book! struct allocation
gift/first/title: "Contact"
gift/first/author: "Carl Sagan"
gift/first/year: 1985
gift2: declare struct! [
first [book! value] ;-- inlined book! struct value
second [book! value] ;-- inlined book! struct value
]
4.8 Pointer!
The purpose of the pointer datatype is to hold the memory address of another value. A pointer value is defined by the pointed value address and datatype. As c-string! and struct! are already implicit pointers, the only pointed datatypes allowed are integer!, float!, float32! and byte! (logic! pointer is not needed).
Byte! pointers are equivalent to c-string! references, the difference lies only in the interpretation of the pointed values. Byte! pointer is meant to point to a stream of byte without a specified bound, while c-string! references an array of bytes terminated by a null byte.
Implementation note: The memory size of a pointer is 4 bytes on 32-bit systems (and 8 bytes on 64-bit systems).
4.8.1 Literal format
New pointers value can be created using the following syntax:
foo: declare pointer! [integer!] ;-- equivalent to C's: int *foo;
bar: declare pointer! [byte!] ;-- equivalent to C's: char *bar;
baz: declare pointer! [float!] ;-- equivalent to C's: double *baz;
qux: declare pointer! [pointer!] ;-- equivalent to C's: void **qux;
4.8.2 Declaration
Pointer declaration is only required for arguments in functions' specification block. For local pointer variables, the datatype declaration can be omitted and left to the inferencer to guess. (See "Type inference" section)
Same with local variables declaration examples (with C equivalents):
func [/local p [pointer! [integer!]] ;-- int *p;
func [/local p [pointer! [byte!]] ;-- char *p;
func [/local p [pointer! [float!]] ;-- double *p;
Example of inferred pointer variable type:
foo: func [
a [struct! [count [integer!]]]
/local
p [pointer! [integer!]] ;-- explicit declaration
][
foobar p ;-- foobar modifies p
a/count: p/value
]
bar: func [
a [struct! [count [integer!]]]
/local p ;-- p datatype inferred
][
p: declare pointer! [integer!] ;-- p initialized (implicit declaration)
foobar p
a/count: p/value
]
bar2: func [
a [struct! [count [integer!]]]
/local p ;-- p datatype inferred
][
p: GetPointer a ;-- datatype is guessed from return value
foobar p
a/count: p/value
]
4.8.3 Dereferencing
Dereferencing a pointer is the operation allowing access to the pointed value. In Red/System, it is achieved by adding a /value refinement to the pointer variable (called more generally "path notation"):
<pointer>/value ;-- read access
<pointer>/value: <value> ;-- write access
<pointer> : pointer variable
<value> : a value of same type as in pointer's definition
Usage example
p: declare pointer! [integer!] ;-- declare a pointer on an integer
bar: declare pointer! [integer!] ;-- declare another pointer on an integer
p: as [pointer! [integer!]] 40000000h ;-- type cast an integer! to a pointer!
p/value: 1234 ;-- write 1234 at address 40000000h
foo: p/value ;-- read pointed value back
bar: p ;-- assign pointer address to 'bar
Note: Remember that a pointed value is undefined (can contain an arbitrary value) until you define it explicitly
4.8.4 Pointer arithmetic
It is possible to apply some simple math operations on pointers, like additions and subtractions (as in C). A pointer address will be increased or decreased by the memory size of the pointed value multiplied by the amount to respectively add or subtract.
p: declare pointer! [integer!] ;-- pointed value memory size is 4 bytes
p: as [pointer! [integer!]] 40000000h
p: p + 1 ;-- p points now to 40000004h
p: p + 1 ;-- p points now to 40000008h
q: declare pointer! [byte!] ;-- pointed value memory size is 1 byte
q: as [pointer! [byte!]] 40000000h
q: q + 1 ;-- p points now to 40000001h
q: q + 1 ;-- p points now to 40000002h
Also, additions and subtractions between pointer addresses are allowed. The result value type is, as usual, the type of left operand.
offset: p - q ;-- would store 6 in offset
;-- type of offset is pointer! [integer!]
4.8.5 Pointer path notation
It is possible to use path notation to simulate an array with indexed access. Both reading and writing are possible. Indexes are one-based.
Syntax
<pointer>/<integer> ;-- literal integer index provided
<pointer>/<index> ;-- index provided by a variable
<pointer> : a pointer variable
<integer> : an integer literal value
<index> : an integer variable
Examples:
p: declare pointer! [integer!]
p: as [pointer! [integer!]] 40000000h
a: p/1 ;-- reads an integer! from 40000000h
p/2: a ;-- writes the integer! to 40000004h
Integer variable can also be used as index:
p: declare pointer! [integer!]
p: as [pointer! [integer!]] 40000000h
c: 2
p/c: 1234 ;-- writes 1234 (4 bytes) at 40000004h
Note: Pointer's /value notation is strictly equivalent to /1. The /value notation can be considered as syntactic sugar.
4.8.6 Literal arrays
A pointer can also point to a one-dimensional array of values literally specified.
Syntax
<variable>: [<items>]
<variable> : a pointer of same type as the array items.
<items> : is a non-empty list of integer!, byte!, float!, logic!, c-string! literal values.
The array is statically allocated and can be accessed using pointer path notation or pointer arithmetic. Mixing the different allowed types in the same array is permitted (enumerations can also be used). In such case, the size of each array's slot is either 32-bit, or 64-bit if a float64! value is present.
The size of a literal array can be retrieved using size? keyword.
In the second example, this literal array is not equivalent to its c-string! counterpart "hello", as the literal array does not add a NUL character at tail of the sequence.
In the last example, pay attention to the different pointer indexes used, depending on the size (32-bit or 64-bit) of referenced value.
4.8.7 Binary arrays
Binary arrays are a type of literal array made out of bytes only.
Syntax
<variable>: #{...hex bytes...}
<variable> : a pointer of [pointer! [byte!] type.
The array is statically allocated and can be accessed using pointer path notation or pointer arithmetic. The size in bytes of a binary array can be retrieved using size? keyword.
Note: whitespaces and comments are accepted inside binary arrays.
4.8.8 Null value
A special null value is available to use for pointer! and other pointer-like (pass-by-reference) types (struct!, c-string!) and pseudo-type function!. Null does not have a specific type, but can be used to replace any other pointer-like value. So, null cannot be used as initializing value for a global variable or a local variable that does not have an explicit type specification.
Null is a first class value, so it can be assigned to a variable, passed as argument to a function or returned by a function.
Note: It is not possible to explicitly cast null to a given type, only implicit type casting automatically done by the compiler is allowed.
Examples:
a: declare pointer! [integer!]
a: null ;-- valid assignment, 'a type is defined
b: null ;-- invalid assignment, type of b cannot
;-- be deduced by the compiler
foo: func [s [c-string!] return: [c-string!]][
if s = null [
print "error"
return null
]
return uppercase s
]
b: foo "test" ;-- will set b to "TEST"
b: foo null ;-- will print "error" and set b to null
4.8.9 C void pointer
There is no specific support in Red/System for C-like void pointers. The official way is to use a pointer! [byte!] type to represent C void* pointers.
For pointers to c-string! or struct! variables, a pointer variable can be used then dereferenced and converted using type casting to the target type.
Example:
p-buffer!: alias struct! [buffer [c-string!]]
set-hello: function [
s [p-buffer!]
][
s/buffer: "hello"
s ;-- equivalent to C's char **
]
foo: func [
/local
c [p-buffer!]
][
c: declare p-buffer!
set-hello c
print c/buffer
]
foo ;-- call foo function
would print
hello
4.8.10 Variable pointer
It is possible to get a pointer on an existing variable for the following datatypes:
integer!
byte!
float!
float32!
pointer!
Syntax
:<variable>
<variable> : a variable name of allowed type.
This expression will return a pointer value which type depends on the variable type, so:
Casting is achieved using the `as` keyword followed by the target type and the value to cast.
Type casting is possible between value of compatible types. Compatible types are defined in the following type casting reference matrix. A run-time type conversion might be generated for some types combinations.
Syntax
as <new-type> <expr>
as [<new-type>] <expr> ;-- alternative syntax
as <new-type> keep <expr>
<expr> : any valid R/S expression.
<new-type> : integer! | byte! | logic! | c-string! | float! | float32! |
pointer! [integer!] | struct! [<members>] |
<alias-name>
Notes:
The optional keep keyword ensures that all bits are preserved, and that only the type is changed.
Multiple nested type castings are not allowed and will raise a compilation error.
Trying to assign a value to a variable of different type without a proper type casting, will result in a compilation error.
Example:
foo: 0 ;-- foo is an integer variable
bar: declare pointer! [integer!] ;-- bar is a pointer variable
foo: as integer! bar ;-- type casting
bar: as pointer! [integer!] foo
Type casting reference matrix
Keep in mind that pointer!, c-string!, struct! and function! are passed by reference, so the casting below for these datatypes is applied on their memory address value.
source>>
byte!
integer!
logic!
c-string!
pointer!
struct!
float!
float32!
function!
byte!
WARNING
as byte! ¹
true»#"^(01)" false»#"^(00)"
ERROR
ERROR
ERROR
ERROR
ERROR
ERROR
integer!
as integer!
WARNING
true»1 false»0
as integer!
as integer!
as integer!
to integer!³
to integer!³
as integer!
logic!
#"^(00)"»false else»true
0»false else»true
WARNING
null»false else»true
null»false else»true
null»false else»true
ERROR
ERROR
ERROR
c-string!
ERROR
as c-string!
ERROR
WARNING
as c-string!
as c-string!
ERROR
ERROR
ERROR
pointer!
ERROR
as pointer!
ERROR
as pointer!
WARNING
as pointer!
ERROR
ERROR
as pointer!
struct!
ERROR
as struct!
ERROR
as struct!
as struct!
WARNING
ERROR
ERROR
ERROR
float!
ERROR
to float!
ERROR
ERROR
ERROR
ERROR
WARNING
as float! ²
ERROR
float32!
ERROR
to float32!
ERROR
ERROR
ERROR
ERROR
as float32! ²
WARNING
ERROR
function!
ERROR
as function!
ERROR
as function!
as function!
as function!
ERROR
ERROR
as function!
¹ Casting allowed, but integer values higher than 255 will be truncated, so beware!
² A data modification can occur.
³ Rounded towards zero.
4.10 Size?
Syntax
size? <type>
size? "<string>"
<type> : an valid type name.
"<string>" : a c-string literal value.
Size? returns the memory storage size in bytes required by a value of given type. When passed a c-string literal value, it will return the number of bytes in the c-string, including the ending null byte.
Example
size? byte! ;-- will return 1
size? integer! ;-- will return 4
s!: alias struct! [
a [integer!]
b [float32!]
]
size? s! ;-- will return 8
5. Expressions
Expressions are the basic building blocks of a Red/System program. They are composed of:
variables
literal values
function calls
operator calls
sub-expression in parentheses
5.1 Formal grammar rules
The grammar rules are specified in BNF format, except when using ... to mark a definition in native language.
<literal> ::= ... any valid Red/System literal value ...
<variable> ::= ... any valid Red/System variable name ...
<logic-call> ::= ... function call that returns a value of logic! type ...
<func-call> ::= ... function call that returns a value ...
<statement> ::= ... statement or function without return value...
<logic-literal> ::= "true" | "false"
<math-op> ::= "+" | "-" | "*" | "/" | "//" | "%"
<bitwise-op> ::= "and" | "or" | "xor"
<comparison-op> ::= "=" | "<>" | "<" | "<=" | ">=" | ">"
<op> ::= <math-op> | <bitwise-op>
<cond-expr> ::= <value> <comparison-op> <expression>
<condition> ::= <logic-literal> | <logic-call> | <cond-expr>
<all> ::= "ALL" "[" <condition>+ "]"
<any> ::= "ANY" "[" <condition>+ "]"
<short-circuit> ::= <all> | <any>
<paren> ::= "(" <expression> ")"
<value> ::= <variable> | <literal> | <paren> | "null"
<infix> ::= <value> <op> <expression>
<prefix> ::= <func-call> <expression>* | <statement> <expression>*
<call> ::= <prefix>+ | <infix> | <cond-expr>
<expression> ::= <call> | <value> | <short-circuit>
An expression can be used standalone, after an assignment or as argument to some statements (RETURN, IF or EITHER used as statement).
Examples
a: 123
foo a + 1
0 < foo a + 1
any [(0 < foo a + 1) a > 0]
if all [(0 < foo a + 1) a > 0][print "ok"]
5.2 Evaluation order rule
Expressions are evaluated from left to right. There is no operator precedence except for infix functions which do have precedence over prefix calls.
Function definition and usage is pretty straightforward in Red/System. The function specification block contains all the definitions required by the function. This includes:
calling arguments
optional returned value type
declaration of local variables
special attributes
6.1 Declaration
Syntax
<name>: func | function [
[<attributes>] ;-- optional part
"<function purpose>" ;-- optional doc-string
<argument> [<datatype>]
"<argument description>" ;-- optional doc-string
...
return: [<datatype>] ;-- returned value type (optional part)
"<returned value description>" ;-- optional doc-string
/local ;-- local variables (optional part)
<local> [<datatype>]
...
][
<body>
]
<name> : function's name
<attributes> : special attributes
<argument> : function's argument indentifier
<datatype> : integer! | byte! | logic! |
pointer! [integer! | byte! | float! | float32! | pointer!] |
float! | float32! | c-string! | struct! [<members>] |
struct! [<members>] value | <alias> | <alias> value
<local> : local variable
<body> : function's body code
<alias> : struct alias
Doc-strings are just optional documentation that can be processed by any external tool, they have no runtime effect.
Examples
hello: func [][print "hello"] ;-- no arguments, no locals, no return value
why?: func [return: [integer!]][42] ;-- minimal function returning an integer
inc: func [ ;-- increment an integer
a [integer!]
return: [integer!]
][
a + 1 ;-- last value is returned
]
percent?: func [ ;-- return relative percentage of a / b
a [integer!]
b [integer!]
return: [integer!]
/local c ;-- declare local variables
][
c: 100
a * c / b
]
Grouping of arguments or local variables by type is also possible.
Example
percent?: func [
a b [integer!]
return: [integer!]
/local
c d [integer!]
][
c: 20
d: c + 80
a * d / b
]
6.2 Return value
Any function is able to return a value if necessary. This is trivially achieved as last expression in function's body will be automatically returned if:
a RETURN: statement is present in function's spec block
the datatype of the function ending expression matches the one declared after the RETURN: statement
6.3 Attributes
It is possible to change how the function will behave at runtime using special attributes.
6.3.1 Infix
Allow the function to be called using an infix syntax. The function must take two arguments exactly or else a compilation error will be raised. Example:
avg: func [[infix] a [integer!] b [integer!] return: [integer!]][
(a + b) / 2
]
10 avg 6
will return:
8
Notes:
When the infix syntax is used, the prefix syntax is still allowed, but it will work only if there is no value on the left side of the function call. Example:
func [return: [integer!]][
avg 10 6 ;-- will return 8 as well
]
print "ok" avg 10 6 ;-- will produce a compilation error
The left-to-right evaluation rule applies also for user-defined infix functions, so:
10 avg 6 + 2 ;-- avg is executed first, then +
is not equivalent to
10 avg (6 + 2) ;-- + is executed first, then avg
6.3.2 Cdecl
Changes function's calling convention to C convention. This allows to safely pass a Red/System function as argument to imported C functions.
Example:
#import [
"foo.dll" cdecl [
foo: "foo" [
fun [function! [a [integer!] b [integer!] return: [logic!]]]
return: [integer!]
]
]
]
compare: func [
[cdecl] ;-- use C calling convention
left [integer!] right [integer!]
return: [logic!]
][
left <= right
]
foo :compare ;-- pass the function pointer
6.3.3 Callback
The purpose of the callback attribute is to manually force a callback compilation mode for a function that the compiler failed to infer as a callback. It can be used as a more meaningful replacement for stdcall when used on Windows.
The compiler can correctly infer callbacks when a function pointer is passed as a get-word to an imported function call. Other cases of passing a Red/System function pointer cannot be properly detected as external callbacks.
For example, if Red/System function pointers are passed to an external API in an indirect way (filling an array or a structure), and those functions will be later called by the external code (OS or a library), the callback attribute *must* be used in order for correct code to be generated. Moreover, if the external caller is using C calling convention, then the additional cdecl is required.
6.3.4 Variadic
Triggers the variable argument mode for native or imported functions. A native function using this attribute must provide two arguments in its specification block:
an integer variable for the arguments count
a pointer for the argument list (pointer! [integer!])
An imported function just needs the attribute without any other arguments declaration.
Examples of definition:
my-print: func [ ;-- native function
[variadic]
count [integer!] list [int-ptr!]
][
print ["count: " count lf]
until [
print [list/value lf]
list: list + 1
count: count - 1
zero? count
]
]
#import [ ;-- imported function
LIBC-file cdecl [
printf: "printf" [[variadic]] ;-- no need to specify any argument here
]
]
Passing arguments to a variadic function is achieved by wrapping them in a block (squared brackets delimited list).
Example of calls:
my-print ["hello" 123 "world"]
will output:
count: 3
00402035 ;-- pointer to "hello" c-string
0000007B ;-- 123 in hexadecimal
00402030 ;-- pointer to "world" c-string
Calling an imported variadic function:
printf ["%s %i %s" "hello" 123 "world"]
will output:
hello 123 world
6.3.5 Typed
Triggers the variable argument mode with type information for native functions. Typed is similar to the variadic attribute. A native function using this attribute must provide two arguments in its specification block:
an integer variable for the arguments count
a pointer for the argument records (using the typed-value! alias)
The typed-value! alias is defined as
typed-value!: alias struct! [
value [integer!] ;-- argument value or pointer
type [integer!] ;-- argument type
]
NULL value type ID is set by convention to type-int-ptr! ID (pointer! [integer!]).
Example of definition:
vprint: func [
[typed]
count [integer!] list [typed-value!]
][
print ["count: " count lf]
until [
print [list/value " : "]
print [form-type list/type lf] ;-- form-type converts a type ID to a c-string
list: list + 1
count: count - 1
zero? count
]
]
Passing arguments to a variadic function with type information is achieved by wrapping them in a block (squared brackets).
Example of calls:
vprint ["hello" 123 "world"]
will output:
count: 3
00402043 : c-string! ;-- pointer to "hello" c-string
0000007B : integer! ;-- 123 in hexadecimal
0040203E : c-string! ;-- pointer to "world" c-string
6.3.6 Custom
It is sometimes desirable to have full control over the native stack layout of a function call, for example, when the function call needs to be dynamically constructed. The custom attribute allows you to manually push values on stack and still generate a correct function call with adequate stack cleanups. This is mostly useful with imported C functions that rely on cdecl convention, which requires the caller to clean-up the stack. The custom attribute will take care of it in a cross-platform way.
Example:
foo: as function! [[custom]] <imported-c-function>
push 123
push 0
foo 2 ;-- custom call with 2 arguments
A custom call requires an integer number that is consumed internally by the compiler. That integer specifies the number of arguments pushed on stack (which will be cleaned up when the call returns).
6.3.7 Catch
Allows the function to catch runtime exceptions. The execution will resume inside the function just after the call where the exception occured. See Exceptions section for more info.
6.4 Type inference
Functions offer a limited type inference possibility for local variables.
In practice, it means that it is allowed to omit a local variable type declaration as long as the variable is initialized properly.
Example:
foo: func [
a [integer!]
return: [integer!]
/local c ;-- omitted local variable type
][
c: 10 ;-- variable type is integer!
a + c
]
6.5 Calling a function
Calling a function is achieved by writing its name followed by the required number of arguments.
(from the previous examples)
hello ;-- will print "hello" in the standard output
answer: why? ;-- will return 42 in variable 'answer
foo: 4
foo: inc foo ;-- foo holds 5 after the call to 'inc
bar: percent? 3 4 ;-- bar holds 75
It is also possible to pipe several function calls together:
foo: percent? 11 inc inc why? ;-- will return 25 in foo
6.6 Function pointer
It is possible to obtain a function address to pass it, for example, as an argument to external calls with callbacks.
Syntax
:<function name>
Example:
progress: func [[cdecl] count [integer!]][
print "." ;-- make the user see some progress
]
get-file "bigfile.avi" :progress ;-- blocking job would call 'progress
;-- periodically
A function pointer can be assigned to a variable for later use or dereferencing. Such a variable cannot be passed as an argument to other functions, nor returned by a function. Function pointer pseudo-type is not a first class datatype.
Note: Function address is returned as a function pointer pseudo-type, so it cannot be used as-is in expressions, but it can be safely casted to an integer! if required.
6.6.1 Function definition alias
In order to avoid repeating function specification, it is possible to define aliases by using ALIAS keyword.
Syntax
<name>: alias function! [<spec>]
<name> : aliased identifier (by convention, a ! suffix is added)
<spec> : a function valid specification block
Example:
foo!: alias function! [n [integer!] return: [integer!]]
bar: func [f [foo!]][...]
6.6.2 Function dereferencing
A function pointer can be dereferenced so that the function being pointed at will be called, like any other function. Correct number and type of arguments needs to be passed.
Example:
foo!: alias function! [n [integer!] return: [integer!]]
inc: func [n [integer!] return: [integer!]][
n + 1
]
bar: as foo! :inc
print bar 2 ;-- will output 3
Alternatively, you can dereference a function! pointer stored as a struct member by just accessing it.
Exiting at a function's end is not always desirable. Sometimes, conditional premature exiting from the function is required. This can be done using EXIT or RETURN special keywords.
6.7.1 Exit
Immediatly quits the function.
test: func [a [integer!]][
if zero? a [exit] ;-- exit the function here if a = 0
... ;-- if a <> 0, continue processing...
]
6.7.2 Return
Immediately quits the function and returns a value.
test: func [
a [integer!]
return: [c-string!]
][
if zero? a [
return "Not allowed" ;-- exit the function here if a = 0
]
"ok" ;-- return "ok" if a <> 0
]
6.8 Subroutines
Subroutines are reusable code parts in a function's body that can be called from within the function where they are defined only.
A subroutine name is a local variable that needs to be defined as of type subroutine!, else a compilation error will occur. Once defined, a subroutine can be called by invoking its name from anywhere in the function code (but after its own definition). A subroutine returns its last value to the caller.
In Red/System, variables are statically scoped. The place in source code where a variable is declared determines its scope.
7.1 Global Context
Global context is defined as the global namespace where all global variables and functions are defined. This context is unique. As a simple rule, every variable not declared in a function is a global variable bound to global context.
Example:
foo: 123 ;-- global variable
f: func [/local bar [integer!]][
bar: 123 ;-- locally scoped variable
]
7.2 Functions Contexts
Each defined function has its own local context. Variables declared in a function's definition block are locally scoped and can't be accessed outside of the function's body. On the other hand, global variable can be referenced and modified from functions. If a local variable has the same name as a global one, the local one will take precedence in function's body. The value of the homonym global variable won't be affected by local redefinitions in functions contexts.
Example:
foo: 1 ;-- global variable
var: 2
f: func [
return: [integer!]
/local
bar [integer!]
][
bar: 3
foo + var + bar ;-- will return 6
]
f: func [
return: [integer!]
/local
bar [integer!]
var [integer!]
][
bar: 3
var: 10 ;-- 'var is a local variable here
foo + var + bar ;-- will return 14
]
7.2.1 USE keyword
Inside a function body, it is possible to create arbitrary local contexts, with additional local variables thanks to the USE keyword:
Syntax
use [<spec>][<code>]
<spec> : list of local variables followed by optional type specification
<code> : body block of code
This creates a local context where new variables are available in the body block. Those variables are freed on exiting the body block. Using a variable name already existing in the function specification will result in a compilation error.
Local variables listed in spec block follow the same syntax as local variables in function spec block. Variable names can be followed by a type specification, or let the compiler infer the type (except for structs by value, which must have a declared type). Local contexts created by USE can also be freely nested (as long as no variable names from outer local contexts are re-used).
Examples:
f: func [
/local
foo [integer!]
][
foo: 1
use [bar][
bar: 2
use [baz][
baz: 3
print foo + bar + baz ;-- will output 6
]
]
]
f: func [
cond [logic!]
return: [integer!]
/local
bar [integer!]
][
bar: 3
if cond [
use [var][
var: 10 ;-- 'var is a local variable here
bar: var + bar ;-- will return 14
]
]
bar
]
7.3 Namespaces
It is possible to define local namespaces to provide local contexts able to encapsulate source code.
Syntax
<name>: context [<code>]
<name> : namespace identifier
<code> : body block of code
Any code is allowed in the body block including function definitions. All variables and functions created will have a local name that can be accessed locally or from outside using context name prefix in a path notation:
<name>/<variable> ;-- reading a variable from outside
<name>/<variable>: ;-- setting a variable from outside
<name>/<func-name> ;-- invoking a function from outside
The following elements are affected by namespace and become locally defined when declared in a context body block:
variables
functions
imported functions
enumerations
aliases
Example:
b: 0
a: context [
b: 123
foo: func [/local b][
b: 1
print-line b
]
print-line b ;-- will output 123
foo ;-- will output 1
]
print-line b ;-- will output 0
print-line a/b ;-- will output 123
a/foo ;-- will output 1
a/b: a/b + 1
print-line a/b ;-- will output 124
7.3.1 Nested namespaces
Nesting namespaces is allowed. When conflicting names are used for variables or functions, the nearest definition is used.
Example:
a: context [
b: 123
c: context [
#enum colors! [red green blue]
b: "hello"
foo: func [][print-line b]
]
print-line b ;-- will output 123
c/foo ;-- will output hello
]
print-line a/b ;-- will output 123
a/c/foo ;-- will output hello
print-line a/c/b/2 ;-- will output e
print-line a/c/blue ;-- will output 2
7.3.2 Global context access
From nested namespaces, in order to access an indentifier from global context when it is also defined locally, a special virtual path is provided to solve such cases:
Syntax
system/words/<name> ;-- calling a function, reading a variable
system/words/<name>: ;-- setting a variable
<name> : identifer or access path
Any global context identifier can be retrieved this way.
7.3.3 WITH keyword
In order to limit source code verbosity, the namespace prefix can be omitted if the source code is enclosed in a with body block.
Syntax
with <name> [<code>]
with [<names>] [<code>] ;-- alternative syntax for multiple names
<name> : namespace identifier
<names> : list of namespace identifiers
<code> : body block of code
Note: If multiple namespaces are specified and if they share same identifier(s), the last defined namespace (from the compiler point of view) takes precedence.
Example:
a: context [b: 0]
c: context [b: 1 d: 123]
with a [
print-line b ;-- will output 0
print-line a/b ;-- will output 0
]
with [a c][
print-line d ;-- will output 123
print-line b ;-- will output 1 ('c is defined after 'a)
]
8. Infix operators
Infix operators take two arguments and are positioned between them.
8.1 Math operators
The following math operations apply on integer or float values. When the operation results in an exceeded memory storage limit, behaviour to be defined.
Addition: +
value1 + value2
Subtraction: -
value1 - value2
Multiplication: *
value1 * value2
Division: /
value1 / value2
Remainder: %
value1 % value2
Note: a remainder will be negative if the divisor is negative. Same as in C or // operator in REBOL. Float types are not supported.
Modulo: //
value1 // value2
Note: a positive result is always returned. Same as 'modulo function in REBOL. Float types are not supported.
where
value1 : an expression returning an integer!
value2 : expression of same (or compatible) datatype as <value1>
The resulting value type for math operators is the type of the left argument (an implicit type casting is operated when required).
Note: for + and - operators, a pointer!, struct! or c-string! value can be used for both arguments or as first argument with an integer expression as second argument (see "Pointer arithmetic").
8.2 Bitshift operators
Signed left shift: <<
value1 << value2
Signed right shift: >>
value1 >> value2
Unsigned right shift: >>>
value1 >>> value2
where
value1 : an expression returning an integer! or byte!
value2 : integer! expression restricted to 0-31 range only.
Note: There is no unsigned left shift operator as it is the same as the signed left shift one.
8.3 Bitwise operators
Bitwise OR: or
value1 or value2
Bitwise XOR: xor
value1 xor value2
Bitwise AND: and
value1 and value2
Bitwise / Logical NOT: not
not value1
where
value1 : an expression returning an integer!, byte! or logic!
value2 : expression of same datatype as <value1>
Note: Logical NOT will return the opposite of the logic argument (TRUE<=>FALSE), while bitwise NOT will apply one's complement on the integer argument.
8.4 Comparison operators
These operators can be used only where a condition is allowed. See "Control flow functions" section for a list of functions using conditions.
Equal: =
value1 = value2
Not equal: <>
value1 <> value2
Greater than: >
value1 > value2
Less than: <
value1 < value2
Greater than or equal: >=
value1 >= value2
Less than or equal: <=
value1 <= value2
where
value1 : an expression returning a integer!, byte!, float!, float32!,
c-string!, pointer! or struct!
value2 : expression of same datatype as <value1>
Note:
= and <> can also be used to compare logic! values.
For c-string!, pointer! and struct!, comparisons are done on references, not on the value pointed at.
9. Control flow functions
9.1 if
Execute a block of code if a given condition is true. IF does not return any value, so it cannot be used in an expression.
Syntax
if <condition> [<code>]
<condition> : a conditional expression
<code> : code to execute if the condition is true
Example
if a < 0 [print "a is negative"]
9.2 either
Execute a block of code if a given condition is true, else execute an alternative block of code. If last expressions in both blocks have the same type, EITHER can be used inside an expression.
Syntax
either <condition> [<code>][<alternative>]
<condition> : a conditional expression
<code> : code to execute if the condition is true
<alternative> : code to execute if the condition is false
Examples
either a < 0 [
either a = 0 [
msg: "zero"
][
msg: "negative"
]
][
msg: "positive"
]
print ["a is " msg lf]
An alternative way to write it (allowed because all code paths return a value of the same type):
msg: either a < 0 [
either a = 0 [
"zero"
][
"negative"
]
][
"positive"
]
prin ["a is " msg lf]
9.3 loop
Loop over a block of code, decrementing a counter down to zero. LOOP does not return any value, so it cannot be used in an expression.
Syntax
loop <counter> [<code>]
<counter> : a valid expression returning a positive integer value
<code> : code to execute while the condition is not met
Examples
loop 3 [print "o"]
will output:
ooo
a: 2
b: 3
loop a + b [print "o"]
will output:
ooooo
9.4 until
Loop over a block of code until the condition at the end of the block, is met. UNTIL does not return any value, so it cannot be used in an expression.
Syntax
until [
<code>
<condition>
]
<code> : code to execute while the condition is not met
<condition> : a conditional expression
Note: The loop will always be executed at least once, even if the condition is met from the beginning.
Example
c: 5
until [
print "o"
c: c - 1
c = 0
]
will output:
ooooo
9.5 while
While a given condition is met, execute a block of code. WHILE does not return any value, so it cannot be used in an expression.
Syntax
while [<condition>][<code>]
<condition> : a conditional expression
<code> : code to execute if the condition is met
Note: It is possible to execute any code in the condition block as long as it ends with a conditional expression.
Example
c: 5
while [c > 0][
print "o"
c: c - 1
]
will output:
ooooo
9.6 break
BREAK allows to break out of the nearest enclosing loop at once and resume execution after the loop.
Syntax
break
Example
c: 5
until [
print "o"
break
c: c - 1
c = 0
]
will output:
o
9.7 continue
CONTINUE skips the remaining part of a body loop and resume execution at next loop iteration.
Syntax
continue
Example
c: 5
until [
print "o"
c: c - 1
either c > 3 [continue][break]
c = 0
]
will output:
oo
9.8 any
Global condition is met if at least one of the sub-conditions is met. ANY returns a logic! value.
Syntax
any [<condition-1> <condition-2> ...]
<condition-*> : a conditional expression
Example
if any [foo > 5 bar = 0][
print "true" ;-- reached if at least one condition is met
]
9.9 all
Global condition is met if all the sub-conditions are met. ALL returns a logic! value.
Syntax
all [<condition-1> <condition-2> ...]
<condition-*> : a conditional expression
Example
if all [foo > 5 bar = 0][
print "true" ;-- reached if both conditions are met
]
9.10 case
Execute the block of code following the first condition that is met. If all blocks of code end with an expression of same type, then CASE can be used inside an expression. A catch-all rule can be written using a conditional expression that always results in true.
Note: if no value matches, a runtime error will be raised.
Syntax
case [<condition> [<body>] ...]
<condition> : a conditional expression
<body> : code to execute if the condition is met
Examples
a: 3
case [
zero? a [print "0"]
a = 1 [print "1"]
a > 2 [print "greater than 2"]
]
will output:
greater than 2
Example retrieving the returned value:
time: 8
msg: case [
all [6 < time time < 11]["morning"]
all [11 <= time time < 22]["evening"]
time >= 22 ["night"]
]
print ["Good " msg]
will output:
Good morning
9.11 switch
Execute the block of code following the first value matched, or the default block if present and no value matched. If all blocks of code end with an expression of same type, then SWITCH can be used inside an expression.
Note: if no value matched and no default block is provided, a runtime error will be raised.
Syntax
switch <expression> [<values> [<body>] ...]
switch <expression> [<values> [<body>] ... default [<default-body>]]
<expression> : an expression resulting in byte! or integer! value
<values> : one or several integer! or byte! literal values
<body> : code to execute if one of the <values> is matched
<default-body> : code to execute if no value is matched
Exit and Return are sometimes not enough when it is needed to interrupt current execution and go back through several parent calls before resuming execution. Exceptions are solving such case by providing a way to resume execution from a parent caller anywhere in the calling hierarchy, up to global code level.
10.1 Throw
Raising a new exception is done by calling the throw function followed by an integer value used as the exception ID.
Syntax
throw <id>
<id> : integer value used as exception ID
Throw will produce an exception that will move up through the parent calls until:
a catch statement is encountered and the exception filter value is greater or equal than the exception ID value.
a function with a [catch] attribute is found.
If no catch is encountered, the exception, once reaching global code level, with exit the program with a runtime error.
Note: if throw is used from a function that has the catch attribute, the exception will still be raised and go through parent calls. This way, when a function with a [catch] attribute catches an exception, it can re-throw it if required.
10.2 Catch
There are two ways to catch exceptions created using throw.
10.2.1 Catch statement
Syntax
catch <filter> [<body>]
<filter> : integer value used to filter exceptions.
<body> : arbitrary body of code to which this catch applies to.
An exception occuring in <body> block (including deeply nested ones that were uncaught) will be caught only if its ID value is less or equal than the <filter> value. Once an exception is being caught, the execution resumes after the catch body block.
Note: Once an exception is caught by catch, the exception value can be retrieved using system/thrown.
bar: does [
print "hello"
throw 123
print "<hidden>"
]
foo: does [
catch 5 [bar] ;-- exception 123 not catchable
print "<hidden>"
]
catch 1000 [foo] ;-- exception will be caught here
will output:
hello
10.2.2 Catch attribute
Syntax
func [[catch]...][...]
A [catch] attribute will make the function catch all exceptions and resume at the next instruction. This attribute cannot be used in conjunction with other function attributes.
Example:
baz: does [
print " "
throw 123
print "<hidden>" ;-- never executed
]
bar: does [
print "hello"
baz
print "<hidden>" ;-- never executed
]
foo: func [[catch]][
bar
print "world" ;-- execution resumes there
]
foo
will output:
hello world
10.3 Exception value
The integer argument passed to throw is propagated with the exception, it can be read using a system access path: system/thrown
Syntax
system/thrown ;-- read access (returns an integer)
system/thrown: <id> ;-- write access
<id> : integer value
The system/thrown path can be both read and written. It can be used to take different actions depending on the thrown value. The write access exists so that the value can be resetted manually when needed. It is recommended to reset it to 0 in such cases.
Examples
foo ;-- taken from previous example
print system/thrown
will output:
123
A dispatching use case would look like this:
foo: does [throw 10]
bar: does [throw 20]
baz: does [throw 30]
dispatch: func [[catch] n [integer!]][
system/thrown: 0 ;-- this is only useful if no exception occurs
switch n [
1 [foo]
2 [bar]
3 [baz]
default [
print-line "do nothing"
]
]
switch system/thrown [
0 [print "no exception occured"]
10 [print "foo"]
20 [print "bar"]
30 [print "baz"]
]
]
dispatch 2
will output:
bar
11. Stack functions
11.1 push
Push a value on top of execution stack. Stack pointer is modified.
Syntax
push <value>
<value>: expression of any type
Examples
push 123
push a
push "hello"
push p/value
11.2 pop
Pop a value from top of execution stack. Stack pointer is modified.
Syntax
pop
return: an integer value
12. Debugging functions
12.1 assert
Make a runtime assertion. If assertion fails, a runtime error will be raised.
Syntax
assert <conditional expression>
Example
Red/System []
assert 1 = 2
will produce if saved to %test.reds file and run:
*** Runtime Error 98: assertion failed at line 3
*** in file: %test.reds
13. System structure
System structure is a special struct value, defined at run-time, that gives access to some core features of Red/System.
13.1 args-count
Informs about the number of words passed on command-line. The executable itself is included in the count, so args-count is greater or equal to one.
Syntax
system/args-count
return: an integer value (>= 1)
13.2 args-list
Pointer to an array of words passed on command-line (including the program name). The array's end is marked by a null pointer.
Syntax
system/args-list
return: a pointer value of type: str-array! (alias)
ORBIT_SOCKETDIR=/tmp/orbit-root
SSH_AGENT_PID=2248
TERM=xterm
SHELL=/bin/bash
... ;-- rest of output omitted
13.4 stack/top
Set or retrieve execution stack top address.
Syntax
Getting stack top value:
system/stack/top
return: a pointer value of type: pointer! [integer!]
Setting stack top value:
system/stack/top: <address>
<address>: a pointer value of type: pointer! [integer!]
13.5 stack/frame
Set or retrieve execution stack frame address.
Syntax
Getting stack frame value:
system/stack/frame
return: a pointer value of type: pointer! [integer!]
Setting stack frame value:
system/stack/frame: <address>
<address>: a pointer value of type: pointer! [integer!]
13.6 stack/align
Ensures that native stack is properly aligned at the point of calling, according to target ABI requirements. It returns the newly aligned stack pointer.
Syntax
system/stack/align
return: a pointer value of type: pointer! [integer!]
13.7 stack/allocate
Reserves a storage space on stack (in stack slots unit) and returns a pointer the beginning of that space. This can be used to allocate arbitrary space for data structure on native stack. Such space will be automatically freed on a function exit.
Syntax
<ptr>: system/stack/allocate <slots>
<ptr>: system/stack/allocate/zero <slots>
<ptr> : variable of type: pointer! [integer!]
<slots>: expression returning an integer
The /zero option forces the allocated stack region to be zero-filled.
13.8 stack/free
Frees a storage space on stack (in stack slots unit). This is useful for manually controlling the release of a stack storage space allocated with system/stack/allocate, or from outside a function.
Syntax
system/stack/free <slots>
<slots>: expression returning an integer
13.9 stack/push-all
Saves all registers to stack.
Syntax
system/stack/push-all
The following registers are saved:
IA-32: eax, ecx, edx, ebx, original esp, ebp, esi, edi, eflags, st0-7, xmm0-7.
ARM: r0-r12, r14, d0-15, CPSR.
Note that eax and r0 content is modified after this call (though original values are restored by `pop-all`).
13.10 stack/pop-all
Restore all registers from stack.
Syntax
system/stack/pop-all
See `stack/push-all` section for complete list of registers affected.
13.11 pc
Retrieve the CPU program counter value.
Syntax
system/pc
13.12 cpu/<reg>
Set or retrieve any of the CPU's registers value.
Syntax
Reading a register:
system/cpu/<register>
return: a value of type integer!.
Setting a register:
system/cpu/<register>: <value>
where:
<register>: valid name of a CPU register (platform-dependent)
<value> : an integer! value
13.13 cpu/overflow?
Checks if the last integer math operation has overflown.
Syntax
system/cpu/overflow?
return: a value of type logic!.
As many CPU operations can change this state, it is only reliable if used immediatly after a math operation.
13.14 fpu/type
Returns a unique ID for the currently used FPU.
Syntax
system/fpu/type
Possible returned values are currently:
FPU_TYPE_X87: for x87 FPU.
FPU_TYPE_SSE: for SSE unit on Intel.
FPU_TYPE_VFP: for ARM VFP unit.
13.15 fpu/option
13.15.1 fpu/option/rounding
Set or retrieve the FPU rounding mode. The possible standard modes are:
Nearest: (even) rounded result is the closest to the infinitely precise result
Down: (toward -INF) rounded result is the closest to but no greater than the infinitely precise result
Up: (toward +INF) rounded result is the closest to but no less than the infinitely precise result
Zero: (truncate) rounded result is the closest to but no greater in absolute value than the infinitely precise result
<ptr> : memory location to read/write to (pointer! [integer!])
<old> : integer value to compare to (integer!)
<new> : integer value to write if comparison succeeded (integer!)
The compare & swap semantics are described here. The memory location is read and the value is compared to the old value. If equal, the new value is written and it returns `true`, otherwise it aborts and returns `false`.
13.28 atomic/<math-op>
Performs a thread-safe atomic math or bitwise operation to a given memory location.
Syntax
system/atomic/<op> <ptr> <value>
system/atomic/<op>/old <ptr> <value>
return: the new value, or the old value if /old refinement is present.
where:
<op> : add, sub, or, xor, and
<ptr> : memory location to write to (pointer! [integer!])
<value> : operand value (integer!)
13.29 image/base
Returns the base address of the executable image in memory.
Syntax
system/image/base
return: a value of type pointer! [integer!].
13.30 image/code
Returns the CODE segment offset from image's base.
Syntax
system/image/code
return: a value of type integer!.
13.31 image/code-size
Returns the CODE segment size.
Syntax
system/image/code-size
return: a value of type integer!.
13.32 image/data
Returns the DATA segment offset from image's base.
Syntax
system/image/data
return: a value of type integer!.
13.33 image/data-size
Returns the DATA segment size.
Syntax
system/image/data-size
return: a value of type integer!.
13.34 alias
Get the ID value of an alias. This is required in typed functions in order to distinguish different struct! values.
Syntax
system/alias/<name>
where
<name> : existing alias name
This expression returns an integer! value that can be used to test at runtime, the type of an argument in a typed function.
Example:
foo!: alias struct! [a [byte!]]
s: declare foo!
probe: func [
[typed] count [integer!] list [typed-value!]
][
until [
if list/type = system/alias/foo! [
print "foo! alias detected"
]
list: list + 1
count: count - 1
zero? count
]
]
probe [1 "r" s 123]
will output:
foo! alias detected
13.35 words
Access an indentifier from global context when it is also defined locally.
Syntax
system/words/<name> ;-- calling a function, reading a variable
system/words/<name>: ;-- setting a variable
<name> : identifer or access path
See also "Namespaces" section.
14. Compiler directives
Some features of Red/System need to be processed at compile-time rather than at run-time. This is especially true for features related to the linking phase that builds the executable file. In order to distinguish such compile-time commands or options, compiler directive are introduced. Their syntax is:
#<directive> <argument-1> <argument-2> ...
<directive> : a valid identifier
<argument-*> : argument can be any Red valid datatype
The number of arguments is specific to each compiler directive.
As compiler directives apply globally to programs, they are not allowed inside code blocks (this restriction might be removed if local directives are introduced in the future).
Implementation note: The directive arguments datatype set, is the one provided by REBOL during the bootstrapping phase only. Once the Red layer has been implemented, the allowed datatypes will be Red ones.
15. Importing External Libraries
15.1 #import
Red/System is able to load external shared libraries at the time a Red/System executable is loaded by the operating system. This requires that the programmer gives instructions to the compiler about which library to load and how to map library's functions and variables to Red/System current context. This feature is called "library import" in Red/System and it is supported by a specific compiler directive: #import. This directive can be used anywhere in the global or local contexts of your Red/System program, but be sure to put it before you use one of the mapped functions, else a compiler error will be raised. This directive can be used multiple times in your source code if it makes it more readable. If there is a huge number of functions to imports, putting them in separate includes files would be considered as good practice.
Note: this is not the same as dynamically loading a shared library from your Red/System code after your program has started. Such approach allows you to delay the loading of your libraries and to free them. Imports cannot be freed.
Syntax
#import [
"<library>" <convention> [
<function name>: "<ID>" [
<argument> [<datatype>]
...
return: [<datatype>] ;-- optional part
]
... ;-- more functions mappings
<variable name>: "<VAR>" [
<datatype>
]
... ;-- more variables mappings
]
... ;-- more libraries to load
]
<library> : shared library file name (with extension)
<convention> : calling convention of the library (stdcall | cdecl)
<function name> : name of the mapped function in current context
<variable name> : name of the mapped variable in current context
<ID> : identifier of the function in the shared library
<VAR> : identifier of the variable in the shared library
<argument> : function's argument indentifier
<datatype> : integer! | byte! | pointer! [integer! | byte! | float! | float32! | logic!] |
float! | float32! | c-string! | struct! [<members>] |
function! [<spec>] | <alias>
Notes:
An absolute path to the library can be provided in OS-specific format.
The RETURN: statement indicates that the mapped function has a return value.
There's no limitation on the number of libraries or functions that can be declared this way.
Supported calling conventions are:
stdcall: used mostly by Windows API (but can be also used by third-party DLLs)
cdecl: default calling convention used by C shared libraries.
As Red/System is destined to be used mostly for low-level system programming, syscalls mappings are also supported using the #syscall compiler directive.
Syntax
#syscall [
<function name>: <ID> [
<argument> [<datatype>]
...
return: [<datatype>] ;-- optional part
]
... ;-- more functions mappings
]
<function name> : name of the mapped function in global context
<ID> : syscall integer ID
<argument> : syscall's argument indentifier
<datatype> : integer! | byte! | pointer! [integer! | byte!] |
float! | float32! | c-string! | struct! [<members>]
The RETURN: statement indicates that the mapped syscall has a return value.
There's no limitation on the number of syscalls that can be declared this way.
Usage
The following example is Linux-specific, but should work with most UNIX systems.
#syscall [
write: 4 [
fd [integer!] ;-- file descriptor, STDOUT = 1
buffer [c-string!]
count [integer!]
return: [integer!]
]
quit: 1 [ ;-- "exit" syscall, no return value
status [integer!]
]
]
msg: "Hello World"
result: write 1 msg length? msg
if negative? result [
print "Error: write failed"
quit 3 ;-- exit and return an error code
]
quit 0 ;-- no error
will output (if no error):
Hello World
16. Source Processing
Red/System relies on a preprocessor to make compile-time modifications of the source code in order to provide syntactic sugars, like hexadecimal and character literal forms for integers. Some features are user controlled through compiler directives like #define or #include.
16.1 #define
The #define compiler directive is a rudimentary macro system that can be used to:
define constant values
make simple macro expressions
The name matching method is exact word matching. This ensures that no accidental source code corruption can occur.
Syntax
#define <name> <value>
<name> : identifier to use in the source code
<value> : single value or block of values to replace in the source code
Usage
#define R_PART 00FF0000h ;-- simple constants definitions
#define G_PART 0000FF00h
#define B_PART 000000FFh
#define zero? [0 =] ;-- simple test expression macro
color: 00550063h
if zero? (R_PART and color) [
print "Red not found"
]
if zero? (G_PART and color) [
print "Green not found"
]
if zero? (B_PART and color) [
print "Blue not found"
]
will output:
Green not found
Note: Parens are required in this example on test expressions so that the compiler performs the second infix expression (the and operator) before the first (the equal operator).
16.1.1 Parametrized macros
A more powerful macro version is possible, taking one or several parameters as input that get replaced in the emitted template.
Syntax (declaration)
#define <name>(arg1 arg2 ...) <body>
<name> : macro identifier
<body> : macro body (block! or paren! value)
Syntax (usage)
<name>(value1 value2 ...)
If the body is a block! value, its content replaces the name and parameters passed in the source code. If the body is a paren! value, the parens are kept after replacement in source code.
Notes:
The replacement process in the body block is done deeply in all series found, including paths.
Nested macros are fully supported.
Examples:
#define MAX(a b) (either a > b [a][b])
print MAX(3 4) ;-- will print 4
#define SW(identifier) [system/words/identifier]
a: 123
print SW(a) ;-- will print 123
16.2 #enum
The #enum compiler directive allows to declare enumerations using labels. A list of integers is assign to a list labels. These labels could then be used anywhere in the source code and will be converted to their integer value when needed by the compiler.
Syntax
#enum <name>! [
<label> | <label>: <value>
...
]
<name>! : enumeration name (ending with a ! by convention)
<label> : label to which an integer value is assigned to
<label>: : one or several label(s) set to a given integer value
<value> : integer value
The enumeration starts from 0 by default. It is possible to assign a starting value using a label name with a ending colon, followed by an integer. It is also possible to assign several labels to the same value.
Notes:
labels syntactic rules are the same as for variable identifiers.
enumeration names are pseudo-types that can be used in any local or global variable declaration in place of a real type.
labels can be used in place of integer variables or literal integer values.
ambiguous enum or label names that could conflict with other existing names will raise a compilation error.
Examples
#enum colors! [A B C D E]
print-wide [A E]
will output
0 4
Using in place of literal integers:
#enum colors! [red blue green yellow]
a: red
print switch a [
red ["Red"]
blue ["Blue"]
yellow ["Yellow"]
]
will output
Red
Defining labels with user-selected value:
#enum values! [a: 1 b c d: e: 10]
print-wide [a b c d e]
will output
1 2 3 10 10
16.3 #include
The #include compiler directive will insert the target source file at the current position in the calling source code.
This directive helps split the source code in several files, allowing for example, to put common functions or definitions in a single place and including them where required, across several source files or across different projects.
Syntax
#include %<file>
<file> : relative or full path to a Red/System source file
Usage
Rewriting the example from #define section:
definitions.reds file:
#define R_PART 00FF0000h
#define G_PART 0000FF00h
#define B_PART 000000FFh
#define zero? [0 =]
test-primary: func [
color [integer!]
mask [integer!]
msg [c-string!]
][
if zero? (color and mask) [
print msg
]
]
main.reds file:
#include %definitions.reds
color: 00550063h
test-primary color R_PART "no Red found"
test-primary color G_PART "no Green found"
test-primary color B_PART "no Blue found"
16.4 #if
The #if compiler directive goal is to allow conditional compilation based on a simple conditional expression limited to a few compilation options. If the expression result is TRUE, the following block of code is compiled.
Syntax
#if <option> <op> <value> [
<body>
]
<option> : compiler option name (case-insensitive)
<op> : =, <>, <, >, <= or >=
<value> : any value accepted by the property
<body> : Red/System source code
Valid option names and allowed values are listed in %config.r file, in a comment section. For words value, both word! and lit-word! syntaxes will be accepted.
Example
#if OS = 'Windows [
print "Running on Windows"
]
Note: if the argument is a word and not a value (like an integer! or a logic!), it needs to be prefixed with ' character to avoid being evaluated (as in normal REBOL expressions).
16.5 #either
The #either compiler directive allows conditional compilation based on a simple conditional expression limited to a few compilation options. If the expression result is TRUE, the first block of code is compiled, else the second one is compiled.
Syntax
#either <option> <op> <value> [
<body-TRUE>
][
<body-FALSE>
]
<option> : compiler option name (case-insensitive)
<op> : =, <>, <, >, <= or >=
<value> : any value accepted by the property
<body-*> : Red/System source code
Valid option names and allowed values are listed in %config.r file, in a comment section. For words value, both word! and lit-word! syntaxes will be accepted.
Example
#either OS = 'Windows [
print "Running on Windows"
][
print "Running probably on a UNIX platform"
]
Note: if the argument is a word and not a value (like an integer! or a logic!), it needs to be prefixed with ' character to avoid being evaluated (as in normal REBOL expressions).
16.6 #switch
The #switch compiler directive allows to define several conditionally compiled block of codes, depending on the value of a compilation option. The block following the option name is searched for a matching value and the following block of code is compiled.
Syntax
#switch <option> [
<value-1> [
<body-1>
]
<value-2> [
<body-2>
]
...
#default [ ;-- optional default clause
<body-N>
]
]
<option> : compiler option name (case-insensitive)
<value-*> : any value accepted by the property
<body-*> : Red/System source code
Valid option names and allowed values are listed in %config.r file, in a comment section. For words value, both word! and lit-word! syntaxes will be accepted.
Note: the #default clause is optional. If present, it acts as a default catch-all value if none other is matched.
Example
#switch type [
exe [print "Building an executable"]
dll [print "Building a dynamically linked library"]
obj [print "Building an object file"]
lib [print "Building a statically linked library"]
]
Note: if the argument is a word, it does not need to be prefixed with ' character in a #switch block.
16.7 #verbose
This directive allows to locally change or overwrite the verbosity compilation option.
Syntax
#verbose <level>
<level> : integer value from 0 (no logs) to 11 (exhaustive logs).
It is mostly used for local debugging of the compilation process by framing the source lines to log between two #verbose directives.
Example:
#verbose 9
print "hello" ;-- maximum logs emitted
#verbose 0
print "world" ;-- no logs emitted
16.8 #call
As a dialect of Red, Red/System can call back a Red-level function, passing arguments, using a compiler directive.
Syntax
#call [<Red-call> <arg1>...<argn>]
<Red-call> : a Red word, or path (for refinements), referring to a function! value.
<arg1>...<argn> : list of arguments.
The arguments should be passed as pointers to Red values of the expected type, except for integer! and logic! datatypes, that can be passed inlined, without requiring any boxing. When required, for pointer arguments, a simple type casting is allowed.
Once the Red function returns, the normal execution of Red/System code continues. The Red return value cannot be directly retrieved currently, but it should be accessible from Red arguments stack. The Red function must have all arguments typed, else a compilation error will occur.
When a path is used as the function call, it refers to the function name, followed by refinements. The eventual refinements arguments are then, expected to be provided, following the mandatory ones.
Example
Red []
inc: func [n [integer!]][n + 1]
#system [
#call [inc 123]
int: as red-integer! stack/arguments
print int/value ;-- will print 124
]
16.9 #export
When generating a shared library, the Red/System toolchain needs to know which identifiers will be exposed to third-party users of the library. This is achieved using the #export compiler directive.
Syntax
#export [<symbols>]
#export <cconv> [<symbols>]
<symbols> : one or several function or global variable name
<cconv> : optional calling convention word (stdcall or cdecl)
One or several #export directives can be used in the same Red/System library code.
16.10 #u16
Converts a literal string to UTF-16LE format. The string is stored in memory in that new format with a terminal UTF-16 NUL character.
Syntax
#u16 <string>
<string> : a literal c-string!
Only literal strings can be statically processed by this directive.
16.11 #inline
Injects CPU-specific machine code at current position in code. Both standalone statements and nested statements are supported.
Syntax
#inline <binary>
#inline [<binary> return: [<type>]]
<binary> : a literal binary containing the machine code
<type> : any valid Red/System type
The second syntax is used to nest the machine code in an R/S expression. The returned type specification is then mandatory.
The official Red/System source file suffix to use is: .reds
17.2 Header
Red/System enforces the usage of a standard header for all sources (one of the great ideas in REBOL), to both identify a valid Red/System program and document it.
Syntax
A valid Red/System source file will need this header:
Red/System [
<name>: <value>
... ;-- more attributes...
]
<name> : valid identifier
<value> : any Red valid datatype
There is no minimum or maximum number of entries that a valid header can contain, so an empty block will also be valid (but bad practice).
The attribute that you can specify are not limited, you can add whatever you want/need. Anyway, some attribute names are used by convention:
Title: application title
Purpose: short description of the application purpose
Author: source code author name
File: name of the source file
Version: source code version (usually using a tuple! literal)
Date: date of last version
Rights: copyrights
License: source license (URL or full text)
History: source modifications history
Note(s): any special notice
Example
Red/System [
Title: "Red/System small demo app"
Author: "Nenad Rakocevic"
File: %hello.reds
Rights: "Copyright (C) 2011 Nenad Rakocevic. All rights reserved."
License: "BSD-3 - https://github.com/dockimbel/Red/blob/master/COPYING"
]
17.3 Code flow layout
A typical Red/System program is a mix of function definitions and global code (meaning executable code that is not in a function). There is no concept of "main" function in Red/System. The only entry point is the beginning of the source code and the exit point is at the end of the source code, or at a QUIT call if encountered before.
Example:
foo: 123
print "hello"
bar: func [a [integer!]][foo * 2]
foo-twice: bar foo
either foo < 100 [
print "less than 100"
][
print "more than 100"
]
bye: func [][print "goodbye"]
bye
So, it is possible to mix functions and global code providing that functions are defined before they are called from global context. Such restrictions don't apply if the call is made from a function context, so cross-references like these:
foo: func [...][...bar...]
bar: func [...][...foo...]
can be processed without issues.
17.4 Coding guidelines
TBD
17.5 Shared library program
You can use global code in your shared library program, but this is not recommended, as it might be unsupported in the future. All your code should be in functions.
Note: All shared library programs need to export at least one symbol using the #export directive or a compilation error will occur.
A shared library might implement one or several of the callbacks below:
on-load : called when the library is loaded by the host
on-unload : called when the library is freed by the host
The specifications for these callbacks are system-specific:
The list of following symbols and keywords are reserved, they cannot be used as variable or function name:
% & *
+ - -**
/ // ///
< << <=
<> = >
>> >= >>>
?? alias all
and any as
assert break case
catch comment context
continue declare either
exit false func
function if loop
not null or
pop push return
size? switch throw
true until use
while with xor
19. Possible Evolutions
19.1 Variables
Add support for multiple assignments, like a: b: c: 0
19.2 Pointers
Remove pointer! datatype (struct! is able to do the same job)REJECTED
Accept boolean operations on pointers: OR, XOR, AND (nice but use-cases would be rare?)
19.3 Struct
Add support for specifying struct memory alignment and padding. Default structure and members alignment would be the one specified in target object (per target). Per struct specific rules should be possible using the following syntax:
#align <integer> ;-- change memory alignment for all subsequent
;-- c-strings, pointers and structs.
struct [
[align <n> <little|big>] ;-- change members alignment and endianess
<members>
]
<n> : number of bytes to align members to
<little> : little endian (optional)
<big> : big endian (optional)
Add support for passing struct! by value when required.
19.4 C-strings
Add a FOREACH control flow function to traverse c-strings (or even array! values):
A simple way to traverse a c-string could be:
foo: "I am a c-string"
foreach c foo [prin c]
will output:
I am a c-string
19.5 Logic!
Add logic! support for OR, XOR, AND operators (if it provides any advantage over ANY/ALL).
19.6 Integer!
Bind the integer! type to int32! or int64! depending on the target platform. It needs some further investigations to determine if it can be a real advantage or not.
19.7 Functions
Add support for functions refinements (same as in REBOL)
Infer functions return value datatype
Accept an integer parameter for [catch] attribute to set manually the catching level (to avoid manual exception re-throwing).
Distinguish the behaviour of function from func to assume all set-words are locals (as in Red).
19.8 New datatypes
Array!: this datatype would allow declaring arrays of values that could be accessed with an integer index (similar to C arrays). Redundancy with c-string! datatype would need to be considered. Here is a draft of possible syntax and usage:
Binary!: this datatype was reserved early in the compiler's datatypes list, but not implemented. Its purpose was just to provide literal input/output forms in hexadecimal for c-string values. A standalone datatype for such purpose might be avoidable, hence the delayed implementation.REJECTED
Logic!: add a boolean datatype, so that booleans resulting from conditional expressions become first class citizens.
19.9 New functions
Add a FORM function (convert any datatype to c-string!)
Add a SWITCH function: branch on different code blocks depending on a input value
Add a INLINE function to inline machine code: inline #{1234...} and/or assembler
Add a NOT function that would return the boolean opposite of argument value. As booleans are not really supported in the current specs, NOT addition is postponed.
Add a REPEAT function to be able to loop with a counter:
total: 0
repeat c 10 [total: total + c]
Add a TYPE? function returning the argument's type as integer! (should use defines to name them)
19.10 Misc
Add a module system (per-file contexts for example)
Support 0 and 1 as valid boolean resultsREJECTED
Extend the get-word! syntax to integer! variables.
Support multiple nested type castings.
20. Document History
08/09/2022 - revision 60
Adds pointer! to pointer! mentions.
05/09/2022 - revision 59
Improves struct by value description.
23/02/2022 - revision 58
Removes EITHER from expression rules, as the current compiler can't handle it properly.
14/08/2020 - revision 57
Adds subroutines description.
Adds binary! arrays description.
Adds multiple assignments and variable grouping mentions.
Adds system/stack/allocate/zero description.
Adds system/fpu/status description.
05/09/2019 - revision 56
Fixes issues #3301, #3303, #3305.
27/08/2019 - revision 55
Adds system/atomic/* intrinsics description.
25/06/2019 - revision 54
Adds a #INLINE directive to inline machine code.
Drop support for % and // operators on float types.
Adds system/io/* intrinsics.
Adds system/stack/*-all intrinsics.
Document the division specific behaviors.
13/09/2017 - revision 53
Extends the description of literal arrays to heterogeneous arrays.
Adds `keep` option description to `as`.
Allow function! to pointer! in the type casting matrix.
01/05/2017 - revision 52
Adds description of nested structs values.
Adds stack/allocate and stack/free sections.
20/04/2017 - revision 51
Adds description of structs by value passing/returning.
Adds USE keyword description.
31/03/2017 - revision 50
Adds description of get-path to get struct member address.
29/03/2017 - revision 49
Adds description for variables importing from external libraries.
Updates #call description to include refinements.
Adds a section for `size?` description.
11/03/2017 - revision 48
Additional explanations for `callback` attribute usage.
10/10/2016 - revision 47
Allows integer to floats, and floats to integer direct type casting (using FPU for conversions).
Adds description of system/cpu/overflow? accessor.
Minor improvement of type casting matrix, "to" removed to avoid confusion.
09/03/2016 - revision 46
Added #u16 directive description.
12/05/2015 - revision 45
Added BREAK and CONTINUE descriptions.
Added LOOP description.
Updated keywords section.
13/03/2015 - revision 44
Move exceptions description to its own separate section and upgraded it with the new CATCH statement description.
Fixed C void pointer bad code example
13/10/2014 - revision 43
Added notes about the possible future distinction between function and func.
Fixed 2 misleading statements about whether or not the condition is met in until.
Minor grammar cleanups.
29/07/2014 - revision 42
Removed the note about ELF backend not calling on-load/on-unload.
Added system/cpu et system/fpu sections.
26/04/2014 - revision 41
Added literal arrays section.
Type matrix adjustments on AS/TO meaning
14/02/2014 - revision 40
Function! to function! type casting now allowed.
09/08/2013 - revision 39
Extended function! pointer description to struct members. Updated type casting matrix accordingly.
Added function! as valid struct member.
Added back the callback attribut description.
Added custom attribut description.
Added system/stack/align description.
Added #call compiler directive description.
Added #export compiler directive description.
Added shared library and driver programs layouts sections.
18/04/2013 - revision 38
Added "Exceptions" section.
Added "Catch" attribute entry in "Attributes" section.
Added "throw" to keywords list.
24/11/2012 - revision 37
Issue #280 fixed (missing function! as possible target in type casting matrix)
Issue #283 fixed (uint8! mentions removed from document, only kept as possible evolutions)
Issue #286 fixed (missing `null` keyword in reserved keywords list)
Changed precedence order in WITH namespaces list
Changed description of modulo/remainder for floats, now they give the same result.
24/10/2012 - revision 36
Added docstrings in function syntax.
NULL type ID note added.
Added Function pointers alias and dereferencing descriptions.
Added description for variable pointer (get-word extended).
Removed items already implemented from "Possible Evolutions" section.
26/02/2012 - revision 33
Removed misleading float32! syntax description and replaced by a simple explanation on how to form float32! literal values.
Fixed code example in section 4.4.2, the `as float32!` type casting was missing.
04/02/2012 - revision 32
Added float! and float32! datatypes.
Type casting matrix updated with float! and float32!
Updated list of runtime type IDs.
Added #enum description
03/01/2012 - revision 31
Completed CASE description
Added SWITCH description
Added SWITCH to keywords list
Changed the casing of the function names used as section headers.
29/12/2011 - revision 30
Added CASE description.
Update keywords list.
Minor typo fixed.
"API Reference" level removed, all sub-sections are moved up and placed after "Scoping" chapter.
Literal struct members default zero value mention added.
21/09/2011 - revision 29
Added description for system/alias/... special path.
20/09/2011 - revision 28
Typed attribute ID values and macros updated.
Callback attribute removed and replaced by 'cdecl attribute.
17/09/2011 - revision 27
Added missing bitshift operators in reserved keywords list.
Improve variadic function names in examples to avoid confusion with C function names.
01/09/2011 - revision 26
Added system/pc description.
12/08/2011 - revision 25
Fixed invalid hex values in some code examples.
Minor code example improvement for typed attributes.
Minor editing changes
Updated examples to use variadic print function
09/08/2011 - revision 24
Typeinfo function attribute renamed to typed.
Added a note about imported library path format.
Special 'system structure fully documented
Added push and pop as reserved keywords
08/08/2011 - revision 23
Variadic and typeinfo function attributes added.
Get-stack and set-stack functions removed.
Added system/stack/* descriptions.
04/08/2011 - revision 22
Stack manipulation functions added.
24/07/2011 - revision 21
Pointers arithmetic extended to allow addition and subtraction of pointer arguments.
07/07/2011 - revision 20
Added ASSERT keyword description
05/07/2011 - draft 19
Fixed #if and #either examples. Note added about lit-words arguments
Delimiters and free-form syntax descriptions added
Various small fixes and cleanups
04/07/2011 - draft 18
Added a note about alias names living in their own namespace.
Added % operator and notes to help distinguish % and // operators.
Added a mention for global variables initialization restriction in Null description.
Added -**, >>>, %, /// in reserved keywords list.
Added bitshift operators section.
Propagated "struct" and "pointer" replacement by "declare "
23/06/2011 - draft 17
Updated type casting description to match the new relaxed syntax implemented.
Added null keyword description.
Upgraded pointer! type reference to take byte! into account
C void pointers now mapped to pointer! [byte!] and byte-ptr!
22/06/2011 - draft 16
Added missing 'comment word in keywords list
Added variable initialization at root level restriction description.
Note added to warn about transparent statements after a function ending expression.
Minor code example cleanup (local type declaration removed)
Added a function! column in the type casting matrix.
Added missing Expression chapter.
12/06/2011 - draft 15
Added #either directive
#if and #either now accept any comparison operator
11/06/2011 - draft 14
Added #if and #switch directives description
07/06/2011 - draft 13
Keywords list updated, only symbols and keywords built in the compiler are retained.
Function pointer description and example updated wrt recent implementation changes.
Added & as a reserved keyword (for future use)
Keyword list updated and cleaned up
Fixed non-confirming hex literals in examples (thanks Kaj)
Minor fixes
05/06/2011 - draft 12
Callback description rewrote entirely to match the new behaviour.
Function! type added to imported functions specification rules
04/06/2011 - draft 11
Multi-line comments restriction added.
Compiler directives usage inside code blocks is now forbidden explicitly.
Multiple type castings explicitly forbidden and added as possible evolution.
Type inference for return value removed.
Added new function attribute: callback
23/05/2011 - draft 10
Added type casting combinations matrix
Fixed uncomplete code example for ALIAS (thanks to Kaj)
Fixed list of allowed characters in variables (issue #48)
Minor other fixes
20/05/2011 - draft 9
Added missing type casting syntax description for compound types (thanks to Andreas).
19/05/2011 - draft 8
Added logic! datatype to struct members and type casting allowed types.
Added a note about evaluation rule for user-defined infix functions.
Added missing types supporting math and comparison operations.
Explained condition permitting that EITHER function be used inside expression.
Mention about not possible inclusion in expressions for IF, UNTIL, WHILE.
Returned type for ANY and ALL precised.
05/05/2011 - draft 7
Removed mentions for c-string variables acting as constants.
27/04/2011 - draft 6
Added 'infix attribute support in functions spec block.
Added missing logic! datatype in functions spec block definition.
15/04/2011 - draft 5
Letters in hex integers are restricted to uppercase only. Warning added for variable names that could be mistaken for hex integers.
Logic! support for OR, XOR, AND operators withdrawn from specifications and put in the "Possible Evolution" section. (They can be replaced by ANY/ALL/NOT to compose logic expressions)
'Comment added as reserved keyword
Added a note for = and <> operators for use with logic! values. C-string! values comparisons restricted to those two operators only (c-string! was wrongly allowed for all comparison operators).
10/04/2011 - draft 4
Struct arguments are passed by reference now again. STRUCT returns a reference to the struct value.
Syntax now precised for STRUCT keyword followed by an alias name
Struct! arithmetic added
C-string! arithmetic added
Pointer! now restricted to [integer!] only. It is more consistent with c-string! and struct! which are both (implicit) pointers already.
Pointer! section moved after c-string! and struct! (because it is less important now)
08/04/2011 - draft 3
Added get-word! syntax for getting struct variable address
New Possible Evolution: extend get-word! syntax to all variable types
Added "pointer" as reserved keyword
Renamed "alias-type" keyword to "alias"
Byte! added to definitions
Pointer syntax and declaration improved and extended to support paths with indexes
String! type renamed to c-string!
C-string path accesses now uses byte! values for reading and writing
Added type inference for functions local variables and return value
Function address can be returned using get-word! syntax
New "Nested functions" section added to be completed later
Added byte! datatype, character specific syntax moved from integer! to byte!
Added logic! datatype (boolean values)
Added TRUE and FALSE keywords
Added NOT operator
Old "Logical" operators renamed to the more appropriate "Bitwise" operators
Bitwise ops completed with boolean counterparts
More accurate syntax description for all infix operators
Functions local variables type is now optional if the variables are properly initialized
Added an empty "Type Conversion" to be completed later
Removed functions and macros defined in runtime from keywords, they can be redefined by user code if required
New Possible Evolution: integer! as a platform-specific type
Minor corrections and additions
29/03/2011 - draft 2
Added missing array! datatype proposition in Evolutions
Added missing EXIT and RETURN in reserved keywords list