The T++ Programming Language
Before reading this, it is suggested you familiarise yourself with the compiler
and toolchain environment here.
T++ is a high level programming language for the R3X runtime, it is highly imperative, and is suitable
for almost every task. The language features a relatively simple syntax with support for calling functions from
dynamic libraries (both native and virtual), function calls, strings, memory management and lots which is not
mentioned. Granted, it is a hobby project and hence is not as powerful or good as some of the mainstream languages
like Java or C#, but from a not-so-professional perspective, it is good enough. Considerable effort has been made
to improve the syntax and the overall language, which is still a WIP.
Type System
One very improtant thing to note here is that T++ is techinically typeless, meaning there is *no* type safety, it is
unlikely that type safety is going to be added in the near future.
Keywords
Keywords form the base of any programming language, and T++ is no exception to this rule (of course). The following is the list
of keywords available in the T++ programming language:
-
function function_name(number_of_arguments)
This keyword defines a function, with the name function_name which takes number_of_arguments arguments. Please
note that number_of_arguments should always be a constant. The resulting keyword for ending a function is, endf. Also,
all statements (except preprocessor ones, the "global" and the "struct" keyword) can only be executed inside a function block.
Sample usage is shown below:
function my_function(3)
...
endf
-
struct name_of_struct(number_of_members)
Declare a structure with number_of_members amount of members. The structure can be composed of
int8 (8-bit, 1 byte) or int16 (16-bit, 2 bytes) or int32 (32-bit 4 bytes). Please note that since pointers are 32-bit in R3X,
int32 can be used to store pointers. The resulting keyword to end a structure declaration is ends. Structure declarations
are global and must be done outside the scope of functions.
An example is given below:
...
struct mystruct(3)
int8 member1_8bit
int16 member2_16bit
int32 member3_32bit
ends
...
-
let variable_name = expression
Set a local variable with variable_name to expression. expression can be either a string, integer, character, another variable or an expression.
An example is given below:
...
let a = 5*6
let b = a+5
let c = a+b+27*3
...
-
return expression
Return from a function with a value of expression. T++ expects all functions to have a reachable return.
-
if (expr)
Executes the next statement if expr is true. (usually a goto to a local label). Jumps to next if false. An example is
shown below:
...
if (a := 0) goto a_is_zero
if (a := 5) goto a_is_equal_to_5
if (a > 5) goto a_is_greater_than_5
if (a < ) goto a_is_lesser_than_5
...
:a_is_zero
/* Do something */
:a_is_equal_to_5
/* Dome something */
...
-
while (expr) ... endw
Executes statements sandwiched between while (...) and endw, until expr is true. An example is given below
/* While a is greater than 5, keep executing "a = a - 1"
while (a > 5)
a = a - 1
endw
-
goto label
Jumps to label. An example is given below:
...
goto label01
...
:label01
let a = 5
a = a*45
...
-
gosub label
Calls label. Recursion supported. An example is given below:
...
gosub label01
/* this code will be executed after return */
/* a will be set from 5 to 1 */
let a = 1
:label01
let b = 11*3
let a = 5
/* return value will be trashed */
return 0
...
-
native (function_name, name_of_native_library, number_of_arguments)
Declare a native function, present in a native (.SO/.DLL) library name_of_native_library (absolute path must be given)
with number of arguments as number_of_arguments. This function can then be called normally just like any other function. Note
that this declaration is recommended to be done outside any function block, usually in a header file (*.h). An example
is given below:
...
native(test_func, "./lib.so", 3) // Declare a native function test_func present in lib.so that takes 3 arguments.
...
-
extern (function_name, name_of_dynamic_library, number_of_arguments)
Declare a function in an R3X dynamic library (*.ro) whose location is in name_of_dynamic_library (absolute
path must be given). This works same as "native", the only difference being, that this is used for R3X dynamic libraries,
while "native" is used for native dynamic libraries. An example is given below:
extern (test_func, "./test_func.ro", 3) // Declare a dynamic library function test_func present in ./test_func.ro that takes 3 arguments.
-
global variable_name
Create a global variable, note that you have to set it's value in your initialisation function, global variables can't be initialised in
declaration (yet). An example is given below
...
global myvar
...
Language and syntax
R3X follows an imperative style, along with newlines to differentiate between statements. (with the exception of if).
Function calls
Functions must be defined before they are to be called. There is unfortunately, no support for prototypes (like C) here, yet.
To call a function, the '@' character must precede the function name followed by arguments enclosed within '(' and ')'. An example is given
below:
...
function add2numbers(2)
let number1 = $1
let number2 = $2
return number1 + number2
endf
...
function main(0)
let my_num = @add2numbers(2,5)
// my_num = 7
// the below function call's return value will be trashed, in case it's not needed
@add2numbers(40, 45)
...
endf
...
However, this only supports internal (and native/external dynamic) functions. If you want to call an address, you must use raw_call.
@raw_call(function_address, number_of_arguments, arg1, arg2, arg3, ...).
function_address shall be an expression that equates to the address that needs to be called, number_of_arguments shall be
the number of arguments to be passed, then follows the list of expressions equating to the arguments that need to be passed.
An example is given below:
...
arg2 = 5
arg3 = 28
// call address 0x140000 with 3 arguments
@raw_call(0x140000, 3, 3*5, arg2, arg3+6)
...
Note: All programs must have a main function. Dynamic libraries don't have a main function on the other hand.
Function Arguments
Function arguments can be accessed by prefixing a '$' infront of the number of argument. Arguments are 1-indexed, so this means,
the first argument can be accessed by '$1'. Note that this NOT permitted to be used in expressions. For example:
let a = $1+2
will translate to:
let a = $3
and not
let a = (first_argument_value) + 2
Hence, it is recommended, to rather have this:
function myfunc (2)
let arg1 = $1
let arg2 = $2
...
// Now you can use arg1 and arg2 normally in expressions. like:
let a = arg1 + arg2
...
endf
Pointers
Pointers are variables that point to a section of memory. Accessing Pointers generally refers to accessing the values at these memory
locations pointed by the variables. Since T++ is a typeless language there are no pointer types, this task is rather, accomplished
by specific operators. The following are the operators that can be used:
int8_ptr (expression) - 8-bit pointer access
int16_ptr(expression) - 16-bit pointer access
int32_ptr(expression) - 32-bit pointer access
Several examples are given below, including pointer access and assignment:
...
// set addr4MB to 0x400000
let addr4MB = 0x400000
let whatisat4MB = int8_ptr(whatisat4MB)
// whatisat4MB contains the value of the byte (8-bit) at 0x400000.
let int32_ptr(whatistat4MB+4) = 69
// now 4 bytes after 0x400000 contains the 32-bit integer '69'.
...
Labels
Labels can be defined by prefixing a colon (':'). An example is given below:
...
:label01
...
:label02
...
:label03
...
Keywords like goto and gosub can be used to jump between labels.
Structures and globals
Structures can be accessed by: [struct name_of_struct]expression.member_name
name_of_struct is the structure name which is given during declaration, expression gives out the pointer to the struct,
and member_name is the member name whose value is to be returned. You can use the sizeof(name_of_struct) to return the
size of a structure. An example is given below:
struct mystruct(5)
int8 m1
int8 m2
int32 m3
int16 m4
int32 m5
int8 m6
ends
...
let mystruct_ptr = alloc(sizeof(mystruct))
// set m3 to 5
let [struct mystruct]mystruct_ptr.m3 = 5
// return value of m2.
let mystruct_val_m2 = [struct mystruct]mystruct_ptr.m2
...
Global variables are also accessed in a similar way, using [global]variable_name
variable_name is the name of the variable whose value is to be accessed. An example is given below:
global myvar
...
let [global]myvar = 5
...
let value_of_var = [global]myvar
...
Address of functions or local variables
Address of functions or local variables can be taken by using the addressof(variable_or_function) operator.
variable_or_function refers to the variable/function name whose address is to taken. For functions, they MUST be prefixed with a
'@'. An example is given below:
function func1(6)
...
endf
...
function func2(0)
let x = 0
...
let addressof_x = addressof(x)
let addressof_func1 = addressof(@func1)
...
endf
...
Internal functions
There are some internal functions that are provided by the language, these are:
- print string ; ...
The "print" function will print the string to screen which can be preceded by integers, floats or pointers to strings
(represented by ...). An example is given below:
...
print "value of my_var is"; my_var
print "string at my_var is :"; $my_var // treat my_var as a string
print "float at my_var is: "; %my_var // treat my_var as float
...
- input pointer_to_string, number_of_chars_to_input
The "input" function will take input from the terminal, and put that into pointer_to_string until RETURN is pressed,
however if the input exceeds, number_of_chars_to_input then it will automatically return back to the caller. An example is given below:
...
let ptr_str = alloc(256+1) // extra for null terminator
input ptr_str, 256
// ptr_str contains what the user typed in.
...
- alloc(bytes_to_allocate)
The alloc function allocates pages (as required by bytes_to_allocate) and returns the pointer to the page(s) allocated by the VM.
An example is given below:
// allocate 256 bytes
let alloc_addr = alloc(256)
- end
The end function will quit the program, if called from the main thread. An example is given below:
...
end
...
thread function_name
The 'thread' function dispatches a function as a thread. The function shall take 0 arguments. The following is an example:
...
function mythread(0)
while(true)
/* do something */
endw
endf
...
function main(0)
...
thread mythread
...
endf
NOTE: Never return from a thread function! If you want to quit, simply do: asm "exit".
Floats
T++ supports Floating Point (Decimal) members, operations on them can be done through using special operators. The following is a list
of all floating point operations available.
- add_f(expression1, expression2)
add_f will add two floating point numbers which will be given out by expression1 and expression2 respectively.
- sub_f(expression1, expression2)
sub_f will subtract two floating point numbers which will be given out by expression1 and expression2 respectively.
- mul_f(expression1, expression2)
mul_f will multiply two floating point numbers which will be given out by expression1 and expression2 respectively.
- div_f(expression1, expression2)
div_f will add two floating point numbers which will be given out by expression1 and expression2 respectively.
NOTES:
• Declaration of floating point numbers must be done like this (i.e. have at least 1 digit after the decimal point):
let A = 30.0 // "let A = 30" will set A to an integer, NOT a floating point number.
...
• Integers and Floats CANNOT be MULTIPLIED. To be able to do this, you must use two operators conv_f(expression) (which converts integer to floats)
and conv_i(expression). An example is given below:
let integernum = 30
let floatnum = 50.5
let result = mul_f(floatnum, conv_f(integernum)) // convert integernum to float and multiply
...
let result = integernum * conv_i(floatnum) // convert floatnum to integer and then multiply (decimal points will be lost!)
Operators and Operator precedence
T++ has no operator precedence, all operations which are to be done first must be enclosed in brackets or done sequentially.
The following is a list of all operators present in T++:
- right_shift(expression1, expression2)
Right Shift expression1 by expression2 bits.
- left_shift(expression1, expression2)
Left Shift expression1 by expression2 bits.
- expression1 & expression2
The '&' (Bitwise AND) operator will do a bitwise AND on expression1 by expression2
- expression1 | expression2
The '|' (Bitwise OR) operator will do a bitwise OR on expression1 by expression2
- expression1 ^ expression2
The '^' (Bitwise XOR) operator will do a bitwise XOR on expression1 by expression2
- expression1 && expression2
The '&&' (Logical AND) operator will do a logical AND on expression1 by expression2
- expression1 || expression2
The '&' (Logical OR) operator will do a logical OR on expression1 by expression2
- expression1 != expression2
If expression1 is not equal to expression2, then the expression evaluates to true. Else it evaluates to false.
- expression1 := expression2
If expression1 is equal to expression2, then the expression evaluates to true. Else it evaluates to false.
- expression1 > expression2
If expression1 is greater than expression2, then the expression evaluates to true. Else it evaluates to false.
- expression1 < expression2
If expression1 is lesser than expression2, then the expression evaluates to true. Else it evaluates to false.
- expression1 >= expression2
If expression1 is greater than or equal to expression2, then the expression evaluates to true. Else it evaluates to false.
- expression1 <= expression2
If expression1 is lesser than or equal to expression2, then the expression evaluates to true. Else it evaluates to false.
Escape sequences
T++ now supports escape sequences. Note that for now they can only be used in strings and are very limited, here
is a list of them:
\n Newline (0x0A)
\r Carraige Return (0x0D)
\t TAB Character (0x09)
\\ '\' Character
The Preprocessor
T++ uses GCC as a preprocessor. This means that anything that can be done in the C preprocessor which does not make use of
C language features is accomplishable in T++ as well.