2018-08-06 23:23:34 +00:00
.. _programstructure:
2018-08-06 01:35:43 +00:00
2018-09-15 14:21:05 +00:00
====================
Programming in Prog8
====================
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
This chapter describes a high level overview of the elements that make up a program.
Details about the syntax can be found in the :ref: `syntaxreference` chapter.
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Elements of a program
---------------------
Program
Consists of one or more *modules* .
Module
2018-09-15 14:35:15 +00:00
A file on disk with the `` .p8 `` suffix. It contains *directives* and *code blocks* .
2018-08-06 23:23:34 +00:00
Whitespace and indentation in the source code are arbitrary and can be tabs or spaces or both.
You can also add *comments* to the source code.
One moudule file can *import* others, and also import *library modules* .
Comments
Everything after a semicolon `` ; `` is a comment and is ignored by the compiler.
If the whole line is just a comment, it will be copied into the resulting assembly source code.
This makes it easier to understand and relate the generated code. Examples::
A = 42 ; set the initial value to 42
; next is the code that...
Directive
These are special instructions for the compiler, to change how it processes the code
and what kind of program it creates. A directive is on its own line in the file, and
starts with `` % `` , optionally followed by some arguments.
Code block
A block of actual program code. It defines a *scope* (also known as 'namespace') and
2018-09-15 14:21:05 +00:00
can contain Prog8 *code* , *variable declarations* and *subroutines* .
2018-08-06 23:23:34 +00:00
More details about this below: :ref: `blocks` .
Variable declarations
The data that the code works on is stored in variables ('named values that can change').
The compiler allocates the required memory for them.
There is *no dynamic memory allocation* . The storage size of all variables
is fixed and is determined at compile time.
Variable declarations tend to appear at the top of the code block that uses them.
They define the name and type of the variable, and its initial value.
2018-09-15 14:21:05 +00:00
Prog8 supports a small list of data types, including special 'memory mapped' types
2018-08-06 23:23:34 +00:00
that don't allocate storage but instead point to a fixed location in the address space.
Code
These are the instructions that make up the program's logic. There are different kinds of instructions
('statements' is a better name):
- value assignment
- looping (for, while, repeat, unconditional jumps)
- conditional execution (if - then - else, and conditional jumps)
- subroutine calls
- label definition
Subroutine
2018-09-15 00:12:37 +00:00
Defines a piece of code that can be called by its name from different locations in your code.
2018-10-08 20:35:39 +00:00
It accepts parameters and can return a value (optional).
2018-09-15 00:12:37 +00:00
It can define its own variables, and it is even possible to define subroutines nested inside other subroutines.
Their contents is scoped accordingly.
2018-08-06 23:23:34 +00:00
Label
2018-09-15 00:12:37 +00:00
This is a named position in your code where you can jump to from another place.
You can jump to it with a jump statement elsewhere. It is also possible to use a
subroutine call to a label (but without parameters and return value).
2018-09-19 23:13:21 +00:00
Labels can only be defined in a block or in another subroutine, so you can't define a label
inside a loop statement block for instance.
2018-08-06 23:23:34 +00:00
Scope
Also known as 'namespace', this is a named box around the symbols defined in it.
This prevents name collisions (or 'namespace pollution'), because the name of the scope
is needed as prefix to be able to access the symbols in it.
Anything *inside* the scope can refer to symbols in the same scope without using a prefix.
2018-09-15 14:21:05 +00:00
There are three scopes in Prog8:
2018-08-06 23:23:34 +00:00
- global (no prefix)
- code block
- subroutine
Modules are *not* a scope! Everything defined in a module is merged into the global scope.
.. _blocks:
Blocks, Scopes, and accessing Symbols
-------------------------------------
2018-10-06 15:21:34 +00:00
**Blocks** are the top level separate pieces of code and data of your program. They are combined
2018-08-06 23:23:34 +00:00
into a single output program. No code or data can occur outside a block. Here's an example::
~ main $c000 {
; this is code inside the block...
}
The name of a block must be unique in your entire program.
Also be careful when importing other modules; blocks in your own code cannot have
the same name as a block defined in an imported module or library.
2018-09-07 22:07:25 +00:00
If you omit both the name and address, the entire block is *ignored* by the compiler (and a warning is displayed).
2018-08-06 23:23:34 +00:00
This is a way to quickly "comment out" a piece of code that is unfinshed or may contain errors that you
want to work on later, because the contents of the ignored block are not fully parsed either.
The address can be used to place a block at a specific location in memory.
Usually it is omitted, and the compiler will automatically choose the location (usually immediately after
the previous block in memory).
The address must be >= `` $0200 `` (because `` $00 `` --`` $ff `` is the ZP and `` $100 `` --`` $200 `` is the cpu stack).
2018-10-06 15:21:34 +00:00
**The special "ZP" ZeroPage block**
Blocks named "ZP" are treated a bit differently: they refer to the ZeroPage.
The contents of every block with that name (this one may occur multiple times) are merged into one.
Its start address is always set to `` $04 `` , because `` $00 - $01 `` are used by the hardware
and `` $02 - $03 `` are reserved as general purpose scratch registers.
.. _scopes:
**Scopes**
2018-10-02 23:11:28 +00:00
.. sidebar ::
Scoped access to symbols / "dotted names"
Every symbol is 'public' and can be accessed from elsewhere given its full "dotted name".
So, accessing a variable `` counter `` defined in subroutine `` worker `` in block `` main `` ,
can be done from anywhere by using `` main.worker.counter `` .
2018-10-06 15:21:34 +00:00
*Symbols* are names defined in a certain *scope* . Inside the same scope, you can refer
to them by their 'short' name directly. If the symbol is not found in the same scope,
the enclosing scope is searched for it, and so on, until the symbol is found.
2018-08-06 23:23:34 +00:00
2018-10-06 15:21:34 +00:00
Scopes are created using several statements:
2018-08-06 23:23:34 +00:00
2018-10-06 15:21:34 +00:00
- blocks (top-level named scope)
- subroutines (nested named scopes)
- for, while, repeat loops (anonymous scope)
- if statements and branching conditionals (anonymous scope)
2018-08-06 23:23:34 +00:00
Program Start and Entry Point
-----------------------------
Your program must have a single entry point where code execution begins.
The compiler expects a `` start `` subroutine in the `` main `` block for this,
taking no parameters and having no return value.
2018-09-28 19:28:16 +00:00
.. sidebar ::
60hz IRQ entry point
When running the generated code on the StackVm virtual machine,
it will use the `` irq `` subroutine in the `` irq `` block for the
60hz irq routine. This is optional.
2018-08-06 23:23:34 +00:00
As any subroutine, it has to end with a `` return `` statement (or a `` goto `` call)::
2018-08-08 23:54:43 +00:00
~ main {
2018-09-30 16:49:58 +00:00
sub start () {
2018-08-08 23:54:43 +00:00
; program entrypoint code here
return
}
}
2018-08-06 23:23:34 +00:00
2018-09-28 19:28:16 +00:00
2018-08-06 23:23:34 +00:00
The `` main `` module is always relocated to the start of your programs
address space, and the `` start `` subroutine (the entrypoint) will be on the
first address. This will also be the address that the BASIC loader program (if generated)
calls with the SYS statement.
2018-09-28 19:28:16 +00:00
2018-08-08 23:54:43 +00:00
Variables and values
--------------------
2018-08-06 23:23:34 +00:00
2018-08-08 23:54:43 +00:00
Variables are named values that can change during the execution of the program.
2018-10-06 15:21:34 +00:00
They can be defined inside any scope (blocks, subroutines, for loops, etc.) See :ref: `Scopes <scopes>` .
2018-09-18 21:14:32 +00:00
When declaring a numeric variable it is possible to specify the initial value, if you don't want it to be zero.
For other data types it is required to specify that initial value it should get.
2018-08-08 23:54:43 +00:00
Values will usually be part of an expression or assignment statement::
2018-08-06 23:23:34 +00:00
2018-10-22 22:52:51 +00:00
12345 ; integer number
$aa43 ; hex integer number
2018-12-10 23:09:37 +00:00
%100101 ; binary integer number (% is also remainder operator so be careful)
2018-10-22 22:52:51 +00:00
"Hi, I am a string" ; text string
'a' ; petscii value (byte) for the letter a
-33.456e52 ; floating point number
2018-08-06 23:23:34 +00:00
2018-10-22 22:52:51 +00:00
byte counter = 42 ; variable of size 8 bits, with initial value 42
2018-08-06 01:35:43 +00:00
2018-09-11 22:51:48 +00:00
2018-10-30 19:29:03 +00:00
Array types are also supported. They can be made of bytes, words and floats::
2018-09-11 22:51:48 +00:00
2018-10-01 20:23:16 +00:00
byte[4] array = [1, 2, 3, 4] ; initialize the array
byte[99] array = 255 ; initialize array with all 255's [255, 255, 255, 255, ...]
byte[100] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
value = array[3] ; the fourth value in the array (index is 0-based)
char = string[4] ; the fifth character (=byte) in the string
2018-08-14 14:29:08 +00:00
2018-10-22 22:52:51 +00:00
.. note ::
2018-10-27 20:16:18 +00:00
Right now, the array should be small enough to be indexable by a single byte index.
This means byte arrays should be <= 256 elements, word arrays <= 128 elements, and float
arrays <= 51 elements. This limit may or may not be lifted in a future version.
2018-10-22 22:52:51 +00:00
2018-12-31 00:52:18 +00:00
You can split an array initializer list over several lines if you want.
2018-08-06 01:35:43 +00:00
2018-08-13 08:51:05 +00:00
Note that the various keywords for the data type and variable type (`` byte `` , `` word `` , `` const `` , etc.)
2018-10-22 22:52:51 +00:00
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name `` byte ``
2018-08-13 08:51:05 +00:00
for instance.
2018-09-06 19:13:49 +00:00
.. todo ::
There must be a way to tell the compiler which variables you require to be in Zeropage:
`` zeropage `` modifier keyword on vardecl perhaps?
2018-09-04 21:37:21 +00:00
2018-09-02 09:54:42 +00:00
Variables that represent CPU hardware registers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following variables are reserved
2018-11-02 21:06:57 +00:00
and map directly (read/write) to a CPU hardware register: `` A `` , `` X `` , `` Y `` .
2018-09-02 09:54:42 +00:00
2018-08-13 08:51:05 +00:00
2018-08-08 23:54:43 +00:00
Special types: const and memory-mapped
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When using `` const `` , the value of the 'variable' can no longer be changed.
You'll have to specify the initial value expression. This value is then used
by the compiler everywhere you refer to the constant (and no storage is allocated
2018-10-07 00:05:26 +00:00
for the constant itself). This is only valid for the simple numeric types (byte, word, float).
2018-08-08 23:54:43 +00:00
When using `` memory `` , the variable will point to specific location in memory,
rather than being newly allocated. The initial value (mandatory) must be a valid
memory address. Reading the variable will read the given data type from the
address you specified, and setting the varible will directly modify that memory location(s)::
const byte max_age = 2000 - 1974 ; max_age will be the constant value 26
memory word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
2018-09-06 19:13:49 +00:00
.. note ::
Directly accessing random memory locations is not yet supported without the
intermediate step of declaring a memory-mapped variable for the memory location.
The advantages of this however, is that it's clearer what the memory location
stands for, and the compiler also knows the data type.
2018-08-06 23:23:34 +00:00
Integers
^^^^^^^^
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Integers are 8 or 16 bit numbers and can be written in normal decimal notation,
in hexadecimal and in binary notation.
2018-09-30 21:07:26 +00:00
A single character in single quotes such as `` 'a' `` is translated into a byte integer,
which is the Petscii value for that character.
2018-08-06 01:35:43 +00:00
2018-10-12 16:01:40 +00:00
Unsigned integers are in the range 0-255 for unsigned byte types, and 0-65535 for unsigned word types.
The signed integers integers are in the range -128..127 for bytes,
and -32768..32767 for words.
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Strings
^^^^^^^
2018-08-06 01:35:43 +00:00
2018-09-15 21:56:56 +00:00
Strings are a sequence of characters enclosed in `` " `` quotes. The length is limited to 255 characters.
2018-08-06 23:23:34 +00:00
They're stored and treated much the same as a byte array,
but they have some special properties because they are considered to be *text* .
Strings in your source code files will be encoded (translated from ASCII/UTF-8) into either CBM PETSCII or C-64 screencodes.
PETSCII is the default choice. If you need screencodes (also called 'poke' codes) instead,
you have to use the `` str_s `` variants of the string type identifier.
2018-09-15 21:56:56 +00:00
.. caution ::
It's probably best that you don't change strings after they're created.
This is because if your program exits and is restarted (without loading it again),
it will then operate on the changed strings instead of the original ones.
2018-10-30 19:29:03 +00:00
The same is true for arrays by the way.
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Floating point numbers
^^^^^^^^^^^^^^^^^^^^^^
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Floats are stored in the 5-byte 'MFLPT' format that is used on CBM machines,
2019-01-05 15:09:05 +00:00
and currently all floating point operations are specific to the Commodore-64.
2018-08-06 23:23:34 +00:00
This is because routines in the C-64 BASIC and KERNAL ROMs are used for that.
So floating point operations will only work if the C-64 BASIC ROM (and KERNAL ROM)
2019-01-05 15:09:05 +00:00
are banked in.
Also your code needs to import the `` c64flt `` library to enable floating point support
in the compiler, and to gain access to the floating point routines.
(this library contains the directive to enable floating points, you don't have
to worry about this yourself)
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
The largest 5-byte MFLPT float that can be stored is: **1.7014118345e+38** (negative: **-1.7014118345e+38** )
2018-12-30 20:40:27 +00:00
Converting types into other types
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Sometimes you need an unsigned word where you have an unsigned byte, or you need some other type conversion.
Many type conversions are possible by just writing `` as <type> `` at the end of an expression::
uword uw = $ea31
ubyte ub = uw as ubyte ; ub will be $31, identical to lsb(uw)
float f = uw as float ; f will be 59953, but this conversion can be omitted in this case
word w = uw as word ; w will be -5583 (simply reinterpret $ea31 as 2-complement negative number)
f = 56.777
ub = f as ubyte ; ub will be 56
Sometimes it is a straight 'type cast' where the value is simply interpreted as being of the other type,
sometimes an actual value conversion is done to convert it into the targe type.
Try to avoid type conversions as much as possible.
2018-08-06 23:23:34 +00:00
Initial values across multiple runs of the program
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2018-10-06 22:21:13 +00:00
When declaring values with an initial value, this value will be set into the variable each time
the program reaches the declaration again. This can be in loops, multiple subroutine calls,
2019-01-08 01:25:22 +00:00
or even multiple invocations of the entire program. If you omit an initial value, it will
be set to zero only for the first run of the program. A second run will utilize the last value
where it left off (but your code will be a bit smaller because no initialization instructions
are generated)
.. warning ::
this behavior may change in a future version so that subsequent runs always
use the same initial values
2018-10-06 22:21:13 +00:00
2018-11-22 23:31:21 +00:00
This only works for simple types, *and not for string variables and arrays* .
2018-10-06 22:21:13 +00:00
It is assumed these are left unchanged by the program.
If you do modify them in-place, you should take care yourself that they work as
expected when the program is restarted.
(This is an optimization choice to avoid having to store two copies of every string and array)
2018-08-06 23:23:34 +00:00
Loops
-----
2018-09-18 21:14:32 +00:00
The *for* -loop is used to let a variable (or register) iterate over a range of values. Iteration is done in steps of 1, but you can change this.
2018-10-06 20:54:03 +00:00
The loop variable can be declared as byte or word earlier so you can reuse it for multiple occasions,
or you can declare one directly in the for statement which will only be visible in the for loop body.
Iterating with a floating point variable is not supported. If you want to loop over a floating-point array, use a loop with an integer index variable instead.
2018-09-18 21:14:32 +00:00
2018-08-06 23:23:34 +00:00
The *while* -loop is used to repeat a piece of code while a certain condition is still true.
The *repeat--until* loop is used to repeat a piece of code until a certain condition is true.
2018-08-14 12:33:36 +00:00
You can also create loops by using the `` goto `` statement, but this should usually be avoided.
2018-08-06 23:23:34 +00:00
2018-09-19 23:13:21 +00:00
.. attention ::
The value of the loop variable or register after executing the loop *is undefined* . Don't use it immediately
after the loop without first assigning a new value to it!
(this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value)
2018-10-22 22:52:51 +00:00
Loop variables that are declared inline are scoped in the loop body so they're not accessible at all after the loop finishes.
2018-09-19 23:13:21 +00:00
2018-08-06 23:23:34 +00:00
Conditional Execution
2018-08-06 01:35:43 +00:00
---------------------
2018-08-06 23:23:34 +00:00
Conditional execution means that the flow of execution changes based on certiain conditions,
2018-08-14 00:22:59 +00:00
rather than having fixed gotos or subroutine calls::
2018-08-06 23:23:34 +00:00
2018-09-23 00:04:45 +00:00
if A>4 goto overflow
2018-08-06 23:23:34 +00:00
2018-09-23 00:04:45 +00:00
if X==3 Y = 4
if X==3 Y = 4 else A = 2
2018-08-14 00:22:59 +00:00
2018-09-23 00:04:45 +00:00
if X==5 {
2018-08-14 00:22:59 +00:00
Y = 99
} else {
A = 3
}
2018-08-06 23:23:34 +00:00
2018-09-23 00:04:45 +00:00
Conditional jumps (`` if condition goto label `` ) are compiled using 6502's branching instructions (such as `` bne `` and `` bcc `` ) so
2018-08-06 23:23:34 +00:00
the rather strict limit on how *far* it can jump applies. The compiler itself can't figure this
out unfortunately, so it is entirely possible to create code that cannot be assembled successfully.
You'll have to restructure your gotos in the code (place target labels closer to the branch)
if you run into this type of assembler error.
2018-09-02 16:32:48 +00:00
There is a special form of the if-statement that immediately translates into one of the 6502's branching instructions.
This allows you to write a conditional jump or block execution directly acting on the current values of the CPU's status register bits.
2018-09-21 22:33:25 +00:00
The eight branching instructions of the CPU each have an if-equivalent (and there are some easier to understand aliases):
====================== =====================
condition meaning
====================== =====================
`` if_cs `` if carry status is set
`` if_cc `` if carry status is clear
`` if_vs `` if overflow status is set
`` if_vc `` if overflow status is clear
`` if_eq `` / `` if_z `` if result is equal to zero
`` if_ne `` / `` if_nz `` if result is not equal to zero
`` if_pl `` / `` if_pos `` if result is 'plus' (>= zero)
`` if_mi `` / `` if_neg `` if result is 'minus' (< zero)
====================== =====================
2018-09-02 16:32:48 +00:00
So `` if_cc goto target `` will directly translate into the single CPU instruction `` BCC target `` .
2018-09-15 21:56:56 +00:00
.. note ::
For now, the symbols used or declared in the statement block(s) are shared with
the same scope the if statement itself is in.
Maybe in the future this will be a separate nested scope, but for now, that is
only possible when defining a subroutine.
2018-08-06 23:23:34 +00:00
Assignments
-----------
Assignment statements assign a single value to a target variable or memory location.
Augmented assignments (such as `` A += X `` ) are also available, but these are just shorthands
for normal assignments (`` A = A + X `` ).
2018-09-27 21:21:04 +00:00
Only register variables and variables of type byte, word and float can be assigned a new value.
It's not possible to set a new value to string or array variables etc, because they get allocated
a fixed amount of memory which will not change.
2018-09-24 20:34:12 +00:00
.. attention ::
**Data type conversion (in assignments):**
When assigning a value with a 'smaller' datatype to a register or variable with a 'larger' datatype,
the value will be automatically converted to the target datatype: byte --> word --> float.
So assigning a byte to a word variable, or a word to a floating point variable, is fine.
The reverse is *not* true: it is *not* possible to assign a value of a 'larger' datatype to
a variable of a smaller datatype without an explicit conversion. Otherwise you'll get an error telling you
that there is a loss of precision. You can use builtin functions such as `` round `` and `` lsb `` to convert
2018-09-25 17:52:59 +00:00
to a smaller datatype, or revert to integer arithmetic.
2018-08-06 23:23:34 +00:00
2018-12-30 20:40:27 +00:00
Direct access to memory locations
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Normally memory locations are accessed by a *memory mapped* name, such as `` c64.BGCOL0 `` that is defined
as the memory mapped address $d021.
If you want to access a memory location directly (by using the address itself), without defining
2018-12-31 03:48:26 +00:00
a memory mapped location, you can do so by enclosing the address in `` @(...) `` ::
2018-12-30 20:40:27 +00:00
2018-12-31 03:48:26 +00:00
A = @($d020) ; set the A register to the current c64 screen border color ("peek(53280)")
@($d020) = 0 ; set the c64 screen border to black ("poke 53280,0")
@(vic+$20) = 6 ; you can also use expressions to 'calculate' the address
2018-12-30 20:40:27 +00:00
2018-08-06 23:23:34 +00:00
Expressions
-----------
2018-09-03 21:19:25 +00:00
In most places where a number or other value is expected, you can use just the number, or a constant expression.
2018-09-24 20:34:12 +00:00
If possible, the expression is parsed and evaluated by the compiler itself at compile time, and the (constant) resulting value is used in its place.
Expressions that cannot be compile-time evaluated will result in code that calculates them at runtime.
2018-09-03 21:19:25 +00:00
Expressions can contain procedure and function calls.
There are various built-in functions such as sin(), cos(), min(), max() that can be used in expressions (see :ref: `builtinfunctions` ).
You can also reference idendifiers defined elsewhere in your code.
2018-09-24 20:34:12 +00:00
.. attention ::
2018-09-25 17:52:59 +00:00
**Data type conversion (during calculations) and floating point handling:**
2018-09-24 20:34:12 +00:00
BYTE values used in arithmetic expressions (calculations) will be automatically converted into WORD values
if the calculation needs that to store the resulting value. Once a WORD value is used, all other results will be WORDs as well
(there's no automatic conversion of WORD into BYTE).
2018-09-25 17:52:59 +00:00
When a floating point value is used in a calculation, the result will be a floating point, and byte or word values
will be automatically converted into floats in this case. The compiler will issue a warning though when this happens, because floating
point calculations are very slow and possibly unintended!
2018-12-20 22:28:03 +00:00
Calculations with integer variables will not result in floating point values.
if you divide two integer variables say 32500 and 99 the result will be the integer floor
2018-09-24 20:34:12 +00:00
division (328) rather than the floating point result (328.2828282828283). If you need the full precision,
2018-12-20 22:28:03 +00:00
you'll have to make sure at least the first operand is a floating point. You can do this by
using a floating point value or variable, or use a type cast.
When the compiler can calculate the result during compile-time, it will try to avoid loss
of precision though and gives an error if you may be losing a floating point result.
2018-12-20 01:52:33 +00:00
2018-08-06 23:23:34 +00:00
2018-08-07 20:49:01 +00:00
Arithmetic and Logical expressions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Arithmetic expressions are expressions that calculate a numeric result (integer or floating point).
Many common arithmetic operators can be used and follow the regular precedence rules.
2018-09-24 20:34:12 +00:00
Logical expressions are expressions that calculate a boolean result: true or false
(which in reality are just a 1 or 0 integer value).
2018-08-06 23:23:34 +00:00
2018-08-07 20:49:01 +00:00
You can use parentheses to group parts of an expresion to change the precedence.
Usually the normal precedence rules apply (`` * `` goes before `` + `` etc.) but subexpressions
within parentheses will be evaluated first. So `` (4 + 8) * 2 `` is 24 and not 20,
and `` (true or false) and false `` is false instead of true.
2018-08-06 23:23:34 +00:00
Subroutines
-----------
Defining a subroutine
^^^^^^^^^^^^^^^^^^^^^
Subroutines are parts of the code that can be repeatedly invoked using a subroutine call from elsewhere.
2018-10-08 20:35:39 +00:00
Their definition, using the `` sub `` statement, includes the specification of the required parameters and return value.
2018-09-15 00:12:37 +00:00
Subroutines can be defined in a Block, but also nested inside another subroutine. Everything is scoped accordingly.
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Calling a subroutine
^^^^^^^^^^^^^^^^^^^^
2018-08-06 01:35:43 +00:00
2018-09-30 16:49:58 +00:00
The arguments in parentheses after the function name, should match the parameters in the subroutine definition.
2018-10-08 20:35:39 +00:00
It is possible to not store the return value but the compiler
2018-09-30 16:49:58 +00:00
will issue a warning then telling you the result values of a subroutine call are discarded.
2018-08-13 23:15:11 +00:00
2018-09-03 21:19:25 +00:00
.. caution ::
Note that *recursive* subroutine calls are not supported at this time.
If you do need a recursive algorithm, you'll have to hand code it in embedded assembly for now,
or rewrite it into an iterative algorithm.
.. _builtinfunctions:
2018-08-13 23:15:11 +00:00
Built-in Functions
------------------
2018-09-02 09:54:42 +00:00
There's a set of predefined functions in the language. These are fixed and can't be redefined in user code.
You can use them in expressions and the compiler will evaluate them at compile-time if possible.
sin(x)
2019-01-04 21:11:46 +00:00
Sine. (floating point version)
2018-08-13 23:15:11 +00:00
2018-09-02 09:54:42 +00:00
cos(x)
2019-01-04 21:11:46 +00:00
Cosine. (floating point version)
2019-01-06 21:22:12 +00:00
sin8u(x)
Fast 8-bit ubyte sine of angle 0..255, result is in range 0..255
2019-01-04 21:11:46 +00:00
sin8(x)
2019-01-06 21:22:12 +00:00
Fast 8-bit byte sine of angle 0..255, result is in range -127..127
sin16u(x)
Fast 16-bit uword sine of angle 0..255, result is in range 0..65535
2019-01-04 21:11:46 +00:00
sin16(x)
2019-01-06 21:22:12 +00:00
Fast 16-bit word sine of angle 0..255, result is in range -32767..32767
cos8u(x)
Fast 8-bit ubyte cosine of angle 0..255, result is in range 0..255
2019-01-04 21:11:46 +00:00
cos8(x)
2019-01-06 21:22:12 +00:00
Fast 8-bit byte cosine of angle 0..255, result is in range -127..127
cos16u(x)
Fast 16-bit uword cosine of angle 0..255, result is in range 0..65535
2019-01-04 21:11:46 +00:00
cos16(x)
2019-01-06 21:22:12 +00:00
Fast 16-bit word cosine of angle 0..255, result is in range -32767..32767
2018-08-13 23:15:11 +00:00
2018-09-02 09:54:42 +00:00
abs(x)
2018-08-13 23:15:11 +00:00
Absolute value.
2018-09-02 09:54:42 +00:00
tan(x)
2018-08-13 23:15:11 +00:00
Tangent.
2018-09-02 09:54:42 +00:00
atan(x)
2018-08-13 23:15:11 +00:00
Arctangent.
2018-09-17 22:31:11 +00:00
ln(x)
2018-12-18 01:02:19 +00:00
Natural logarithm (base e).
2018-09-17 22:31:11 +00:00
log2(x)
Base 2 logarithm.
2018-08-13 23:15:11 +00:00
2018-09-02 09:54:42 +00:00
sqrt(x)
2018-08-13 23:15:11 +00:00
Square root.
2018-09-04 21:37:21 +00:00
round(x)
Rounds the floating point to the closest integer.
2018-08-13 23:15:11 +00:00
2018-09-04 21:37:21 +00:00
floor (x)
Rounds the floating point down to an integer towards minus infinity.
2018-08-13 23:15:11 +00:00
2018-09-04 21:37:21 +00:00
ceil(x)
Rounds the floating point up to an integer towards positive infinity.
2018-08-13 23:15:11 +00:00
2018-09-02 09:54:42 +00:00
rad(x)
2018-08-13 23:15:11 +00:00
Degrees to radians.
2018-09-02 09:54:42 +00:00
deg(x)
2018-08-13 23:15:11 +00:00
Radians to degrees.
2018-09-02 09:54:42 +00:00
2018-09-04 21:37:21 +00:00
max(x)
2018-10-30 19:29:03 +00:00
Maximum of the values in the array value x
2018-09-04 21:37:21 +00:00
min(x)
2018-10-30 19:29:03 +00:00
Minimum of the values in the array value x
2018-09-04 21:37:21 +00:00
avg(x)
2018-10-30 19:29:03 +00:00
Average of the values in the array value x
2018-09-04 21:37:21 +00:00
sum(x)
2018-10-30 19:29:03 +00:00
Sum of the values in the array value x
2018-09-04 21:37:21 +00:00
len(x)
2018-10-30 19:29:03 +00:00
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
2018-09-14 23:14:48 +00:00
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte.
2018-12-24 23:33:04 +00:00
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
length of the string during execution, the value of len(string) may no longer be correct!
2018-09-04 21:37:21 +00:00
2018-09-06 19:13:49 +00:00
lsb(x)
2018-12-20 01:52:33 +00:00
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
2018-09-06 19:13:49 +00:00
msb(x)
Get the most significant byte of the word x.
2019-01-09 00:03:33 +00:00
mkword(lsb, msb)
Efficiently create a word value from two bytes (the lsb and the msb). Avoids multiplication and shifting.
2018-09-04 21:37:21 +00:00
any(x)
2018-10-30 19:29:03 +00:00
1 ('true') if any of the values in the array value x is 'true' (not zero), else 0 ('false')
2018-09-04 21:37:21 +00:00
all(x)
2018-10-30 19:29:03 +00:00
1 ('true') if all of the values in the array value x are 'true' (not zero), else 0 ('false')
2018-09-04 21:37:21 +00:00
2018-09-15 16:43:23 +00:00
rnd()
returns a pseudo-random byte from 0..255
rndw()
returns a pseudo-random word from 0..65535
rndf()
returns a pseudo-random float between 0.0 and 1.0
2018-09-04 21:37:21 +00:00
lsl(x)
2018-09-02 09:54:42 +00:00
Shift the bits in x (byte or word) one position to the left.
Bit 0 is set to 0 (and the highest bit is shifted into the status register's Carry flag)
2018-09-24 20:34:12 +00:00
Modifies in-place, doesn't return a value (so can't be used in an expression).
2018-09-02 09:54:42 +00:00
2018-09-04 21:37:21 +00:00
lsr(x)
2018-09-02 09:54:42 +00:00
Shift the bits in x (byte or word) one position to the right.
The highest bit is set to 0 (and bit 0 is shifted into the status register's Carry flag)
2018-09-24 20:34:12 +00:00
Modifies in-place, doesn't return a value (so can't be used in an expression).
2018-09-02 09:54:42 +00:00
2018-09-04 21:37:21 +00:00
rol(x)
2018-09-02 09:54:42 +00:00
Rotate the bits in x (byte or word) one position to the left.
This uses the CPU's rotate semantics: bit 0 will be set to the current value of the Carry flag,
while the highest bit will become the new Carry flag value.
(essentially, it is a 9-bit or 17-bit rotation)
Modifies in-place, doesn't return a value (so can't be used in an expression).
2018-09-04 21:37:21 +00:00
rol2(x)
2018-09-02 09:54:42 +00:00
Like _rol but now as 8-bit or 16-bit rotation.
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
2018-09-04 21:37:21 +00:00
ror(x)
2018-09-02 09:54:42 +00:00
Rotate the bits in x (byte or word) one position to the right.
This uses the CPU's rotate semantics: the highest bit will be set to the current value of the Carry flag,
while bit 0 will become the new Carry flag value.
(essentially, it is a 9-bit or 17-bit rotation)
Modifies in-place, doesn't return a value (so can't be used in an expression).
2018-09-04 21:37:21 +00:00
ror2(x)
2018-09-02 09:54:42 +00:00
Like _ror but now as 8-bit or 16-bit rotation.
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
2019-01-01 17:38:43 +00:00
memcopy(from, to, numbytes)
Efficiently copy a number of bytes (1 - 256) from a memory location to another.
NOTE: 'to' must NOT overlap with 'from', unless it is *before* 'from'.
Because this function imposes some overhead to handle the parameters,
it is only faster if the number of bytes is larger than a certain threshold.
Compare the generated code to see if it was beneficial or not.
2018-09-24 21:38:33 +00:00
set_carry() / clear_carry()
2018-09-02 09:54:42 +00:00
Set (or clear) the CPU status register Carry flag. No result value.
(translated into `` SEC `` or `` CLC `` cpu instruction)
2019-01-01 16:55:01 +00:00
set_irqd() / clear_irqd()
2018-09-02 09:54:42 +00:00
Set (or clear) the CPU status register Interrupt Disable flag. No result value.
(translated into `` SEI `` or `` CLI `` cpu instruction)
2018-10-14 20:28:16 +00:00
rsave()
Saves the CPU registers and the status flags.
You can now more or less 'safely' use the registers directly, until you
restore them again so the generated code can carry on normally.
2018-12-09 18:48:59 +00:00
Note: it's not needed to rsave() before an asm subroutine that clobbers the X register
(which is used as the internal evaluation stack pointer).
The compiler will take care of this situation automatically.
2018-10-14 20:28:16 +00:00
rrestore()
Restores the CPU registers and the status flags from previously saved values.