Files
prog8/docs/source/variables.rst
T

842 lines
47 KiB
ReStructuredText
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
.. _variables:
********************
Variables and Values
********************
.. index:: single: Variables; Overview
Because this is such a big subject, variables and values have their own chapter.
Structs and pointers are in a separate chapter again: :ref:`pointers`.
Variables
---------
Variables are named values that can be modified during the execution of the program.
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, but this is not mandatory.
They define the name and type of the variable, and its initial value.
Prog8 supports a small list of data types, including special memory-mapped types
that don't allocate storage but instead point to a fixed location in the address space.
Declaring a variable
^^^^^^^^^^^^^^^^^^^^
.. index:: pair: Variables; Declaring
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,
or an expression. If you don't provide an initial value yourself, zero will be used.
The syntax for variable declarations is::
<datatype> [ @tag ] <variable name> [ = <initial value> ]
.. index::
pair: Data Types; word
pair: Data Types; uword
pair: Data Types; byte
pair: Data Types; ubyte
pair: Data Types; float
pair: Data Types; bool
pair: Data Types; long
pair: Data Types; str
For boolean and numeric variables, you can actually declare them in one go by listing the names in a comma separated list.
Type tags, and the optional initialization value, are applied equally to all variables in such a list.
Various examples::
word thing = 0
byte counter = len([1, 2, 3]) * 20
byte age = 2018 - 1974
float wallet = 55.25
long large = 998877
ubyte x,y,z ; declare three ubyte variables x y and z
str name = "my name is Alice"
uword address = &counter
bool flag = true
byte[] values = [11, 22, 33, 44, 55]
byte[5] values ; array of 5 bytes, initially set to zero
byte[5] values = [255]*5 ; initialize with five 255 bytes
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
uword @requirezp zpaddr = $3000 ; we require this variable in zeropage
word @shared asmvar ; variable is used in assembly code but not elsewhere
byte @nozp memvar ; variable that is never in zeropage
Here are the tags you can add to a variable:
========== ======
Tag Effect
========== ======
@zp prioritize the variable for putting it into Zero page. No guarantees; if ZP is full the variable will be placed in another memory location.
@requirezp force the variable into Zero page. If ZP is full, compilation will fail.
@nozp force the variable to normal system ram, never place it into zeropage.
@shared means the variable is shared with some assembly code and that it cannot be optimized away if not used elsewhere. It also guarantees that the variable keeps program-static storage with a stable, externally visible address.
@nosplit (only valid on (u)word arrays) Store the array as a single linear array instead of a separate array for lsb and msb values
@alignword aligns string or array variable on an even memory address
@align64 aligns string or array variable on a 64 byte address interval (example: for C64 sprite data)
@alignpage aligns string or array variable on a 256 byte address interval (example: to avoid page boundaries)
@dirty the variable won't be set to zero when entering the subroutine (note: it will still be set to zero once on program startup, like all other uninitialized variables). You'll usually have to make sure to assign a value yourself before using the variable! This is used to reduce overhead in certain scenarios. 🦶🔫 Footgun warning.
========== ======
.. note::
The ``@zp``, ``@requirezp`` and ``@nozp`` tags are only meaningful on the 6502 targets.
The m68k targets have no zero page; there these tags are accepted but ignored, and the
compiler notes their redundancy.
.. _private-variables:
.. index:: pair: Variables; Private
.. index:: pair: Variables; Public
You can use the ``private`` keyword (must come first, before the datatype and any tags) to make a variable or constant invisible from outside its block::
private ubyte secret = 42
private ubyte @shared hiddenvar = 10
private const ubyte fixed_value = 99
This makes the variable only accessible within the block where it's declared. Accessing it from another block will result in a compilation error.
You can also use the ``public`` keyword to explicitly mark a variable or constant as accessible from other blocks.
This is especially useful with ``%option private_symbols`` which makes all symbols private by default::
%option private_symbols
main {
public ubyte visible = 10
ubyte hidden = 20 ; only accessible within this block
}
Variables can be defined inside any scope (blocks, subroutines etc.) See :ref:`blocks`.
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.
Values will usually be part of an expression or assignment statement::
12345 ; integer number
$aa43 ; hex integer number
%100101 ; binary integer number (% is also remainder operator so be careful)
false ; boolean false
-33.456e52 ; floating point number
"Hi, I am a string" ; text string, encoded with default encoding
'a' ; byte value (ubyte) for the letter a
sc:"Alternate" ; text string, encoded with c64 screencode encoding
sc:'a' ; byte value of the letter a in c64 screencode encoding
byte counter = 42 ; variable of size 8 bits, with initial value 42
.. index:: pair: Variables; Zeropage
**putting a variable in zeropage:**
If you add the ``@zp`` tag to the variable declaration, the compiler will prioritize this variable
when selecting variables to put into zeropage (but no guarantees). If there are enough free locations in the zeropage,
it will try to fill it with as much other variables as possible (before they will be put in regular memory pages).
Use ``@requirezp`` tag to *force* the variable into zeropage, but if there is no more free space the compilation will fail.
It's possible to put strings, arrays and floats into zeropage too, however because Zp space is really scarce
this is not advised as they will eat up the available space very quickly. It's best to only put byte or word
variables in zeropage. By the way, there is also ``@nozp`` to keep a variable *out of the zeropage* at all times.
Example::
byte @zp smallcounter = 42
uword @requirezp zppointer = $4000
.. index:: pair: Variables; shared
**shared variables:**
If you add the ``@shared`` tag to the variable declaration, the compiler will know that this variable
is a prog8 variable shared with some assembly code elsewhere. This means that the assembly code can
refer to the variable even if it's otherwise not used in prog8 code itself.
(usually, these kinds of 'unused' variables are optimized away by the compiler, resulting in an error
when assembling the rest of the code). Example::
byte @shared assemblyVariable = 42
``@shared`` additionally guarantees that the variable stays in program-static storage with a
stable, externally visible address. This is exactly what escaping addresses (or pointers to the
variable) and inline-assembly references require, and it applies even when the variable is
declared inside a subroutine that would otherwise move it into a stack frame on targets that
have a stack-based memory model (m68k).
.. index:: pair: Variables; uninitialized
**uninitialized variables:**
All variables will be initialized by prog8 at startup, they'll get their assigned initialization value, or be cleared to zero.
This (re)initialization is also done on each subroutine entry for the variables declared in the subroutine.
There may be certain scenarios where this initialization is redundant and/or where you want to avoid the overhead of it.
In some cases, Prog8 itself can detect that a variable doesn't need a separate automatic initialization to zero, if
it's trivial that it is not being read between the variable's declaration and the first assignment. This also applies
to a loop variable introduced implicitly by a ``for`` statement. However Prog8 is not yet very smart at detecting these redundant
initializations. If you want to be sure, check the generated assembly output.
In any case, you can use the ``@dirty`` tag on the variable declaration to make the variable *not* being reinitialized
when entering the subroutine (it will still be set to 0 once at program startup).
This means you usually have to make sure to assign a value yourself, before using the variable. 🦶🔫 Footgun warning.
Note that arrays are an exception to the per-entry reinitialization above: on the 6502-based and
virtual targets, arrays are only cleared to zero once at program startup (as part of the BSS clear),
not on every subroutine entry. On the M68K targets this is different: locals that live in a stack
frame (see :ref:`m68k_memory_model`) are zero-initialized on every call, and this includes arrays.
Cleaning large arrays is costly on every call, and the compiler warns you for this.
You might consider adding ``@dirty`` on such arrays but you must make sure you assign to the array first
otherwise your program will read garbage values from it.
**memory alignment:**
A string or array variable can be aligned to a couple of possible interval sizes in memory.
The use for this is very situational, but two examples are: sprite data for the C64 that needs
to be on a 64 byte aligned memory address, or an array aligned on a full page boundary to avoid
any possible extra page boundary clock cycles on certain instructions when accessing the array.
You can align on word, 64 bytes, and page boundaries::
ubyte[] @alignword array = [1, 2, 3, 4, ...]
ubyte[] @align64 spritedata = [ %00000000, %11111111, ...]
ubyte[] @alignpage lookup = [11, 22, 33, 44, ...]
Initializing a variable
^^^^^^^^^^^^^^^^^^^^^^^
.. index:: pair: Variables; Initializing
You can specify an initialization value in the variable declaration.
This will then be used to initialize the variable with at the start of the subroutine, instead of the default value 0.
The provided value doesn't have to be a constant; it can be any expression.
It is a shorter notation for declaring the variables and then assigning the values to them in separate assignment statement(s).
There are a few special situations:
initializing an array: ``ubyte[3] array = [11,22,33]``
The initialization value has to be a range value or an array literal (remember you can use '[4] * 3' and such).
Of course the size of the range or the number of values in the array has to match the declared array size.
initializing a multi variable declaration with different values: ``ubyte a,b,c = 11,22,33``
Here we have separate initialization values for each of the declared variables in that order.
This is just a shorter way to write this as writing it as three separate variable declarations.
initializing a multi variable declaration with the same value for all: ``ubyte a,b,c = 42``
The initialization value here is a single constant value which will then be assigned to each of the variables.
initializing from a subroutine returning multiple result values: ``ubyte a,b,c = multi()``
Here the initialization value can also be a subroutine call to a subroutine returning multiple result values, which will then be put
into the declared variables in order. Of course the number of values has to match the number of variables.
Constants
---------
.. index:: single: Constants
When using ``const``, the value of the 'variable' cannot be changed; it has become a compile-time constant value instead.
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 memory is allocated
for the constant itself). Only the simple numeric types (byte, word, long, float) and pointer types can be defined as a constant.
If something is defined as a constant, very efficient code can usually be generated from it.
Variables on the other hand can't be optimized as much, need memory, and more code to manipulate them.
A subset of the library routines in the ``math``, ``strings`` and ``floats`` modules are recognised in
compile time expressions. For example, the compiler knows what ``math.sin8u(12)`` is and replaces it with the computed result.
The type of a constant can be omitted. When omitted, the type is inferred from the initializer expression:
- Integer literals default to ``long``
- Float literals default to ``float``
- Boolean literals default to ``bool``
- ``memory()`` calls default to ``uword`` (or ``long`` on 32-bit targets)
- All other expressions default to ``long``
Examples::
const long SCREEN_WIDTH = 320 ; explicit type
const SCREEN_HEIGHT = 240 ; inferred as long
const PI = 3.14 ; inferred as float
const DEBUG = true ; inferred as bool
Enums
-----
.. index:: single: Enums
There is a more convenient way to define a bunch of constants that belong together: a "enum".
That is a grouped list of constants that get autonumbered for you (unless you override the numeric value yourself).
It starts numbering from zero by default. Right now the enum declaration does not define a new type, only a list of constants. Here's an example::
enum Priority {
LOW = 1,
NORMAL,
HIGH,
EXTREME=255
}
This will define a bunch of constants like so (the "Enum::Field" syntax is specific for enumeration elements)::
const ubyte Priority::LOW = 1
const ubyte Priority::NORMAL= 2
const ubyte Priority::HIGH = 3
const ubyte Priority::EXTREME = 255
Data Types
----------
.. index:: pair: Data Types; Overview
Prog8 supports the following data types:
================== ======================= ====================== =========================================
type identifier type storage size example var declaration and literal value
================== ======================= ====================== =========================================
``byte`` signed byte 1 byte = 8 bits ``byte myvar = -22``
``ubyte`` unsigned byte 1 byte = 8 bits ``ubyte myvar = $8f``, ``ubyte c = 'a'``
``bool`` boolean 1 byte = 8 bits ``bool myvar = true`` or ``bool myvar = false``
``word`` signed word 2 bytes = 16 bits ``word myvar = -12345``
``uword`` unsigned word 2 bytes = 16 bits ``uword myvar = $8fee``
``long`` signed 32 bits integer 4 bytes ``long large = $12345678``
there is no unsigned long type at the moment.
``float`` floating-point 5 bytes = 40 bits ``float myvar = 1.2345``
stored in 5-byte cbm MFLPT format
``byte[x]`` signed byte array x bytes ``byte[4] myvar``
``ubyte[x]`` unsigned byte array x bytes ``ubyte[4] myvar``
``word[x]`` signed word array 2*x bytes ``word[4] myvar``
``uword[x]`` unsigned word array 2*x bytes ``uword[4] myvar``
``float[x]`` floating-point array 5*x bytes ``float[4] myvar``. The 5 bytes per float is on CBM targets.
``bool[x]`` boolean array x bytes ``bool[4] myvar`` note: consider using bit flags in a byte or word instead to save space
``byte[]`` signed byte array depends on value ``byte[] myvar = [1, 2, 3, 4]``
``ubyte[]`` unsigned byte array depends on value ``ubyte[] myvar = [1, 2, 3, 4]``
``word[]`` signed word array depends on value ``word[] myvar = [1, 2, 3, 4]``
``uword[]`` unsigned word array depends on value ``uword[] myvar = [1, 2, 3, 4]``
``float[]`` floating-point array depends on value ``float[] myvar = [1.1, 2.2, 3.3, 4.4]``
``bool[]`` boolean array depends on value ``bool[] myvar = [true, false, true]`` note: consider using bit flags in a byte or word instead to save space
``str[]`` array with string ptrs 2*x bytes + strs ``str[] names = ["ally", "pete"]`` note: equivalent to a uword array.
``str`` string (PETSCII) varies ``str myvar = "hello."``
implicitly terminated by a 0-byte
``StructType[x]`` array of struct inst. x * sizeof(struct) ``Enemy[10] enemies`` see :ref:`structarrays`
``^^type`` typed pointer 2 or 4 bytes pointer types are explained in their own chapter :ref:`pointers`
``pointer`` untyped address 2 or 4 bytes ``pointer ptr = $a000``. See :ref:`pointers` for details.
================== ======================= ====================== =========================================
.. note::
The actual size of pointer types (``^^type`` and ``pointer``) depends on the target's
:ref:`pointer size <pointer_size>`: 2 bytes on 6502 targets, 4 bytes on m68k and virtual targets.
Integers (bytes, words, longs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. index:: pair: Data Types; Integers
Integers are 8, 16 or 32 bit numbers and can be written in normal decimal notation,
in hexadecimal and in binary notation. There is no octal notation. Hexadecimal has the '$' prefix,
binary has the '%' prefix. ``%`` is also the remainder operator so be careful: if you want to take the remainder
of something with an operand starting with 1 or 0, you'll have to add a space in between, otherwise
the parser thinks you've typed an invalid binary number.
You can use underscores to group digits to make long numbers more readable: any underscores in the number are ignored by the compiler.
For instance ``3_000_000`` is a valid decimal number and so is ``%1001_0001`` a valid binary number.
A single character in single quotes such as ``'a'`` is translated into a byte integer,
which is the PETSCII value for that character. You can prefix it with the desired encoding, like with strings, see :ref:`encodings`.
.. _endianness:
*Endianness:* on the 6502-family targets all integers are stored in *little endian* byte order: the Least Significant Byte first and the Most Significant Byte last.
The m68k targets (amiga500, amiga1200, qemu68k) are the exception: the 68000/68020 CPUs are *big endian*, so there integers
(and pointers, which are wider on those targets - see :ref:`pointer_size`) are stored Most Significant Byte first.
**bytes versus words versus longs:**
Prog8 tries to determine the data type of integer values according to the table below,
and sometimes the context in which they are used.
========================= =================
value datatype
========================= =================
-128 .. 127 byte
0 .. 255 ubyte
-32768 .. 32767 word
0 .. 65535 uword
-2147483648 .. 2147483647 long (there is no unsigned long right now)
========================= =================
Numeric expressions usually 'stay within their type' unless a cast is used, see :ref:`arithmetic`.
If the number fits in a byte but you really require it as a word value, you'll have to explicitly cast it: ``60 as uword``
or you can use the full word hexadecimal notation ``$003c``. This is useful in expressions where you want a calculation
to be done on word values, and don't want to explicitly have to cast everything all the time. For instance::
ubyte column
uword offset = column * 64 ; does (column * 64) as uword, wrong result?
uword offset = column * $0040 ; does (column as uword) * 64 , a word calculation
.. attention::
Doing math on signed integers can result in code that is a lot larger and slower than
when using unsigned integers. Make sure you really need the signed numbers, otherwise
stick to unsigned integers for efficiency.
.. attention::
Not all operations on Long integers are supported at the moment, although most common
operations should work fine. Notably absent for now are multiplication and division of longs.
There is no unsigned long type at the moment, but you can sometimes simply treat the signed
long value as an unsigned 32 bits value just fine.
Operations on long integers take a lot of instructions on 8 bit CPUs so code that uses them
a lot will be much slower than when you restrict yourself to 8 or 16 bit values. Use long values sparingly.
.. danger::
**longs and cx16.R12,R13,R14,R15**:
**Some operations on long values require the use of the R12-R15 virtual register as temporary storage**
So if you are working with long values, you should assume that the contents of R12-R15 could be destroyed.
(Many operations preserve the values, but not all, because of reasons)
**Using R12,R13,R14,R15 in expressions that work with longs, will sometimes give a corrupted result, without
a warning of the compiler!** It is strongly advised to *not* use R12-R15 at all when dealing with longs.
Booleans
^^^^^^^^
.. index:: pair: Data Types; Booleans
Booleans are a distinct type called ``bool`` in Prog8 and can have only the values ``true`` or ``false``.
In memory, they are stored as a byte containing 0 or 1.
You can cast any numeric to a bool, in which case 0 will become ``false`` and any nonzero value will become ``true``.
Floating point numbers
^^^^^^^^^^^^^^^^^^^^^^
.. index:: pair: Data Types; Floating point
Floats are stored in the 5-byte 'MFLPT' format that is used on CBM machines.
Floating point support is available on the c64 and cx16 (and virtual) compiler targets.
On the cbm-compatible systems, the rom routines are used for floating point operations,
so on both systems the correct rom banks have to be banked in to make this work.
Although the C128 shares the same floating point format, Prog8 currently doesn't support
using floating point on that system. The c128 fp routines require the fp variables to be in
another ram bank than the program, which Prog8 doesn't support yet.
Also your code needs to import the ``floats`` 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)
The largest 5-byte MFLPT float that can be stored is: **1.7014118345e+38** (negative: **-1.7014118345e+38**)
You can use underscores to group digits in floating point literals to make long numbers more readable:
any underscores in the number are ignored by the compiler.
For instance ``30_000.999_999`` is a valid floating point number 30000.999999.
.. attention::
On the X16, make sure rom bank 4 is still active before doing floating point operations (it's the bank that contains the fp routines).
On the C64, you have to make sure the Basic ROM is still banked in (same reason).
.. _arrayvars:
Arrays
^^^^^^
.. index:: pair: Data Types; Arrays
Arrays can be created from a list of booleans, bytes, words, floats, pointers, struct initializers,
or ``memory()`` calls (either directly or via ``const`` variables holding ``memory()`` results).
Addresses of other variables are also accepted, such as explicit address-of expressions, strings,
or other array variables.
The values in an array literal always have to be constants.
A trailing comma is allowed, sometimes this is easier when copying values
or when adding more stuff to the array later. Here are some examples of arrays::
byte[10] array ; array of 10 bytes, initially set to 0
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
ubyte[99] array = [255]*99 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
str[] names = ["ally", "pete"] ; array of string pointers/addresses (equivalent to array of uwords)
uword[] others = [names, array] ; array of pointers/addresses to other arrays
bool[2] flags = [true, false] ; array of two boolean values (take up 1 byte each, like a byte array)
^^float[3] values ; array of three pointers to floats
Enemy[10] enemies ; array of 10 Enemy struct instances (see :ref:`pointers`)
uword[2] memarr = [memory("m1",10,0), memory("m2",20,0)] ; array of memory block addresses
value = array[3] ; the fourth value in the array (index is 0-based)
char = string[4] ; the fifth character (=byte) in the string
char = string[-2] ; the second-to-last character in the string (Python-style indexing from the end)
.. note::
On the 6502-family targets (c64, c128, cx16, pet32, virtual) the 8-bit index register limits an array to be
indexable by a single byte. This means:
- byte/string arrays are limited to 256 elements (255 characters for a string, because of the
terminating null byte)
- word arrays to 256 elements as well, if split (which is the default); when not split the maximum
length is 128. See below for details about this distinction.
- float arrays to 51 elements
On the m68k targets (amiga500, amiga1200, qemu68k) the total array size limit is **32768 bytes**. There the
byte/bool array limit is 32768 elements, the word array limit is 16384 elements, and the long, float and
string array limits are 8192 elements each (a ``str`` array holds 4-byte pointers on these targets).
Split word arrays are not supported on the m68k targets.
Arrays can be initialized with a range expression or an array literal value.
You can write out such an initializer value over several lines if you want to improve readability.
When an initialization value is given, you are allowed to omit the array size in the declaration,
because it can be inferred from the initialization value.
You can use '*' to repeat array fragments to build up a larger array.
You can assign a new value to an element in the array, but you can't assign a whole
new array to another array at once. This is usually a costly operation. If you really
need this you have to write it out depending on the use case: you can copy the memory using
``sys.memcopy(sourcearray, targetarray, sizeof(targetarray))``. Or perhaps use ``sys.memset`` instead to
set it all to the same value, or maybe even simply assign the individual elements.
The various keywords for the data type and variable type (``byte``, ``word``, ``const``, etc.)
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
for instance. The same applies to the names of the builtin functions (such as ``memory``, ``mkword``,
``peek`` and ``poke``): they cannot be redefined as a variable, block, subroutine, struct or label.
Using the ``in`` operator you can easily check if a value is present in an array,
example: ``if choice in [1,2,3,4] {....}``
2D Arrays
^^^^^^^^^
.. index:: triple: Data Types; Matrixes; Two-dimensional Arrays
Prog8 provides a convenient syntactic sugar for working with two-dimensional arrays.
Instead of manually calculating offsets into a flat 1D array, you can declare and
access arrays using familiar 2D syntax.
Declaration uses two bracket pairs to specify the number of rows and columns::
ubyte[3][4] matrix ; 3 rows, 4 columns (12 elements total)
Access uses chained indexing with ``[row][column]``::
ubyte value = matrix[1][2] ; element at row 1, column 2
matrix[0][0] = 42 ; set top-left element
This is purely **syntactic sugar**. The compiler automatically transforms
``matrix[row][col]`` into the equivalent 1D index calculation
``matrix[row * numCols + col]``. There is no runtime overhead or special
2D tracking — after compilation it behaves exactly like a regular 1D array access.
**Initialization:**
When initializing a 2D array, provide the values as a **flat list** that matches
the total number of elements (rows × columns). Nested list initializers
(such as ``[[1,2,3],[4,5,6]]``) are **not supported**::
ubyte[2][3] matrix = [1, 2, 3, 4, 5, 6] ; correct: flat list
**Size limitations:**
The combined array size is subject to the same limits as regular 1D arrays:
- For byte arrays (``ubyte``, ``byte``, ``bool``): maximum **256 elements** total (rows × columns ≤ 256).
- For split word arrays (``uword``, ``word``, ``str``): maximum **256 elements** total, which occupies 512 bytes of storage (LSB and MSB arrays).
- For long arrays (``long``): maximum **64 elements** total (rows × columns ≤ 64), which occupies 256 bytes.
- For float arrays (``float``): maximum **51 elements** total (rows × columns ≤ 51), which occupies 255 bytes.
- For sequential word arrays (``@nosplit uword`` etc): maximum **128 elements** total, which occupies 256 bytes.
- The ``@split`` tag works normally with 2D syntax.
- The ``@nosplit`` tag can also be used on 2D word arrays if sequential storage is needed.
These limits apply to the 6502-family targets. On the m68k targets (amiga500, amiga1200, qemu68k) the maximum total
element count is 32768 bytes: byte/bool arrays up to 32768 elements, sequential word arrays up to 16384, and
long, float and str arrays up to 8192 elements (str arrays hold 4-byte pointers there). Split word arrays are
not supported on the m68k targets.
**Not supported:**
- 3D or higher-dimensional arrays are not supported.
- Chained indexing (``arr[y][x]``) can only be used on variables that were
explicitly declared with the 2D ``[rows][cols]`` syntax. Using it on a
regular 1D array variable will produce a compile error.
**Arrays at a specific memory location:**
Using the memory-mapped syntax it is possible to define an array to be located at a specific memory location.
For instance to reference the first 5 rows of the Commodore 64's screen matrix as an array, you can define::
&ubyte[5*40] top5screenrows = $0400
This way you can set the second character on the second row from the top like this::
top5screenrows[41] = '!'
**Array indexing on a pointer variable:**
A variable that can hold a memory address can be used in limited scenarios as a 'pointer' to a byte in memory at a specific,
dynamic, location. You can use array indexing on such a variable to use it as a byte array at
a dynamic location in memory: currently this is equivalent to directly referencing the bytes in
memory at the given index. In contrast to a real array variable, the index value can be larger than an ubyte.
Unlike array variables, negative indexing for pointer variables does *not* mean it will be counting from the end, because the size of the buffer is unknown.
Instead, it simply addresses memory that lies *before* the pointer variable.
Which variable types can be used for pointer indexing depends on the :ref:`pointer size <pointer_size>` of the target:
``uword`` on 6502 targets, ``long`` (or ``pointer``) on m68k targets.
See also :ref:`pointervars` and the chapter about it :ref:`pointers`.
**LSB/MSB split word and str arrays:**
As an optimization, (u)word arrays, pointer arrays, and str arrays are split by the compiler in memory as two separate arrays,
one with the LSBs and one with the MSBs of the word values. This is more efficient to access by the 6502 cpu.
It also allows a maximum length of 256 for word arrays, where normally it would have been 128.
This is a 6502-only optimization: the m68k targets never split arrays, and a ``@nosplit`` tag on an m68k target
is redundant.
For normal prog8 array indexing, the compiler takes care of the distinction for you under water.
*But for assembly code, or code that otherwise accesses the array elements directly, you have to be aware of the distinction from 'normal' arrays.*
In the assembly code, the array is generated as two byte arrays namely ``name_lsb`` and ``name_msb``, immediately following each other in memory.
The ``@nosplit`` tag can be added to the variable declaration to *not* split the array. This is useful for compatibility with
code that expects the words to be sequentially in memory (such as the cx16.FB_set_palette routine).
.. note::
Some obscure array operations may not yet be supported on "split word arrays".
If you get a compiler error message hinting that this is the case,
simply revert to a regular sequential word array using ``@nosplit``, and please report the issue so that
the missing function can be added.
.. note::
Array literals are stored as split arrays if they're initializing a split word array, otherwise,
they are stored as sequential words! So if you pass one directly to a subroutine (like ``func([1111,2222,3333])``),
the array values are sequential in memory. If this is undesirable (i.e. the subroutine expects a split word array),
you have to create a normal array variable first and then pass that to the subroutine.
.. caution::
The default is to split word arrays. Normal array access is taken care of by Prog8, so you won't
notice this optimization. However if you are accessing the array's values using other ways (for example via a pointer,
and then using ``peekw`` to get the value) you have to be aware of this. In that ``peekw`` example you have
to use ``@nosplit`` on the word array so that the words stay sequentially in memory which is what ``peekw`` needs.
Also be careful when passing arrays to library routines (this is via a pointer!): the library routine
has to be able to deal with the split array, otherwise you have to use ``@nosplit`` as well.
.. _encodings:
Strings
^^^^^^^
.. index:: pair: Data Types; Strings
Strings are a sequence of characters enclosed in double quotes. The length of a string *literal* is limited to
255 characters on all targets, including the m68k targets (amiga500, amiga1200, qemu68k).
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 (without encoding prefix) will be encoded (translated from ASCII/UTF-8) into bytes via the
*default encoding* for the target platform. On the CBM machines, this is CBM PETSCII.
Strings without an encoding prefix are stored in the machine's default character encoding (which is PETSCII on the CBM machines,
but can be something else on other targets).
There are ways to change the encoding: prefix the string with an encoding name, or use the ``%encoding`` directive to
change it for the whole file at once. Here are examples of the possible encodings:
- ``"hello"`` a string translated into the default character encoding (PETSCII on the CBM machines)
- ``petscii:"hello"`` string in CBM PETSCII encoding
- ``sc:"my name is Alice"`` string in CBM screencode encoding
- ``iso:"Ich heiße François"`` string in iso-8859-15 encoding (Latin)
- ``iso5:"Хозяин и Работник"`` string in iso-8859-5 encoding (Cyrillic)
- ``iso16:"zażółć gęślą jaźń"`` string in iso-8859-16 encoding (Eastern Europe)
- ``atascii:"I am Atari!"`` string in "atascii" encoding (Atari 8-bit)
- ``cp437:"≈ IBM Pc ≈ ♂♀♪☺¶"`` string in "cp437" encoding (IBM PC codepage 437) See note below!
- ``kata:"アノ ニホンジン ワ ガイコクジン。 # が # ガ"`` string in "kata" encoding (Katakana)
- ``c64os:"^Hello_World! \\ ~{_}~"`` string in "c64os" encoding (C64 OS)
So what follows below is a string literal that will be encoded into memory bytes using the iso encoding.
It can be correctly displayed on the screen only if a iso-8859-15 charset has been activated first
(the Commander X16 has this capability)::
iso:"Käse, Straße"
.. sidebar:: str parameters in subroutines
A subroutine parameter declared as type ``str`` will be changed into a ``^^ubyte`` type (pointer to ubyte) instead
because Prog8 doesn't pass strings by value. This means you *can* assign new values to this parameter variable: you can
set it to a new memory address. You're not actually assigning something to a string variable, because that's not what its type actually is.
You can concatenate two string literals using '+', which can be useful to
split long strings over separate lines. But remember that the length
of the total string still cannot exceed 255 characters.
A string literal can also be repeated a given number of times using '*', where the repeat number must be a constant value.
You cannot assign a new value to a ``str`` variable. If you really want to set a new string value into the variable,
you have to explicitly copy it over the old value with ``strings.copy()`` or ``strings.ncopy()`` for instance.
There are several escape sequences available to put special characters into your string value:
- ``\\`` - the backslash itself, has to be escaped because it is the escape symbol by itself
- ``\n`` - newline character (move cursor down and to beginning of next line)
- ``\r`` - carriage return character (more or less the same as newline if printing to the screen)
- ``\"`` - quote character (otherwise it would terminate the string)
- ``\'`` - apostrophe character (has to be escaped in character literals, is okay inside a string)
- ``\uHHHH`` - a unicode codepoint \u0000 - \uffff (16-bit hexadecimal)
- ``\xHH`` - 8-bit hex value that will be copied verbatim *without encoding*
- String literals can contain many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳".
Characters like ^, _, \\, {, } and | (that have no direct PETSCII counterpart) are still accepted and converted to the closest PETSCII equivalents. (Make sure you save the source file in UTF-8 encoding if you use this.)
Using the ``in`` operator you can easily check if a character is present in a string,
example: ``if '@' in email_address {....}``. However this gives no clue about the location
in the string where the character is present; if you need that, use the ``strings.find()``
library function instead.
**Caution:**
This checks *all* elements in the string with the length as it was initially declared.
Even when a string was changed and is terminated early with a 0-byte early,
the containment check with ``in`` will still look at all character positions in the initial string.
Consider using ``strings.find`` followed by ``if_cs`` (for instance) to do a "safer" search
for a character in such strings (one that stops at the first 0 byte)
.. hint::
Strings/arrays and uwords (=memory address) can often be interchanged.
An array of strings is actually an array of uwords where every element is the memory
address of the string. You can pass a memory address to assembly functions
that require a string as an argument.
For regular assignments you still need to use an explicit ``&`` (address-of) to take
the address of the string or array.
.. hint::
You can declare parameters and return values of subroutines as ``str``,
but in this case that is equivalent to declaring them as ``uword`` (because
in this case, the address of the string is passed as argument or returned as value).
.. note:: Strings and their (im)mutability
*String literals outside of a string variable's initialization value*,
are considered to be "constant", i.e. the string isn't going to change
during the execution of the program. The compiler takes advantage of this in certain
ways. For instance, multiple identical occurrences of a string literal are folded into
just one string allocation in memory. Examples of such strings are the string literals
passed to a subroutine as arguments.
*Strings that aren't such string literals are considered to be unique*, even if they
are the same as a string defined elsewhere. This includes the strings assigned to
a string variable in its declaration! These kind of strings are not deduplicated and
are just copied into the program in their own unique part of memory. This means that
it is okay to treat those strings as mutable; you can safely change the contents
of such a string without destroying other occurrences (as long as you stay within
the size of the allocated string!)
.. note:: printing **cp437** encoded strings
To print strings in the **cp437** encoding, you will probably need ``txt.print_lit(message)`` to properly print
them to the screen. This is because this encoding has symbols in place of where normally ASCII
control characters such as Line feed would be. A regular ``txt.print(message)`` will likely get confused
by such symbols and print them as control characters, messing up the output.
Structs and Pointers
^^^^^^^^^^^^^^^^^^^^
Struct and Pointer types are explained in their own separate chapter :ref:`pointers`.
.. _range-expression:
Ranges
^^^^^^
.. index:: pair: Data Types; Ranges
A special value is the *range expression* which represents a range of integer numbers or characters,
from the starting value to (and including) the ending value::
<start> to <end> [ step <step> ]
<start> downto <end> [ step <step> ]
You can provide a step value if you need something else than the default increment which is one (or,
in case of downto, a decrement of one). For a range used directly by a ``for`` loop, the step may be an
integer expression. It is evaluated once at the start of the loop. The signedness of the step determines
how its runtime value is interpreted: an unsigned step is always ascending, while a signed step uses its
runtime sign to select ascending or descending iteration. A zero step, or a step pointing away from the
end value, produces an empty loop. If adding the step would wrap the loop variable's fixed-width type,
the loop terminates instead of continuing with the wrapped value.
Range expressions used elsewhere, such as for array initialization or containment checks, still require a
constant step.
Because a step of minus one is so common you can just use
the downto variant to avoid having to specify the step as well::
0 to 7 ; range of values 0, 1, 2, 3, 4, 5, 6, 7
20 downto 10 step -3 ; range of values 20, 17, 14, 11
aa = 5
xx = 10
aa to xx ; range of 5, 6, 7, 8, 9, 10
for i in 0 to 127 {
; i loops 0, 1, 2, ... 127
}
Range expressions are most often used in for loops, but can be also be used to create array initialization values::
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
Memory-mapped
^^^^^^^^^^^^^
.. index:: pair: Data Types; Memory-mapped
When using ``&`` (the address-of operator but now applied to the datatype in the variable's declaration),
the variable will be placed at a designated position in memory rather than being newly allocated somewhere.
The initial value in the declaration should be the valid memory address where the variable should be placed.
Reading the variable will then read its value from that address, and setting the variable will directly modify those memory location(s)::
const byte max_age = 2000 - 1974 ; max_age will be the constant value 26
&word SCREENCOLORS = $d020 ; a 16-bit word at the address $d020-$d021
If you need to use the variable's memory address instead of the value placed there, you can still use `&variable` as usual.
You can memory map all datatypes except strings.
.. _pointervars:
Direct access to memory locations ('peek' and 'poke')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Usually specific memory locations are accessed through a memory-mapped variable, such as ``cbm.BGCOL0`` that is defined
as the background color register at the memory address $d021 (on the c64 target).
If you want to access any memory location directly (by using the address itself or via a pointer variable),
without defining a memory-mapped location, you can do so by enclosing the address in ``@(...)``::
color = @($d020) ; set the variable 'color' 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
You can actually also use the array indexing notation for this. It will be silently converted into
the direct memory access expression as explained above. Unlike regular arrays,
the index is not limited to an ubyte value. You can use a larger integer type to index a pointer variable like this::
pointervar[999] = 0 ; set memory byte to zero at location pointervar + 999.
The variable type must be able to hold a :ref:`pointer <pointer_size>` for the target: ``uword`` on 6502 targets, ``long`` on m68k targets.
More information about *typed pointers* can be found in the chapter :ref:`pointers`.
Converting/Casting 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
.. note::
The ``as`` operator has very low precedence, lower than almost all other operators.
This means that ``a + b as long`` is parsed as ``(a + b) as long``.
Use parentheses if you want to cast an operand before an operation: ``(a as long) * b``.
Sometimes it is a straight reinterpretation of the given value as being of the other type,
sometimes an actual value conversion is done to convert it into the other type.
Try to avoid those type conversions as much as possible.
Initial values across multiple runs of the program
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. index:: pair: Data Types; Initial values
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,
or even multiple invocations of the entire program.
If you omit the initial value, zero will be used instead.
This only works for simple types, *and not for string variables and arrays*.
It is assumed these are left unchanged by the program; they are not re-initialized on
a second run.
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)