mirror of
https://github.com/irmen/prog8.git
synced 2026-10-22 05:02:58 +00:00
make user docs and some code comments less convoluted
This commit is contained in:
1 parent
13828ea932
commit
1f8b42fae5
19 files changed
+128
-117
No files matched your search
@@ -19,7 +19,7 @@ import prog8.code.target.zp.PETZeropage
|
||||
|
||||
|
||||
/**
|
||||
* Comprehensive tests for Zeropage free list configurations across all targets.
|
||||
* Tests for Zeropage free list configurations across all targets.
|
||||
*
|
||||
* Tests verify that for each target and ZeropageType combination:
|
||||
* - The exact list of free locations is correct
|
||||
|
||||
@@ -38,13 +38,13 @@ internal fun IRInstruction.forwardableImmediateFor(register: VirtualRegister, ty
|
||||
* It may contain registers that are NOT call arguments.
|
||||
* For every call argument register, the loaded immediate value is forwarded
|
||||
* directly into the argument's hardware register instead of going through the
|
||||
* register file (move #imm,dX instead of move p8_regfile+N,dX, and
|
||||
* fmovecr or a const-pool load into fpX instead of moving through p8_fregfile).
|
||||
* register file. That is move #imm,dX instead of move p8_regfile+N,dX, and
|
||||
* fmovecr or a const-pool load into fpX instead of moving through p8_fregfile.
|
||||
*
|
||||
* [deadRegisters] are the registers from [loads] that are not read anywhere
|
||||
* else in the subroutine (their only use is the forwarded call argument);
|
||||
* their register-file stores are omitted entirely. Note that this can also
|
||||
* remove stores for registers that are not call arguments at all.
|
||||
* else in the subroutine; their only use is the forwarded call argument, so
|
||||
* their register-file stores are omitted entirely. This can also remove
|
||||
* stores for registers that are not call arguments at all.
|
||||
*
|
||||
* The read check covers the subroutine's whole flattened instruction list
|
||||
* rather than only the part after the call (see `isRegisterReadElsewhere`),
|
||||
|
||||
@@ -1255,7 +1255,7 @@ class TestInstructionSelectionOptimizations : FunSpec({
|
||||
lines.any { it == "move.w #1234,var" } shouldBe true
|
||||
}
|
||||
|
||||
// === extb.l gate robustification ===
|
||||
// === extb.l gate hardening ===
|
||||
|
||||
test("M68020 sign-extends byte to long with extb.l") {
|
||||
val lines = generateAsm(
|
||||
|
||||
@@ -494,7 +494,7 @@ internal val CallResult.hardwareSlot: CallingConventionSlot?
|
||||
/**
|
||||
* The slot-based arguments, in the order they must be loaded to avoid register clobbering.
|
||||
* Order matches the old 6502 codegen: paired regs first (AX/AY/XY), then single regs (Y, X, A),
|
||||
* then float regs, then status flags. This ensures that loading a paired register
|
||||
* then float regs, then status flags. That way, loading a paired register
|
||||
* (which clobbers two hardware regs) doesn't overwrite a value needed for a later argument.
|
||||
* Within each group the original argument order is kept.
|
||||
*/
|
||||
|
||||
@@ -1104,8 +1104,9 @@ internal fun AsmGen.copyRegisterLong(dstReg: Int, srcReg: Int) {
|
||||
emitLine("bpl -")
|
||||
}
|
||||
|
||||
/** the address of a [base] (+ [displacement], for a Symbol base only - matches the previous behavior
|
||||
* of ignoring any offset that came together with a fixed numeric address) */
|
||||
/** the address of a [base], plus [displacement]. The displacement is only added
|
||||
* for a Symbol base; this matches the previous behavior of ignoring any offset
|
||||
* that came together with a fixed numeric address. */
|
||||
internal fun AsmGen.resolveAddress(base: AddressBase, displacement: Int = 0): String {
|
||||
return when (base) {
|
||||
is AddressBase.Symbol -> {
|
||||
|
||||
@@ -10,7 +10,7 @@ import prog8tests.helpers.compileText
|
||||
/**
|
||||
* Tests for bugs that only occurred when compiling with -noopt (optimizations disabled).
|
||||
*
|
||||
* These tests ensure that boolean literals in logical expressions are handled correctly
|
||||
* These tests verify that boolean literals in logical expressions are handled correctly
|
||||
* when the optimizer doesn't remove them.
|
||||
*/
|
||||
class TestBooleanLiteralsNoOpt: FunSpec({
|
||||
|
||||
@@ -9,9 +9,9 @@ import java.nio.file.Files
|
||||
/**
|
||||
* These tests execute compiled 6502 code in the ksim65 simulator to verify that
|
||||
* in-place modifications (augmented assignments) on struct fields reached via
|
||||
* a pointer are correct, especially when the pointer variable is *not* located
|
||||
* in the zeropage (so its effective address must live in the shared scratch
|
||||
* pointer that evaluation of the source expression may clobber).
|
||||
* a pointer are correct. This is especially tricky when the pointer variable is
|
||||
* *not* located in the zeropage: its effective address must live in the shared
|
||||
* scratch pointer, which evaluation of the source expression may clobber.
|
||||
*/
|
||||
class TestPointerFieldAugmentedAssignment6502 : FunSpec({
|
||||
val outputDir = Files.createTempDirectory("prog8test")
|
||||
|
||||
@@ -105,7 +105,7 @@ Pointers and Structs
|
||||
A variable that can hold a :ref:`pointer <pointer_size>` for the target can be used as a pointer to a memory location,
|
||||
so the value it points to is always a single byte. This is similar to ``uint8_t*`` from C.
|
||||
You have to deal with the pointer manually if the object it points to is something different.
|
||||
- Note that there is the ``peekw`` builtin function that *does* allow you to directly obtain the *word* value at the given memory location.
|
||||
- There is the ``peekw`` builtin function that *does* allow you to directly obtain the *word* value at the given memory location.
|
||||
So if you use this, you can use uword pointers as pointers to word values without much hassle.
|
||||
- "dereferencing" an untyped pointer is done via array indexing ``ptr[index]`` or via the memory read operator ``@(ptr)``, or ``peek/peekw(ptr)``.
|
||||
- Pointers don't have to be a variable, you can immediately access the value of a given memory location using ``@($d020)`` for instance.
|
||||
|
||||
@@ -53,7 +53,8 @@ Mac OS (and Linux, and WSL2 on Windows):
|
||||
|
||||
The Gradle build system is used to build the compiler. You will also need at least Java version 17 or higher to build it.
|
||||
The most interesting gradle commands to run are probably the ones listed below.
|
||||
(Note: if you have a recent gradle installed on your system already, you can probably replace the ``./gradlew`` wrapper commands with just the regular ``gradle`` command.)
|
||||
If you have a recent Gradle installation already, you can probably replace the ``./gradlew`` wrapper commands
|
||||
with the regular ``gradle`` command.
|
||||
|
||||
``./gradlew build``
|
||||
Builds the compiler code and runs all available checks and unit-tests.
|
||||
@@ -93,7 +94,7 @@ For normal use, the ``installDist`` task should suffice and after successful com
|
||||
3. rebuild the full project.
|
||||
|
||||
Alternatively you can also use the Makefile in the antlr directory to generate the parser, but for development the
|
||||
Antlr4 plugin provides several extremely handy features so you'll probably want to have it installed anyway.
|
||||
Antlr4 plugin provides several useful features so you'll probably want to have it installed anyway.
|
||||
|
||||
.. image:: _static/antlrparser.png
|
||||
:alt: Generating the Antlr4 parser files
|
||||
@@ -371,10 +372,10 @@ One or more .p8 module files
|
||||
Tells the assembler to issue warning messages about symbol shadowing.
|
||||
These *can* be problematic, but usually aren't because prog8 has different scoping rules
|
||||
than the assembler has.
|
||||
You may want to watch out for shadowing of builtin names though. Especially 'a', 'x' and 'y'
|
||||
as those are the cpu register names and if you shadow those, the assembler might
|
||||
interpret certain instructions differently and produce unexpected opcodes (like LDA X getting
|
||||
turned into TXA, or not, depending on the symbol 'x' being defined in your own assembly code or not)
|
||||
You may want to watch out for shadowing of builtin names though. Especially 'a', 'x' and 'y',
|
||||
as those are the cpu register names. If you shadow those, the assembler might interpret certain
|
||||
instructions differently and produce unexpected opcodes, like LDA X getting turned into TXA.
|
||||
Whether that actually happens depends on whether the symbol 'x' is defined in your own assembly code.
|
||||
|
||||
``-watch``
|
||||
Enables continuous compilation mode (watches for file changes).
|
||||
@@ -382,7 +383,7 @@ One or more .p8 module files
|
||||
almost instant compilation times (less than a second) can be achieved in this mode.
|
||||
The compiler will compile your program and then instead of exiting, it waits for any changes in the module source files.
|
||||
As soon as a change happens, the program gets compiled again.
|
||||
Note that it is possible to use the watch mode with multiple modules as well, but it will
|
||||
It is possible to use the watch mode with multiple modules as well, but it will
|
||||
recompile everything in that list even if only one of the files got updated.
|
||||
*Combined with the Emulator's Host-FS or real hardware Calypso's network drive:* this enables you to
|
||||
edit prog8 source files with an editor on the X16 itself (such as XVI, XEdit). When saving the source file,
|
||||
|
||||
@@ -103,9 +103,9 @@ Language :index:`Features`
|
||||
- ``when`` statement to avoid if-else chains
|
||||
- ``on .. goto`` statement for fast jump tables
|
||||
- ``in`` expression for concise and efficient multi-value/containment test
|
||||
- ``defer`` statement with program-wide unwinding to help write concise and robust resource cleanup logic
|
||||
- ``defer`` statement with program-wide unwinding to help write reliable resource cleanup code
|
||||
- Several specialized built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
|
||||
- Various powerful built-in libraries to do I/O, number conversions, graphics and more
|
||||
- Built-in libraries for I/O, number conversions, graphics and more
|
||||
- Floating point math is supported on most cbm-compatible compiler targets.
|
||||
- Provides access to most Kernal ROM routines as external subroutine definitions you can call normally.
|
||||
- Strings can contain escaped characters but also many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest PETSCII equivalents.
|
||||
|
||||
+11
-13
@@ -188,9 +188,8 @@ Miscellaneous
|
||||
|
||||
.. note::
|
||||
The arguments are in 'natural' left to right reading order that is first the msb then the lsb.
|
||||
Don't get confused by how the system actually stores this 16-bit word value in memory (which is
|
||||
in little-endian format, so lsb first then msb, on the 6502 targets; the m68k targets store it
|
||||
big-endian, msb first)
|
||||
The system stores this 16-bit word value in memory in little-endian format on the 6502 targets
|
||||
(lsb first, then msb); the m68k targets store it big-endian (msb first).
|
||||
|
||||
:index:`mklong` (msb, b2, b1, lsb)
|
||||
Efficiently create a long value from four bytes (the msb, second, first and finally the lsb). Avoids multiplication and shifting.
|
||||
@@ -198,9 +197,8 @@ Miscellaneous
|
||||
|
||||
.. note::
|
||||
The arguments are in 'natural' left to right reading order that is first the msb then the lsb.
|
||||
Don't get confused by how the system actually stores this 32-bit word value in memory (which is
|
||||
in little-endian format, so lsb first then b1, b2 and finally the msb, on the 6502 targets;
|
||||
the m68k targets store it big-endian, msb first)
|
||||
The system stores this 32-bit word value in memory in little-endian format on the 6502 targets
|
||||
(lsb first, then b1, b2 and finally the msb); the m68k targets store it big-endian (msb first).
|
||||
|
||||
:index:`mklong2` (msw, lsw)
|
||||
Efficiently create a long value from two words (the msw, and the lsw). Avoids multiplication and shifting.
|
||||
@@ -274,9 +272,9 @@ Miscellaneous
|
||||
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)
|
||||
On M68k targets, the extend (X) bit substitutes the carry flag for rotates
|
||||
(because M68k has separate C and X flags; ``set_carry`` and ``clear_carry`` manage both).
|
||||
Essentially, it is a 9-bit or 17-bit rotation.
|
||||
On M68k targets, the extend (X) bit substitutes the carry flag for rotates, because M68k has
|
||||
separate C and X flags; ``set_carry`` and ``clear_carry`` manage both.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
You can rol a memory location directly by using the direct memory access syntax, so like ``rol(@($5000))``
|
||||
You can use ``if_cc`` or ``if_cs`` after a rol to act on the new carry bit, if required.
|
||||
@@ -291,9 +289,9 @@ Miscellaneous
|
||||
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)
|
||||
On M68k targets, the extend (X) bit substitutes the carry flag for rotates
|
||||
(because M68k has separate C and X flags; ``set_carry`` and ``clear_carry`` manage both).
|
||||
Essentially, it is a 9-bit or 17-bit rotation.
|
||||
On M68k targets, the extend (X) bit substitutes the carry flag for rotates, because M68k has
|
||||
separate C and X flags; ``set_carry`` and ``clear_carry`` manage both.
|
||||
Modifies in-place, doesn't return a value (so can't be used in an expression).
|
||||
You can ror a memory location directly by using the direct memory access syntax, so like ``ror(@($5000))``
|
||||
You can use ``if_cc`` or ``if_cs`` after a ror to act on the new carry bit, if required.
|
||||
@@ -1443,7 +1441,7 @@ For non-blocking sequenced playback (driven by an IRQ handler):
|
||||
|
||||
``sub set_gap(ubyte ticks)``
|
||||
Set the number of silence ticks between notes (default 1).
|
||||
``0`` means no silence gap at all — notes flow into each other seamlessly.
|
||||
``0`` means no silence gap at all — notes flow into each other without a break.
|
||||
The note-on time is automatically adjusted: ``note = duration - gap``,
|
||||
minimum 1 tick. This means changing the gap does **not** affect the
|
||||
overall tempo. Call this before ``song()``.
|
||||
|
||||
@@ -21,7 +21,7 @@ or :source:`online here <scripts/profiler.py>`).
|
||||
This script cross-references the memory stats file with an assembly listing of the program, produced by the Prog8 compiler with the ``-asmlist`` option.
|
||||
It then prints the top N lines in your (assembly) program source that perform the most reads and writes,
|
||||
which you can use to identify possible hot spots/bottlenecks/variables that should be better placed in zeropage etc.
|
||||
Note that the profiler simply works with the total number of accesses to memory locations.
|
||||
The profiler works with the total number of accesses to memory locations.
|
||||
This is *not* the same as the most run-time (cpu instructions cycle times aren't taken into account at all)!
|
||||
Here is an example of the output it generates:
|
||||
|
||||
|
||||
+30
-28
@@ -96,7 +96,7 @@ Identifiers
|
||||
|
||||
Naming things in Prog8 is done via valid *identifiers*. They start with a letter,
|
||||
and after that, a combination of letters, numbers, or underscores.
|
||||
Note that any Unicode Letter symbol is accepted as a letter!
|
||||
Any Unicode Letter symbol is accepted as a letter!
|
||||
Examples of valid identifiers::
|
||||
|
||||
a
|
||||
@@ -114,7 +114,7 @@ Examples of valid identifiers::
|
||||
.. index:: pair: Identifiers; Scoped names
|
||||
|
||||
Sometimes called "qualified names" or "dotted names", a scoped name is a sequence of identifiers separated by a dot.
|
||||
They are used to reference symbols in other scopes. Note that unlike many other programming languages,
|
||||
They are used to reference symbols in other scopes. Unlike many other programming languages,
|
||||
scoped names always need to be fully scoped (because they always start in the global scope). Also see :ref:`blocks`::
|
||||
|
||||
main.start ; the entrypoint subroutine
|
||||
@@ -179,7 +179,7 @@ the enclosing scope is searched for it, and so on, up to the top level block, un
|
||||
If the symbol was not found the compiler will issue an error message.
|
||||
|
||||
**:index:`Subroutines`** create a new scope. All variables inside a subroutine are hoisted up to the
|
||||
scope of the subroutine they are declared in. Note that you can define **nested subroutines** in Prog8,
|
||||
scope of the subroutine they are declared in. You can define **nested subroutines** in Prog8,
|
||||
and such a nested subroutine has its own scope! This also means that you have to use a fully qualified name
|
||||
to access a variable from a nested subroutine::
|
||||
|
||||
@@ -281,7 +281,7 @@ Directives
|
||||
Level: not at module scope.
|
||||
Tells the assembler to continue assembling on the given alignment interval. For example, ``%align $100``
|
||||
will insert an assembler command to align on the next page boundary.
|
||||
Note that this has no impact on variables following this directive! Prog8 reallocates all variables
|
||||
This has no impact on variables following this directive! Prog8 reallocates all variables
|
||||
using different rules. If you want to align a specific variable (array or string), you should use
|
||||
one of the alignment tags for variable declarations instead.
|
||||
Valid intervals are from 2 to 65536.
|
||||
@@ -295,7 +295,7 @@ Directives
|
||||
Level: not at module scope.
|
||||
Declares that a piece of *assembly code* is inside the curly braces.
|
||||
This code will be copied as-is into the generated output assembly source file.
|
||||
Note that the start and end markers are both *double curly braces* to minimize the chance
|
||||
The start and end markers are both *double curly braces* to minimize the chance
|
||||
that the assembly code itself contains either of those. If it does contain a ``}}``,
|
||||
it will confuse the parser.
|
||||
|
||||
@@ -308,7 +308,7 @@ Directives
|
||||
|
||||
.. caution::
|
||||
Avoid using single-letter symbols in included assembly code, as they could be confused with CPU registers.
|
||||
Also, note that all prog8 symbols are prefixed in assembly code, see :ref:`symbol-prefixing`.
|
||||
Also, all prog8 symbols are prefixed in assembly code, see :ref:`symbol-prefixing`.
|
||||
|
||||
|
||||
.. index:: pair: Directives; %asmbinary
|
||||
@@ -318,7 +318,7 @@ Directives
|
||||
This directive can only be used inside a block.
|
||||
The assembler itself will include the file as binary bytes at this point, prog8 will not process this at all.
|
||||
This means that the filename must be spelled exactly as it appears on your computer's file system.
|
||||
Note that this filename may differ in case compared to when you chose to load the file from disk from within the
|
||||
This filename may differ in case compared to when you chose to load the file from disk from within the
|
||||
program code itself (for example on the C64 and X16 there's the PETSCII encoding difference).
|
||||
The file is located relative to the current working directory!
|
||||
The optional offset and length can be used to select a particular piece of the file.
|
||||
@@ -344,7 +344,7 @@ Directives
|
||||
|
||||
.. caution::
|
||||
Avoid using single-letter symbols in included assembly code, as they could be confused with CPU registers.
|
||||
Also, note that all prog8 symbols are prefixed in assembly code, see :ref:`symbol-prefixing`.
|
||||
Also, all prog8 symbols are prefixed in assembly code, see :ref:`symbol-prefixing`.
|
||||
|
||||
Here is a small example program to show how to use labels to reference the included contents from prog8 code::
|
||||
|
||||
@@ -665,7 +665,8 @@ value. Range expressions used outside a direct ``for`` loop still require a con
|
||||
.. attention::
|
||||
The value of the loop variable after executing the loop *is undefined* - you cannot rely
|
||||
on it to be the last value in the range for instance! The value of the variable should only be used inside the for loop body.
|
||||
(this is an optimization issue to avoid having to deal with mostly useless post-loop logic to adjust the loop variable's value)
|
||||
This is an optimization issue, to avoid having to deal with mostly useless post-loop logic to adjust
|
||||
the loop variable's value.
|
||||
Do not modify the loop variable inside the loop body. The compiler assumes exclusive
|
||||
control over the loop variable; changing it can cause incorrect iteration counts or
|
||||
infinite loops. If you need to alter the iteration, use a ``while`` loop instead.
|
||||
@@ -968,7 +969,7 @@ on .. goto / on .. call statement (jump table)
|
||||
.. index:: single: Conditionals; on goto/call statement
|
||||
|
||||
The ``on goto / call`` statement is suitable to create a fast call of a subroutine from a list based on an index value.
|
||||
it selects a function to jump to in O(1) whereas a similar when-statement, runs in O(n) because that one checks each index value.
|
||||
It selects a function to jump to in O(1), whereas a similar when-statement runs in O(n) because that one checks each index value.
|
||||
The ``on goto / call`` instead simply gets the correct entry from an array of function pointers and jumps to it directly.
|
||||
The index value that is used is 0-based; 0 will jump to the first entry in the list, 1 to the second, and so on.
|
||||
If the value is too large (i.e. outside the list of functions), no call is performed, and execution continues.
|
||||
@@ -1008,7 +1009,7 @@ compile-time constant integers (bytes or words). They also have to be the same
|
||||
datatype as the when-value, otherwise no efficient comparison can be done.
|
||||
You can explicitly put a list of numbers that all should result in the same case,
|
||||
or even use any *range expression* as long as it denotes a constant list of numbers.
|
||||
Be aware that every number is compared individually so using long lists of numbers and/or
|
||||
Be careful: every number is compared individually so using long lists of numbers and/or
|
||||
many choice cases will result in poor performance. If you need to call a certain function
|
||||
based on some sequential index number, look at the ``on .. goto / call`` statement instead.
|
||||
|
||||
@@ -1173,7 +1174,7 @@ The ``swap`` keyword swaps the values in two variables without the need of a tem
|
||||
|
||||
swap(var1, var2)
|
||||
|
||||
Supports booleans and all other numeric datatypes including pointers. Note that complicated expressions
|
||||
Supports booleans and all other numeric datatypes including pointers. Complicated expressions
|
||||
that you want to swap may not be implemented yet. To avoid such errors you'll have to swap them
|
||||
in the old fashioned way (using a temporary variable), until an optimized code path gets implemented
|
||||
in a future Prog8 version.
|
||||
@@ -1260,17 +1261,17 @@ arithmetic: ``+`` ``-`` ``*`` ``/`` ``%``
|
||||
``/`` is division (will result in integer division when using on integer operands, and a floating point division when at least one of the operands is a float)
|
||||
``%`` is the remainder operator: ``25 % 7`` is 4. Be careful: without a space after the %, it will be parsed as a binary number.
|
||||
So ``25 %10`` will be parsed as the number 25 followed by the binary number 2, which is a syntax error.
|
||||
Note that remainder is only supported on integer operands (not floats).
|
||||
Remainder is only supported on integer operands (not floats).
|
||||
|
||||
bitwise arithmetic: ``&`` ``|`` ``^`` ``~`` ``<<`` ``>>``
|
||||
``&`` is bitwise and, ``|`` is bitwise or, ``^`` is bitwise xor, ``~`` is bitwise invert (this one is an unary operator)
|
||||
``<<`` is bitwise left shift and ``>>`` is bitwise right shift (both will not change the datatype of the value)
|
||||
While the operands can be signed integers (the expression will just consider the underlying bit patterns),
|
||||
the result value of a bitwise expression is always unsigned.
|
||||
The operands can be signed integers; the expression just considers the underlying bit patterns.
|
||||
The result value of a bitwise expression is always unsigned.
|
||||
|
||||
assignment: ``=``
|
||||
Sets the target on the LHS (left hand side) of the operator to the value of the expression on the RHS (right hand side).
|
||||
Note that an assignment sometimes is not possible or supported.
|
||||
An assignment is sometimes not possible or supported.
|
||||
It's possible to chain assignments like ``x = y = z = 42`` as a shorthand for the three assignments with the same value.
|
||||
|
||||
augmented assignment: ``+=`` ``-=`` ``*=`` ``/=`` ``&=`` ``|=`` ``^=`` ``<<=`` ``>>=``
|
||||
@@ -1331,7 +1332,7 @@ address of: ``&``, ``&<``, ``&>``, ``&&``
|
||||
in the :ref:`variables` chapter.
|
||||
|
||||
``&<`` and ``&>`` are for use on split word arrays, they give you the address of the LSB byte array
|
||||
and MSB byte array separately, respectively. Note that ``&<`` is just the same as ``&`` in this case.
|
||||
and MSB byte array separately, respectively. ``&<`` is the same as ``&`` in this case.
|
||||
For more details on split word arrays, see :ref:`arrayvars`.
|
||||
Split word arrays (and with them the ``&>`` operator) don't exist on the m68k targets; there ``&>``
|
||||
is an error.
|
||||
@@ -1345,7 +1346,7 @@ address of: ``&``, ``&<``, ``&>``, ``&&``
|
||||
|
||||
type cast: ``as``
|
||||
Explicitly convert an expression to another data type.
|
||||
Note that ``as`` has very low precedence, lower than most other operators.
|
||||
``as`` has very low precedence, lower than most other operators.
|
||||
This means that ``a + b as long`` is parsed as ``(a + b) as long``.
|
||||
If you want to cast an operand before an operation, use parentheses: ``(a as long) * b``.
|
||||
|
||||
@@ -1584,7 +1585,7 @@ flag such as Carry (Pc).
|
||||
|
||||
.. note::
|
||||
Asmsubs can also be tagged as ``inline asmsub`` to make trivial pieces of assembly inserted
|
||||
directly instead of a call to them. Note that it is literal copy-paste of code that is done,
|
||||
directly instead of a call to them. It is literal copy-paste of code that is done,
|
||||
so make sure the assembly is actually written to behave like such - which probably means you
|
||||
don't want a ``rts`` or ``jmp`` or ``bra`` in it!
|
||||
Inlining may increase code size significantly and can only be used in limited scenarios
|
||||
@@ -1593,8 +1594,9 @@ flag such as Carry (Pc).
|
||||
The **sixteen 'virtual' 16-bit registers** from the Commander X16 can also be specified as ``R0`` .. ``R15`` .
|
||||
This means you don't have to set them up manually before calling a subroutine that takes
|
||||
one or more parameters in those 'registers'. You can just list the arguments directly.
|
||||
*This also works on the other compilation targets!* (however they might not be as efficient there as on the X16,
|
||||
because on most other targets such as the C64, these registers are not placed in zeropage due to lack of space)
|
||||
*This also works on the other compilation targets!* However, they might not be as efficient there as on
|
||||
the X16, because on most other targets such as the C64, these registers are not placed in zeropage due
|
||||
to lack of space.
|
||||
In both regular **prog8** *and* **assembly** code these 'registers' are directly accessible too via:
|
||||
|
||||
- ``cx16.r0`` - ``cx16.r15`` (memory-mapped **uword** values, most often these are used)
|
||||
@@ -1652,7 +1654,7 @@ and returning stuff in several registers as well. The ``clobbers`` clause is use
|
||||
are clobbered by the call instead of being unchanged or returning a meaningful result value.
|
||||
This register clobber information currently is only for documentation purposes.
|
||||
|
||||
Note that the address ($ffd5 in the example above) can actually be an expression as long as it is a compile time constant. This can
|
||||
The address ($ffd5 in the example above) can actually be an expression as long as it is a compile time constant. This can
|
||||
make it easier to define jump tables for example, like this::
|
||||
|
||||
const uword APIBASE = $8000
|
||||
@@ -1692,7 +1694,7 @@ Otherwise the compiler will warn you about discarding the result of the call.
|
||||
There is no guarantee of a left-to-right or right-to-left evaluation of the call arguments.
|
||||
|
||||
.. caution::
|
||||
Note that due to the way parameters are processed by the compiler,
|
||||
Due to the way parameters are processed by the compiler,
|
||||
subroutines are *non-reentrant*. This means you cannot create *recursive calls* (routines calling themselves)
|
||||
without doing some manual work to save and restore the variables that need to retain their value between calls.
|
||||
If you really need a recursive algorithm, there are a few options:
|
||||
@@ -1775,11 +1777,11 @@ Deferred ("cleanup") code
|
||||
.. index:: pair: Subroutines; Deferred code
|
||||
|
||||
Usually when a subroutine exits, it has to clean up things that it worked on. For example, it has to close
|
||||
a file that it opened before to read data from, or it has to free a piece of memory that it allocated via
|
||||
a dynamic memory allocation library, etc.
|
||||
Every spot where the subroutine exits (return statement, jump, or the end of the routine) you have to take care
|
||||
of doing the cleanups required. This can get tedious, and the cleanup code is separated from the place where
|
||||
the resource allocation was done at the start.
|
||||
a file it opened earlier to read data from, or free a piece of memory it allocated via a dynamic memory
|
||||
allocation library.
|
||||
Every exit of the subroutine (a return statement, a jump, or the end of the routine) needs the required
|
||||
cleanups. This can get tedious, and the cleanup code is separated from the place where the resource
|
||||
allocation was done at the start.
|
||||
|
||||
The ``defer`` keyword can be used to schedule a statement (or block of statements) to be executed
|
||||
just before exiting of the current subroutine. That can be via a return statement or a jump to somewhere else,
|
||||
|
||||
@@ -94,7 +94,7 @@ So the syntax for declaring typed pointers looks like this:
|
||||
|
||||
``^^type[size]``: array with size size containing pointers to a type.
|
||||
So for example; ``^^word[100] values`` declares values to be an array of 100 pointers to words.
|
||||
Note that an array of pointers (regardless of the type they point to) is always a split word array.
|
||||
An array of pointers (regardless of the type they point to) is always a split word array.
|
||||
(this is the most efficient way to access the pointers, and they need to be copied to zeropage first to
|
||||
be able to use them anyway. It also allows for arrays of up to 256 pointers instead of 128.)
|
||||
|
||||
@@ -129,11 +129,13 @@ For example, to advance a pointer to the next value, you can use ``pointer++``.
|
||||
To make it point to the preceding value, you can use ``pointer--``.
|
||||
**Adding or subtracting X to a pointer will change the pointer by X times the size of the value it points at (the same as the C language does it),
|
||||
instead of simply adding or subtracting the value from the pointer address value.**
|
||||
(that is what Prog8 still does for untyped uword pointers, or pointers to a type that just takes up a single byte of memory).
|
||||
That is what Prog8 still does for untyped uword pointers, or pointers to a type that just takes up
|
||||
a single byte of memory.
|
||||
|
||||
That special pointer arithmetic is also performed for pointers to struct types:
|
||||
the compiler knows the memory storage size of the whole struct type and advances or rewinds
|
||||
the pointer value (memory address) by the appropriate number of bytes (X times the size of the struct). More info about structs can be found below.
|
||||
the pointer value (memory address) by the appropriate number of bytes (X times the size of the struct).
|
||||
More info about structs can be found below.
|
||||
|
||||
You can subtract two pointers of the **same** type from each other. The result is the number of elements between them (C-style),
|
||||
using the same scaling rules as adding or subtracting an integer. The result has the target's address type (``uword`` on 16-bit targets, ``long`` on 32-bit targets).
|
||||
@@ -318,7 +320,7 @@ Typical uses:
|
||||
- **Hardware or memory-mapped registers:** one location that the hardware interprets as a byte or a word depending on how it is accessed.
|
||||
- **Overlays:** reinterpret a buffer, such as the raw bytes of a ``float``, for debugging or serialization, or reuse the same scratch space between different phases of a program.
|
||||
|
||||
Note that unions do not (yet) help save space inside structs, because nesting a union (or struct) as a field is not allowed; see above.
|
||||
Unions do not (yet) help save space inside structs, because nesting a union (or struct) as a field is not allowed; see above.
|
||||
|
||||
Because all fields overlap, static initialization with values is not allowed.
|
||||
The only valid initializer is the empty form ``[]``, which places a zero-initialized union instance in BSS::
|
||||
@@ -380,9 +382,12 @@ Initializing byte-array fields with a string
|
||||
|
||||
For a sized ``ubyte`` (or ``byte``) array field, a string literal may be used in place of an explicit
|
||||
byte array. The string is encoded using the current ``%encoding`` and then filled into the array using
|
||||
C-style semantics: if the encoded string is shorter than the array, the remaining bytes are filled with
|
||||
zero (the first zero acts as the implicit C string terminator); if it is exactly the array length, no
|
||||
extra terminator is added; and if it is longer than the array, a compilation error is reported.
|
||||
C-style semantics:
|
||||
|
||||
- if the encoded string is shorter than the array, the remaining bytes are filled with zero
|
||||
(the first zero acts as the implicit C string terminator)
|
||||
- if it is exactly the array length, no extra terminator is added
|
||||
- if it is longer than the array, a compilation error is reported
|
||||
|
||||
For example, given ``ubyte[4] name``, the initializers ``"abcd"``, ``"abc"`` and ``"a"`` are all
|
||||
accepted and produce ``[a,b,c,d]``, ``[a,b,c,0]`` and ``[a,0,0,0]`` respectively, while ``"abcde"``
|
||||
|
||||
@@ -56,7 +56,7 @@ If it matches one of the existing built-in compilation targets, the internal lib
|
||||
if the user supplied library location doesn't contain a replacement library file for anything that might get imported.
|
||||
The path you need to provide for the ``library`` variable can be relative (to the current working directory where you launch the compiler from)
|
||||
and you can use a tilde ``~`` in it like in a shell path, to refer to a user's home directory.
|
||||
Note that library modules not unique to a specific compilation target (for example, `buffers`, `sorting` or `strings`) will
|
||||
Library modules not unique to a specific compilation target (for example, `buffers`, `sorting` or `strings`) will
|
||||
be picked up from the internal library files just fine as was always the case. You can still provide custom versions of them
|
||||
in your own library folder of course, like you already could with using the ``-srcdirs`` compiler flag.
|
||||
|
||||
@@ -151,9 +151,9 @@ A minimal script for a custom target looks like this::
|
||||
|
||||
``ENTRY`` should point at ``prog8_program_start``, the label the compiler emits for the start of
|
||||
the program. The ``.data`` and ``.bss`` sections are deliberately merged into a single output
|
||||
section so that the linker places them in one load segment: if ``.bss`` were its own output section,
|
||||
section so that the linker places them in one load segment. If ``.bss`` were its own output section,
|
||||
the linker would compute that segment's address as "end of the previous segment + alignment" instead
|
||||
of honouring the address in the script, and ``prog8_program_end`` would no longer point at the true
|
||||
of honouring the address in the script. Then ``prog8_program_end`` would no longer point at the true
|
||||
end of the loaded image. For the same reason ``prog8_program_end`` is defined by the linker script
|
||||
rather than by the generated assembly. Startup code that needs to clear the bss, such as the
|
||||
``qemu68k`` bootinfo support, references that symbol, so leave it in.
|
||||
@@ -165,9 +165,9 @@ Source level restrictions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The m68k code generator rejects a number of directives and options that only make sense for a
|
||||
6502 target: the ``%launcher``, ``%address``, ``%memtop`` and ``%varsaddress`` directives, register
|
||||
annotations on ordinary subroutine parameters (they are only used in ``asmsub``), split word arrays
|
||||
written as ``&>``, and specific block addresses. The ``-varsgolden``, ``-varshigh`` and
|
||||
6502 target: the ``%launcher``, ``%address``, ``%memtop`` and ``%varsaddress`` directives, split word arrays
|
||||
written as ``&>``, and specific block addresses. Register annotations on ordinary subroutine parameters
|
||||
are rejected as well; those are only used in ``asmsub``. The ``-varsgolden``, ``-varshigh`` and
|
||||
``-varsaddress`` command line options are rejected as well, so where the variables are placed is
|
||||
decided by the linker script instead.
|
||||
|
||||
@@ -470,17 +470,16 @@ Other targets may use a different vector or may not support bare handlers at all
|
||||
|
||||
.. caution::
|
||||
It is advised to **not use floating point calculations** inside IRQ handler routines.
|
||||
Beside them being very slow, there are intricate requirements such as having the
|
||||
correct ROM bank enabled to be able to successfully call them (and making sure the correct
|
||||
ROM bank is reset at the end of the handler), and the possibility
|
||||
of corrupting variables and floating point calculations that are being executed
|
||||
in the interrupted main program. These memory locations should be backed up
|
||||
and restored at the end of the handler, further increasing its execution time...
|
||||
They are very slow, and the correct ROM bank must be enabled to call them. Make sure the correct
|
||||
ROM bank is reset at the end of the handler as well.
|
||||
There is also the possibility of corrupting variables and floating point calculations
|
||||
that are being executed in the interrupted main program. These memory locations should be
|
||||
backed up and restored at the end of the handler, further increasing its execution time...
|
||||
|
||||
.. caution::
|
||||
The Commander X16's sixteen 'virtual registers' R0-R15 *are not preserved* in the IRQ handler! (On any system!)
|
||||
So you should make sure that the handler routine does NOT use these registers, or do some sort of saving/restoring yourself
|
||||
of the ones that you do need in the IRQ handler. Note that Prog8 itself may also use these registers, so be very careful.
|
||||
of the ones that you do need in the IRQ handler. Prog8 itself may also use these registers, so be very careful.
|
||||
This is not a X16 specific thing; these registers also exist on the other compiler targets, and the same
|
||||
issue holds there.
|
||||
|
||||
@@ -495,7 +494,7 @@ Commander X16 specific IRQ handling
|
||||
===================================
|
||||
.. index:: single: Targets; Commander X16 IRQ Handling
|
||||
|
||||
Note that for the CommanderX16 the set_rasterirq() will disable VSYNC irqs and never call the system IRQ handler regardless
|
||||
Be careful: for the CommanderX16, set_rasterirq() will disable VSYNC irqs and never call the system IRQ handler regardless
|
||||
of the return value of the user handler routine. This also means the default sys.wait() routine won't work anymore,
|
||||
when using this handler.
|
||||
|
||||
@@ -529,7 +528,7 @@ Look at the examples/cx16/multi-irq-new.p8 example to see how these routines can
|
||||
Here they are, all available in ``cx16``:
|
||||
|
||||
``disable_irqs ()``
|
||||
Disables all Vera IRQ sources. Note that the CPU irq disable flag is not changed by this routine.
|
||||
Disables all Vera IRQ sources. The CPU irq disable flag is not changed by this routine;
|
||||
you can manipulate that via ``sys.set_irqd()`` and ``sys.clear_irqd()`` as usual.
|
||||
|
||||
``enable_irq_handlers (bool disable_all_irq_sources)``
|
||||
@@ -569,4 +568,4 @@ And a utility method to set the VIA1 timer1 to trigger IRQs at regular intervals
|
||||
Set VIA1 timer1 to trigger after the given delay (cycles).
|
||||
Enables VIA timer1 irqs if delay>0, otherwise disables it.
|
||||
If keeprunning then the timer keeps triggering, otherwise it stops after a single trigger.
|
||||
Note that the speed of the timer depends on the clock speed of the X16 (controlled by a jumper on the motherboard, usually 8 MHz).
|
||||
The speed of the timer depends on the clock speed of the X16 (controlled by a jumper on the motherboard, usually 8 MHz).
|
||||
@@ -103,7 +103,7 @@ What they are intended for
|
||||
Without these IDs, the bank selector would only know that *some* routine is being called,
|
||||
but it wouldn't know which one unless you had a separate manager routine for every single banked subroutine.
|
||||
|
||||
Note that the same banked subroutine called from multiple different places in your code will currently
|
||||
The same banked subroutine called from multiple different places in your code will currently
|
||||
result in the *same* ID being passed to the banking routine, as the ID is unique per ``extsub`` declaration.
|
||||
|
||||
Viewing assigned IDs
|
||||
@@ -167,7 +167,7 @@ The bank number is not translated into assembly (only as a comment)::
|
||||
On the C128, it means programs can use ~41 Kb of contiguous RAM at $1c00 to $c000 (exclusive).
|
||||
However, if your program uses floats, Prog8 *does* need the Basic ROM for the floating point routines,
|
||||
and it won't be banked out. Such programs are limited to the regular size of about 38 Kb on the C64, and less on the C128.
|
||||
Be aware that the bank setting is only done if you are *not* using ``%option no_sysinit`` because the
|
||||
Be aware: the bank setting is only done if you are *not* using ``%option no_sysinit`` because the
|
||||
program's bootstrap code is what initializes the memory bank configuration.
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ Regular subroutines
|
||||
For *single* byte, word, and pointer arguments (not long or float), the values are simply loaded in cpu registers by the caller before calling the subroutine.
|
||||
*The subroutine itself will take care of putting the values into the parameter variables.* This saves on code size because
|
||||
otherwise all callers would have to store the values in those variables themselves.
|
||||
Note that this convention is also still used for subroutines that specify parameters to be put into
|
||||
This convention is also still used for subroutines that specify parameters to be put into
|
||||
one of the *virtual registers* R0-R15, as those are in the end just variables too (see :ref:`reusevirtualregs_params`)
|
||||
The rules are as follows:
|
||||
|
||||
@@ -295,7 +295,7 @@ In case of *multiple* return values:
|
||||
|
||||
These are kernal (ROM) routines or low-level assembly routines, that get their arguments via specific registers.
|
||||
Sometimes even via a processor status flag such as the Carry flag.
|
||||
Note that word values can be put in a "CPU register pair" such as AY (meaning A+Y registers) but also
|
||||
Word values can be put in a "CPU register pair" such as AY (meaning A+Y registers) but also
|
||||
in one of the 16 'virtual' 16 bit registers introduced by the Commander X16, R0-R15.
|
||||
Float values can be put in the FAC1 or FAC2 floating point 'registers'.
|
||||
The return values also get returned via designated registers, or via processor status flags again.
|
||||
@@ -336,7 +336,7 @@ Some notes and references into the compiler's source code modules:
|
||||
is more or less a machine code language for a virtual machine - and indeed this is what the built-in
|
||||
prog8 VM will execute if you use the 'virtual' compilation target and use ``-emu`` to launch the VM.
|
||||
(``intermediate`` and ``codeGenIntermediate`` modules, and ``virtualmachine`` module for the VM related stuff)
|
||||
Note that this IR is still *targeted to one specific compilation target only*; various properties and all library
|
||||
This IR is still *targeted to one specific compilation target only*; various properties and all library
|
||||
code for the selected target machine is encoded into the IR. It is *not possible* to eventually create a C64 program
|
||||
from an IR file created for the CommanderX16 target.
|
||||
|
||||
|
||||
+24
-19
@@ -223,7 +223,7 @@ 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.
|
||||
Note that a subset of the library routines in the ``math``, ``strings`` and ``floats`` modules are recognised in
|
||||
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:
|
||||
@@ -310,7 +310,7 @@ Integers (bytes, words, longs)
|
||||
|
||||
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. Note that ``%`` is also the remainder operator so be careful: if you want to take the remainder
|
||||
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.
|
||||
|
||||
@@ -390,8 +390,8 @@ Floating point support is available on the c64 and cx16 (and virtual) compiler t
|
||||
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 (because the c128 fp routines require the fp variables
|
||||
to be in another ram bank than the program, which Prog8 doesn't support yet).
|
||||
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.
|
||||
@@ -415,9 +415,10 @@ Arrays
|
||||
^^^^^^
|
||||
.. index:: pair: Data Types; Arrays
|
||||
|
||||
Arrays can be created from a list of booleans, bytes, words, floats, addresses of other variables
|
||||
(such as explicit address-of expressions, strings, or other array variables), pointers, struct initializers,
|
||||
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::
|
||||
@@ -439,10 +440,14 @@ or when adding more stuff to the array later. Here are some examples of arrays::
|
||||
|
||||
.. 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), and
|
||||
float arrays to 51 elements.
|
||||
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).
|
||||
@@ -460,7 +465,7 @@ need this you have to write it out depending on the use case: you can copy the m
|
||||
``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.
|
||||
|
||||
Note that the various keywords for the data type and variable type (``byte``, ``word``, ``const``, etc.)
|
||||
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.
|
||||
@@ -575,12 +580,12 @@ code that expects the words to be sequentially in memory (such as the cx16.FB_se
|
||||
you have to create a normal array variable first and then pass that to the subroutine.
|
||||
|
||||
.. caution::
|
||||
Be aware that the default is to split word arrays. Normal array access is taken care of by Prog8, so you won't
|
||||
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 make sure 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!): you have to make sure
|
||||
the library routine can deal with the split array otherwise you have to use ``@nosplit`` as well.
|
||||
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:
|
||||
@@ -648,9 +653,9 @@ There are several escape sequences available to put special characters into your
|
||||
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)
|
||||
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,
|
||||
@@ -774,7 +779,7 @@ without defining a memory-mapped location, you can do so by enclosing the addres
|
||||
@(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. Note that unlike regular arrays,
|
||||
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.
|
||||
|
||||
@@ -43,7 +43,7 @@ sealed interface CallTarget {
|
||||
|
||||
/** where an argument comes from, or where a result is delivered */
|
||||
sealed interface CallLocation {
|
||||
/** no explicit location: value simply lives in the given virtual register */
|
||||
/** no explicit location: value lives in the given virtual register */
|
||||
data object Default : CallLocation
|
||||
|
||||
/** the value is passed via the memory location of a subroutine parameter variable */
|
||||
|
||||
@@ -13,8 +13,8 @@ import prog8.vm.VirtualMachine
|
||||
/**
|
||||
* Every VM instruction handler must read exactly the operands that the OpcodeSchema declares for
|
||||
* that (opcode, type) combination. This test builds a canonical instruction for every combination
|
||||
* straight from the schema and executes it, so a handler that reaches for an operand slot the
|
||||
* instruction doesn't have (or casts a memory reference to the wrong kind) fails here.
|
||||
* straight from the schema and executes it. A handler that reaches for an operand slot the
|
||||
* instruction doesn't have, or casts a memory reference to the wrong kind, fails here.
|
||||
*/
|
||||
class TestVmOperandSchema: FunSpec({
|
||||
|
||||
@@ -167,7 +167,7 @@ class TestVmOperandSchema: FunSpec({
|
||||
val instruction = buildInstruction(opcode, type)
|
||||
try {
|
||||
runSingle(instruction)
|
||||
} catch (ex: ProgramExitException) {
|
||||
} catch (_: ProgramExitException) {
|
||||
// fine: return/exit style instruction ran to completion
|
||||
} catch (ex: NullPointerException) {
|
||||
operandProblems += "$instruction -> NPE (operand assumed present): ${ex.message}"
|
||||
|
||||
Reference in new issue
Block a user