prog8/docs/source/syntaxreference.rst

483 lines
16 KiB
ReStructuredText
Raw Normal View History

2018-08-06 01:35:43 +00:00
.. _syntaxreference:
================
Syntax Reference
================
Module file
-----------
This is a file with the ``.ill`` suffix, containing *directives* and *code blocks*, described below.
The file is a text file wich can also contain:
2018-08-06 23:23:34 +00:00
Lines, whitespace, indentation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Line endings are significant because *only one* declaration, statement or other instruction can occur on every line.
Other whitespace and line indentation is arbitrary and ignored by the compiler.
You can use tabs or spaces as you wish.
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Source code comments
^^^^^^^^^^^^^^^^^^^^
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Everything after a semicolon ``;`` is a comment and is ignored.
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::
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
A = 42 ; set the initial value to 42
; next is the code that...
2018-08-06 01:35:43 +00:00
.. _directives:
Directives
-----------
.. data:: %output <type>
Level: module.
Global setting, selects program output type. Default is ``prg``.
- type ``raw`` : no header at all, just the raw machine code data
- type ``prg`` : C64 program (with load address header)
.. data:: %launcher <type>
Level: module.
Global setting, selects the program launcher stub to use.
Only relevant when using the ``prg`` output type. Defaults to ``basic``.
- type ``basic`` : add a tiny C64 BASIC program, whith a SYS statement calling into the machine code
- type ``none`` : no launcher logic is added at all
.. data:: %zp <style>
Level: module.
Global setting, select ZeroPage handling style. Defaults to ``compatible``.
- style ``compatible`` -- only use the few 'free' addresses in the ZP, and don't change anything else.
This allows full use of BASIC and KERNAL ROM routines including default IRQs during normal system operation.
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
except the few addresses mentioned above that are used by the system's IRQ routine.
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
It is also not possible to cleanly exit the program, other than resetting the machine.
This option makes programs smaller and faster because many more variables can
be stored in the ZP, which is more efficient.
- style ``full-restore`` -- like ``full``, but makes a backup copy of the original values at program start.
These are restored (except for the software jiffy clock in ``$a0``--``$a2``)
when the program exits, and allows it to exit back to the BASIC prompt.
Also read :ref:`zeropage`.
.. data:: %address <address>
Level: module.
Global setting, set the program's start memory address
- default for ``raw`` output is ``$c000``
- default for ``prg`` output is ``$0801``
- cannot be changed if you select ``prg`` with a ``basic`` launcher;
then it is always ``$081d`` (immediately after the BASIC program), and the BASIC program itself is always at ``$0801``.
This is because the C64 expects BASIC programs to start at this address.
.. data:: %import <name>
Level: module, block.
This reads and compiles the named module source file as part of your current program.
Symbols from the imported module become available in your code,
without a module or filename prefix.
You can import modules one at a time, and importing a module more than once has no effect.
.. data:: %saveregisters
Level: block.
@todo
.. data:: %noreturn
Level: block, subroutine.
@todo
.. data:: %asmbinary "<filename>" [, <offset>[, <length>]]
Level: block.
This directive can only be used inside a block.
The assembler will include the file as binary bytes at this point, il65 will not process this at all.
The optional offset and length can be used to select a particular piece of the file.
.. data:: %asminclude "<filename>", scopelabel
Level: block.
This directive can only be used inside a block.
The assembler will include the file as raw assembly source text at this point, il65 will not process this at all.
The scopelabel will be used as a prefix to access the labels from the included source code,
otherwise you would risk symbol redefinitions or duplications.
.. data:: %breakpoint
Level: block, subroutine.
Defines a debugging breakpoint at this location. See :ref:`debugging`
.. data:: %asm { ... }
Level: block, subroutine.
Declares that there is *inline assembly code* in the lines enclosed by the curly braces.
This code will be written as-is into the generated output file.
The assembler syntax used should be for the 3rd party cross assembler tool that IL65 uses.
The ``%asm {`` and ``}`` start and end markers each have to be on their own unique line.
Identifiers
-----------
2018-08-08 23:54:43 +00:00
Naming things in IL65 is done via valid *identifiers*. They start with a letter or underscore,
and after that, a combination of letters, numbers, or underscores. Examples of valid identifiers::
2018-08-06 01:35:43 +00:00
a
A
monkey
COUNTER
Better_Name_2
2018-08-08 23:54:43 +00:00
_something_strange_
2018-08-06 01:35:43 +00:00
Code blocks
-----------
A named block of actual program code. Itefines a *scope* (also known as 'namespace') and
can contain IL65 *code*, *directives*, *variable declarations* and *subroutines*::
~ <blockname> [<address>] {
<directives>
<variables>
<statements>
<subroutines>
}
The <blockname> must be a valid identifier or can be completely omitted.
In that case the <address> is required to tell the compiler to put the block at
a certain position in memory. Otherwise it would be impossible to access its contents.
The <address> is optional. It must be a valid memory address such as ``$c000``.
Also read :ref:`blocks`. Here is an example of a code block, to be loaded at ``$c000``::
~ main $c000 {
; this is code inside the block...
}
2018-08-06 23:23:34 +00:00
Labels
------
To label a position in your code where you can jump to from another place, you use a label::
nice_place:
; code ...
It's just an identifier followed by a colon ``:``. It's allowed to put the next statement on
the same line, after the label.
2018-08-06 01:35:43 +00:00
Variables and value literals
----------------------------
The data that the code works on is stored in variables. Variable names have to be valid identifiers.
Values in the source code are written using *value literals*. In the table of the supported
data types below you can see how they should be written.
Variable declarations
^^^^^^^^^^^^^^^^^^^^^
2018-08-06 23:23:34 +00:00
Variables should be declared with their exact type and size so the compiler can allocate storage
for them. You can give them an initial value as well. That value can be a simple literal value,
but you can put a (constant) expression there as well. The syntax is::
<datatype> <variable name> [ = <initial value> ]
Various examples::
word thing
byte counter = 0
byte age = 2018 - 1974
float wallet = 55.25
str name = "my name is Irmen"
word address = #counter
byte[5] values = [11, 22, 33, 44, 55]
byte[5][6] empty_matrix
2018-08-06 01:35:43 +00:00
Data types
^^^^^^^^^^
IL65 supports the following data types:
=============== ======================= ================= =========================================
type identifier type storage size example var declaration and literal value
=============== ======================= ================= =========================================
``byte`` unsigned byte 1 byte = 8 bits ``byte myvar = $8f``
-- boolean 1 byte = 8 bits ``byte myvar = true`` or ``byte myvar == false``
The true and false are actually just aliases
for the byte values 1 and 0.
``word`` unsigned word 2 bytes = 16 bits ``word myvar = $8fee``
``float`` floating-point 5 bytes = 40 bits ``float myvar = 1.2345``
stored in 5-byte cbm MFLPT format
``byte[x]`` unsigned byte array x bytes ``byte[4] myvar = [1, 2, 3, 4]``
``word[x]`` unsigned word array 2*x bytes ``word[4] myvar = [1, 2, 3, 4]``
``byte[x,y]`` unsigned byte matrix x*y bytes ``byte[40,25] myvar = @todo``
word-matrix not supported
2018-08-06 01:35:43 +00:00
``str`` string (petscii) varies ``str myvar = "hello."``
implicitly terminated by a 0-byte
``str_p`` pascal-string (petscii) varies ``str_p myvar = "hello."``
implicit first byte = length, no 0-byte
``str_s`` string (screencodes) varies ``str_s myvar = "hello."``
implicitly terminated by a 0-byte
``str_ps`` pascal-string varies ``str_ps myvar = "hello."``
(screencodes) implicit first byte = length, no 0-byte
=============== ======================= ================= =========================================
2018-08-06 23:23:34 +00:00
**@todo pointers/addresses? (as opposed to normal WORDs)**
2018-08-06 23:23:34 +00:00
**@todo signed integers (byte and word)?**
2018-08-06 01:35:43 +00:00
2018-08-08 23:54:43 +00:00
Memory mapped variables
^^^^^^^^^^^^^^^^^^^^^^^
The ``memory`` keyword is used in front of a data type keyword, to say that no storage
should be allocated by the compiler. Instead, the value assigned to the variable (mandatory now!)
should be the *memory address* where the value is located::
memory byte BORDER = $d020
Constants
^^^^^^^^^
All variables can be assigned new values unless you use the ``const`` keyword.
The initial value will now be evaluated at compile time (it must be a compile time constant expression)
and no storage is allocated for the constant::
const byte max_age = 99
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Reserved names
^^^^^^^^^^^^^^
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
The following names are reserved, they have a special meaning::
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
A X Y ; 6502 hardware registers
AX AY XY ; 16-bit pseudo register pairs
SC SI SZ ; bits of the 6502 hardware status register
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Operators
---------
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
.. data:: # (address-of)
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Takes the address of the symbol following it: ``word address = #somevar``
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
.. data:: + - * / // ** % (arithmetic)
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
``+``, ``-``, ``*``, ``/`` are the familiar arithmetic operations.
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
``//`` means *integer division* even when the operands are floating point values: ``9.5 // 2.5`` is 3 (and not 3.8)
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
``**`` is the power operator: ``3 ** 5`` is equal to 3*3*3*3*3 and is 243.
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
``%`` is the modulo operator: ``25 % 7`` is 4.
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
.. data:: << >> <<@ >>@ & | ^ ~ (bitwise arithmetic)
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
``<<`` and ``>>`` are bitwise shifts (left and right), ``<<@`` and ``@>>`` are bitwise rotations (left and right)
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
``&`` is bitwise and, ``|`` is bitwise or, ``^`` is bitwise xor, ``~`` is bitwise invert (this one is an unary operator)
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
.. data:: = (assignment)
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Sets the target on the LHS (left hand side) of the operator to the value of the expression on the RHS (right hand side).
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
.. data:: += -= *= /= //= **= <<= >>= <<@= >>@= &= |= ^= (augmented assignment)
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Syntactic sugar; ``A += X`` is equivalent to ``A = A + X``
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
.. data:: ++ -- (postfix increment and decrement)
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Syntactic sugar; ``A++`` is equivalent to ``A = A + 1``, and ``A--`` is equivalent to ``A = A - 1``.
Because these operations are so common, we have these short forms.
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
.. data:: == != < > <= >= (comparison)
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
Equality, Inequality, Less-than, Greater-than, Less-or-Equal-than, Greater-or-Equal-than comparisons.
The result is a 'boolean' value 'true' or 'false' (which in reality is just a byte value of 1 or 0).
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
.. data:: not and or xor (logical)
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
These operators are the usual logical operations that are part of a logical expression to reason
about truths (boolean values). The result of such an expression is a 'boolean' value 'true' or 'false'
(which in reality is just a byte value of 1 or 0).
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
.. data:: .. (range creation)
Creates a range of values from the LHS value to the RHS value, inclusive.
These are mainly used in for loops to set the loop range. Example::
0 .. 7 ; range of values 0, 1, 2, 3, 4, 5, 6, 7 (constant)
A = 5
X = 10
A .. X ; range of 5, 6, 7, 8, 9, 10
byte[4] array = 10 .. 13 ; sets the array to [1, 2, 3, 4]
for i in 0 .. 127 {
; i loops 0, 1, 2, ... 127
}
.. data:: [ ... ] (array indexing)
When put after a sequence type (array, string or matrix) it means to point to the given element in that sequence::
array[2] ; the third byte in the array (index is 0-based)
matrix[4,2] ; the byte at the 5th column and 3rd row in the matrix
.. data:: ( ... ) (precedence grouping in expressions, or subroutine parameter list)
Parentheses are used to group parts of an expression to change the order of evaluation.
(the subexpression inside the parentheses will be evaluated first):
``(4 + 8) * 2`` is 24 instead of 20.
2018-08-06 23:23:34 +00:00
Parentheses are also used in a subroutine call, they follow the name of the subroutine and contain
the list of arguments to pass to the subroutine: ``big_function(1, 99)``
Subroutine calls
----------------
You call a subroutine like this::
[ result = ] subroutinename_or_address ( [argument...] )
; example:
outputvar1, outputvar2 = subroutine ( arg1, arg2, arg3 )
Arguments are separated by commas. The argument list can also be empty if the subroutine
takes no parameters.
If the subroutine returns one or more result values, you must use an assignment statement
to store those values somewhere. If the subroutine has no result values, you must
omit the assignment.
Subroutine definitions
----------------------
2018-08-06 01:35:43 +00:00
The syntax is::
sub <identifier> ([proc_parameters]) -> ([proc_results]) {
... statements ...
}
2018-08-06 23:23:34 +00:00
; example:
sub triple_something (amount: X) -> A {
return X * 3
}
The open curly brace must immediately follow the subroutine result specification on the same line,
and can have nothing following it. The close curly brace must be on its own line as well.
Pre-defined subroutines that are available on specific memory addresses
(in system ROM for instance) can be defined by assigning the routine's memory address to the sub,
and not specifying a code block::
sub <identifier> ([proc_parameters]) -> ([proc_results]) = <address>
; example:
sub CLOSE (logical: A) -> (A?, X?, Y?) = $FFC3
.. data:: proc_parameters
2018-08-06 01:35:43 +00:00
comma separated list of "<parametername>:<register>" pairs specifying the input parameters.
You can omit the parameter names as long as the arguments "line up".
2018-08-06 23:23:34 +00:00
.. data:: proc_results
2018-08-06 01:35:43 +00:00
comma separated list of <register> names specifying in which register(s) the output is returned.
If the register name ends with a '?', that means the register doesn't contain a real return value but
is clobbered in the process so the original value it had before calling the sub is no longer valid.
This is not immediately useful for your own code, but the compiler needs this information to
emit the correct assembly code to preserve the cpu registers if needed when the call is made.
For convenience: a single '?' als the result spec is shorthand for ``A?, X?, Y?`` ("I don't know
what the changed registers are, assume the worst")
2018-08-06 23:23:34 +00:00
Loops
-----
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
for loop
^^^^^^^^
@todo::
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
for <loopvar> in <range> [ step <amount> ] {
; do something...
}
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
while loop
^^^^^^^^^^
@todo::
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
while <condition> {
; do something...
}
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
repeat--until loop
^^^^^^^^^^^^^^^^^^
@todo::
repeat {
; do something...
} until <condition>
2018-08-06 01:35:43 +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
Must align this with the various status bits in the cpu... not only true/false....
2018-08-06 01:35:43 +00:00
2018-08-06 23:23:34 +00:00
@todo::
if <condition> {
; do something....
}
[ else {
; do something else...
} ]
@todo::
if <condition> goto <location>
2018-08-06 01:35:43 +00:00