Get rid of sort() and reverse() builtin functions.

Sort() had too many gotchas and reverse() is kinda redundant you can loop in decreasing order through an array too.
This commit is contained in:
Irmen de Jong 2024-07-06 17:07:58 +02:00
parent 0c053e4a2c
commit 25f25a8767
17 changed files with 217 additions and 12776 deletions

View File

@ -76,8 +76,6 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
"ror" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"rol2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"ror2" to FSignature(false, listOf(FParam("item", arrayOf(DataType.UBYTE, DataType.UWORD))), null),
"sort" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
"reverse" to FSignature(false, listOf(FParam("array", ArrayDatatypes)), null),
// cmp returns a status in the carry flag, but not a proper return value
"cmp" to FSignature(false, listOf(FParam("value1", IntegerDatatypes), FParam("value2", NumericDatatypes)), null),
"prog8_lib_stringcompare" to FSignature(true, listOf(FParam("str1", arrayOf(DataType.STR)), FParam("str2", arrayOf(DataType.STR))), DataType.BYTE),
@ -136,6 +134,5 @@ val BuiltinFunctions: Map<String, FSignature> = mapOf(
val InplaceModifyingBuiltinFunctions = setOf(
"setlsb", "setmsb",
"rol", "ror", "rol2", "ror2",
"sort", "reverse",
"divmod", "divmod__ubyte", "divmod__uword"
)

View File

@ -44,8 +44,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
"ror2" -> funcRor2(fcall)
"setlsb" -> funcSetLsbMsb(fcall, false)
"setmsb" -> funcSetLsbMsb(fcall, true)
"sort" -> funcSort(fcall)
"reverse" -> funcReverse(fcall)
"memory" -> funcMemory(fcall, discardResult, resultRegister)
"peekw" -> funcPeekW(fcall, resultRegister)
"peekf" -> funcPeekF(fcall, resultRegister)
@ -403,115 +401,6 @@ internal class BuiltinFunctionsAsmGen(private val program: PtProgram,
}
}
private fun funcReverse(fcall: PtBuiltinFunctionCall) {
val variable = fcall.args.single() as PtIdentifier
val symbol = asmgen.symbolTable.lookup(variable.name)
val (dt, numElements) = when(symbol) {
is StStaticVariable -> symbol.dt to symbol.length!!
is StMemVar -> symbol.dt to symbol.length!!
else -> DataType.UNDEFINED to 0
}
val varName = asmgen.asmVariableName(variable)
when (dt) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_b""")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_w""")
}
DataType.STR -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #${numElements-1}
jsr prog8_lib.func_reverse_b""")
}
DataType.ARRAY_F -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr floats.func_reverse_f""")
}
in SplitWordArrayTypes -> {
// reverse the lsb and msb arrays both, independently
asmgen.out("""
lda #<${varName}_lsb
ldy #>${varName}_lsb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_b
lda #<${varName}_msb
ldy #>${varName}_msb
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements
jsr prog8_lib.func_reverse_b""")
}
else -> throw AssemblyError("weird type")
}
}
private fun funcSort(fcall: PtBuiltinFunctionCall) {
val variable = fcall.args.single() as PtIdentifier
val symbol = asmgen.symbolTable.lookup(variable.name)
val varName = asmgen.asmVariableName(variable)
val (dt, numElements) = when(symbol) {
is StStaticVariable -> symbol.dt to symbol.length!!
is StMemVar -> symbol.dt to symbol.length!!
else -> DataType.UNDEFINED to 0
}
when (dt) {
DataType.ARRAY_UB, DataType.ARRAY_B -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements""")
asmgen.out(if (dt == DataType.ARRAY_UB) " jsr prog8_lib.func_sort_ub" else " jsr prog8_lib.func_sort_b")
}
DataType.ARRAY_UW, DataType.ARRAY_W -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #$numElements""")
asmgen.out(if (dt == DataType.ARRAY_UW) " jsr prog8_lib.func_sort_uw" else " jsr prog8_lib.func_sort_w")
}
DataType.STR -> {
asmgen.out("""
lda #<$varName
ldy #>$varName
sta P8ZP_SCRATCH_W1
sty P8ZP_SCRATCH_W1+1
lda #${numElements-1}
jsr prog8_lib.func_sort_ub""")
}
DataType.ARRAY_F -> throw AssemblyError("sorting of floating point array is not supported")
in SplitWordArrayTypes -> TODO("split words sort")
else -> throw AssemblyError("weird type")
}
}
private fun funcRor2(fcall: PtBuiltinFunctionCall) {
val what = fcall.args.single()
when (what.type) {

View File

@ -34,8 +34,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
"clamp__byte", "clamp__ubyte", "clamp__word", "clamp__uword" -> funcClamp(call)
"min__byte", "min__ubyte", "min__word", "min__uword" -> funcMin(call)
"max__byte", "max__ubyte", "max__word", "max__uword" -> funcMax(call)
"sort" -> funcSort(call)
"reverse" -> funcReverse(call)
"setlsb" -> funcSetLsbMsb(call, false)
"setmsb" -> funcSetLsbMsb(call, true)
"rol" -> funcRolRor(call)
@ -369,65 +367,6 @@ internal class BuiltinFuncGen(private val codeGen: IRCodeGen, private val exprGe
}
}
private fun funcReverse(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val lengthReg = codeGen.registers.nextFree()
val result = mutableListOf<IRCodeChunkBase>()
if(arrayName.type in SplitWordArrayTypes) {
// reverse the lsb and msb arrays both, independently
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val trLsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_lsb", DataType.ARRAY_UB, call.position))
addToResult(result, trLsb, trLsb.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(IMSyscall.REVERSE_BYTES, listOf(IRDataType.WORD to trLsb.resultReg, IRDataType.BYTE to lengthReg), null)
val trMsb = exprGen.translateExpression(PtIdentifier(arrayName.name+"_msb", DataType.ARRAY_UB, call.position))
addToResult(result, trMsb, trMsb.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(IMSyscall.REVERSE_BYTES, listOf(IRDataType.WORD to trMsb.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
val syscall =
when(arrayName.type) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.STR -> IMSyscall.REVERSE_BYTES
DataType.ARRAY_UW, DataType.ARRAY_W -> IMSyscall.REVERSE_WORDS
DataType.ARRAY_F -> IMSyscall.REVERSE_FLOATS
else -> throw IllegalArgumentException("weird type to reverse")
}
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcSort(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val arrayName = call.args[0] as PtIdentifier
val arrayLength = codeGen.symbolTable.getLength(arrayName.name)
val syscall =
when(arrayName.type) {
DataType.ARRAY_UB -> IMSyscall.SORT_UBYTE
DataType.ARRAY_B -> IMSyscall.SORT_BYTE
DataType.ARRAY_UW -> IMSyscall.SORT_UWORD
DataType.ARRAY_W -> IMSyscall.SORT_WORD
DataType.STR -> IMSyscall.SORT_UBYTE
DataType.ARRAY_F -> throw IllegalArgumentException("sorting a floating point array is not supported")
in SplitWordArrayTypes -> TODO("split word sort")
else -> throw IllegalArgumentException("weird type to sort")
}
val result = mutableListOf<IRCodeChunkBase>()
addInstr(result, IRInstruction(Opcode.PREPARECALL, immediate = 2), null)
val tr = exprGen.translateExpression(arrayName)
addToResult(result, tr, tr.resultReg, -1)
val lengthReg = codeGen.registers.nextFree()
addInstr(result, IRInstruction(Opcode.LOAD, IRDataType.BYTE, reg1 = lengthReg, immediate = if(arrayName.type==DataType.STR) arrayLength!!-1 else arrayLength), null)
result += codeGen.makeSyscall(syscall, listOf(IRDataType.WORD to tr.resultReg, IRDataType.BYTE to lengthReg), null)
return ExpressionCodeResult(result, IRDataType.BYTE, -1, -1)
}
private fun funcMkword(call: PtBuiltinFunctionCall): ExpressionCodeResult {
val result = mutableListOf<IRCodeChunkBase>()
val resultReg = codeGen.registers.nextFree()

View File

@ -6,108 +6,6 @@ func_sign_f_into_A .proc
jmp SIGN
.pend
func_swap_f .proc
; -- swap floats pointed to by SCRATCH_ZPWORD1, SCRATCH_ZPWORD2
ldy #4
- lda (P8ZP_SCRATCH_W1),y
pha
lda (P8ZP_SCRATCH_W2),y
sta (P8ZP_SCRATCH_W1),y
pla
sta (P8ZP_SCRATCH_W2),y
dey
bpl -
rts
.pend
func_reverse_f .proc
; --- reverse an array of floats (array in P8ZP_SCRATCH_W1, num elements in A)
_left_index = P8ZP_SCRATCH_W2
_right_index = P8ZP_SCRATCH_W2+1
_loop_count = P8ZP_SCRATCH_REG
pha
jsr a_times_5
sec
sbc #5
sta _right_index
lda #0
sta _left_index
pla
lsr a
sta _loop_count
_loop ; push the left indexed float on the stack
ldy _left_index
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
iny
lda (P8ZP_SCRATCH_W1),y
pha
; copy right index float to left index float
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
inc _left_index
inc _right_index
ldy _right_index
lda (P8ZP_SCRATCH_W1),y
ldy _left_index
sta (P8ZP_SCRATCH_W1),y
; pop the float off the stack into the right index float
ldy _right_index
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
dey
pla
sta (P8ZP_SCRATCH_W1),y
inc _left_index
lda _right_index
sec
sbc #9
sta _right_index
dec _loop_count
bne _loop
rts
.pend
a_times_5 .proc
sta P8ZP_SCRATCH_B1

View File

@ -174,7 +174,6 @@ class TestCompilerOnExamplesBothC64andCx16: FunSpec({
"queens",
"screencodes",
"sincos",
"sorting",
"swirl",
"swirl-float",
"tehtriz",

View File

@ -688,18 +688,6 @@ main {
(statements[7] as Assignment).target.memoryAddress!!.addressExpression.constValue(result.compilerAst)!!.number shouldBe 53281.0
}
test("no crash on sorting unused array") {
val text="""
main {
ubyte[5] cards = [ 14, 6, 29, 16, 3 ]
sub start() {
sort(cards)
}
}"""
compileText(C64Target(), true, text, writeAssembly = false) shouldNotBe null
}
test("no string error when inlining") {
val text="""
main {

View File

@ -406,15 +406,6 @@ main {
ub = zero+(all(warr) as ubyte)*1+zero
txt.print_ub(ub)
txt.nl()
sort(ubarr)
sort(barr)
sort(uwarr)
sort(warr)
reverse(ubarr)
reverse(barr)
reverse(uwarr)
reverse(warr)
}
sub floatingpoint() {
@ -449,7 +440,6 @@ main {
txt.print_ub(ub)
txt.nl()
reverse(flarr)
for ub in 0 to len(flarr)-1 {
floats.print(flarr[ub])
txt.chrout(',')

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@ import prog8tests.helpers.compileText
import kotlin.io.path.readText
class TestCompilerVirtual: FunSpec({
test("compile virtual: any all sort reverse builtin funcs") {
test("compile virtual: any all builtin funcs") {
val src = """
main {
@ -29,9 +29,6 @@ main {
bool result = all(words)
cx16.r0++
result = any(words)
cx16.r0++
sort(words)
reverse(words)
}
}"""
val target = VMTarget()

View File

@ -85,7 +85,7 @@ Features
- Conditional branches for status flags that map 1:1 to processor branch instructions for optimal efficiency
- ``when`` statement to avoid if-else chains
- ``in`` expression for concise and efficient multi-value/containment test
- Several powerful built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``, ``sort`` and ``reverse``
- Several powerful built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
- Variable data types include signed and unsigned bytes and words, arrays, strings.
- Various powerful built-in libraries to do I/O, number conversions, graphics and more
- Floating point math is supported on select compiler targets.

View File

@ -1,10 +1,11 @@
===============
Library modules
===============
=====================================
Library modules and builtin functions
=====================================
The compiler provides several "built-in" library modules with useful subroutine and variables.
The compiler provides several library modules with useful subroutine and variables.
There are also a bunch of builtin functions.
Some of these may be specific for a certain compilation target, or work slightly different,
Some of the libraries may be specific for a certain compilation target, or work slightly different,
but some effort is put into making them available across compilation targets.
This means that as long as your program is only using the subroutines from these
@ -29,6 +30,199 @@ of these library modules automatically as required.
code publicly available on https://www.codebase64.org/
.. _builtinfunctions:
Built-in Functions
------------------
There's a set of predefined functions in the language. These are fixed and can't be redefined in user code.
You can use them in expressions and the compiler will evaluate them at compile-time if possible.
Math
^^^^
abs (x)
Returns the absolute value of a number (integer or floating point).
min (x, y)
Returns the smallest of x and y. Supported for integer types only, for floats use ``floats.minf()`` instead.
max (x, y)
Returns the largest of x and y. Supported for integer types only, for floats use ``floats.maxf()`` instead.
clamp (value, minimum, maximum)
Returns the value restricted to the given minimum and maximum.
Supported for integer types only, for floats use ``floats.clampf()`` instead.
sgn (x)
Get the sign of the value (integer or floating point).
The result is a byte: -1, 0 or 1 (negative, zero, positive).
sqrt (w)
Returns the square root of the number.
Supports unsigned integer (result is ubyte) and floating point numbers.
To do the reverse - squaring a number - just write ``x*x``.
divmod (dividend, divisor, quotient, remainder)
Performs division only once and returns both quotient and remainder in a single call, where using '/' and '%' separately
would perform the division operation twice.
All values are ubytes or all are uwords.
The last two arguments must be variables to receive the quotient and remainder results, respectively.
Array operations
^^^^^^^^^^^^^^^^
any (x)
true if any of the values in the array value x is 'true' (not zero), else false.
all (x)
true if all of the values in the array value x are 'true' (not zero), else false.
len (x)
Number of values in the array value x, or the number of characters in a string (excluding the 0-byte).
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte. See sizeof().
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
length of the string during execution, the value of len(s) may no longer be correct!
(use the ``string.length`` routine if you want to dynamically determine the length by counting to the
first 0-byte)
Miscellaneous
^^^^^^^^^^^^^
cmp (x,y)
Compare the integer value x to integer value y. Doesn't return a value or boolean result, only sets the processor's status bits!
You can use a conditional jumps (``if_cc`` etcetera) to act on this.
Normally you should just use a comparison expression (``x < y``)
lsb (x)
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
msb (x)
Get the most significant byte of the word x.
mkword (msb, lsb)
Efficiently create a word value from two bytes (the msb and the lsb). Avoids multiplication and shifting.
So mkword($80, $22) results in $8022.
.. note::
The arguments to the mkword() function are in 'natural' 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)
peek (address)
same as @(address) - reads the byte at the given address in memory.
peekw (address)
reads the word value at the given address in memory. Word is read as usual little-endian lsb/msb byte order.
peekf (address)
reads the float value at the given address in memory. On CBM machines, this reads 5 bytes.
poke (address, value)
same as @(address)=value - writes the byte value at the given address in memory.
pokew (address, value)
writes the word value at the given address in memory, in usual little-endian lsb/msb byte order.
pokef (address, value)
writes the float value at the given address in memory. On CBM machines, this writes 5 bytes.
pokemon (address, value)
Like poke(), but also returns the previous value in the given address.
Also doesn't have anything to do with a certain video game.
rol (x)
Rotate the bits in x (byte or word) one position to the left.
This uses the CPU's rotate semantics: bit 0 will be set to the current value of the Carry flag,
while the highest bit will become the new Carry flag value.
(essentially, it is a 9-bit or 17-bit rotation)
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can rol a memory location directly by using the direct memory access syntax, so like ``rol(@($5000))``
rol2 (x)
Like ``rol`` but now as 8-bit or 16-bit rotation.
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can rol a memory location directly by using the direct memory access syntax, so like ``rol2(@($5000))``
ror (x)
Rotate the bits in x (byte or word) one position to the right.
This uses the CPU's rotate semantics: the highest bit will be set to the current value of the Carry flag,
while bit 0 will become the new Carry flag value.
(essentially, it is a 9-bit or 17-bit rotation)
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can ror a memory location directly by using the direct memory access syntax, so like ``ror(@($5000))``
ror2 (x)
Like ``ror`` but now as 8-bit or 16-bit rotation.
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can ror a memory location directly by using the direct memory access syntax, so like ``ror2(@($5000))``
setlsb (x, value)
Sets the least significant byte of word variable x to a new value. Leaves the MSB untouched.
setmsb (x, value)
Sets the most significant byte of word variable x to a new value. Leaves the LSB untouched.
sizeof (name) ; sizeof (number)
Number of bytes that the object 'name', or the number 'number' occupies in memory.
This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
memory (name, size, alignment)
Returns the address of the first location of a statically "reserved" block of memory of the given size in bytes,
with the given name. The block is uninitialized memory, it is *not* set to zero!
If you specify an alignment value >1, it means the block of memory will
be aligned to such a dividable address in memory, for instance an alignment of $100 means the
memory block is aligned on a page boundary, and $2 means word aligned (even addresses).
Requesting the address of such a named memory block again later with
the same name, will result in the same address as before.
When reusing blocks in that way, it is required that the size argument is the same,
otherwise you'll get a compilation error.
This routine can be used to "reserve" parts of the memory where a normal byte array variable would
not suffice; for instance if you need more than 256 consecutive bytes.
The return value is just a simple uword address so it cannot be used as an array in your program.
You can only treat it as a pointer or use it in inline assembly.
call (address) -> uword
Calls a subroutine given by its memory address. You cannot pass arguments directly,
although it is ofcourse possible to do this via the global ``cx16.r0...`` registers for example.
It is assumed the subroutine returns a word value (in AY), if it does not, just add void to the call to ignore the result value.
This function effectively creates an "indirect JSR" if you use it on a ``uword`` pointer variable.
But because it doesn't handle bank switching etcetera by itself,
it is a lot faster than ``callfar``. And it works on other systems than just the Commander X16.
callfar (bank, address, argumentword) -> uword ; NOTE: specific to cx16 target for now
Calls an assembly routine in another bank on the Commander X16 (using its ``JSRFAR`` routine)
Be aware that ram OR rom bank may be changed depending on the address it jumps to!
The argumentword will be loaded into the A+Y registers before calling the routine.
The uword value that the routine returns in the A+Y registers, will be returned.
NOTE: this routine is very inefficient, so don't use it to call often. Set the bank yourself
or even write a custom tailored trampoline routine if you need to. Or use ``call`` if you can.
syscall (callnr), syscall1 (callnr, arg), syscall2 (callnr, arg1, arg2), syscall3 (callnr, arg1, arg2, arg3)
Functions for doing a system call on targets that support this. Currently no actual target
uses this though except, possibly, the experimental code generation target!
The regular 6502 based compiler targets just use a subroutine call to asmsub Kernal routines at
specific memory locations. So these builtin function calls are not useful yet except for
experimentation in new code generation targets.
rsave
Saves all registers including status (or only X) on the stack
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* saved,
but you can use ``cx16.save_virtual_registers()`` for that.
rrestore
Restore all registers including status (or only X) back from the cpu hardware stack
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* restored,
but you can use ``cx16.restore_virtual_registers()`` for that.
Low-fi variable and subroutine definitions in all available library modules
---------------------------------------------------------------------------

View File

@ -796,224 +796,8 @@ Otherwise the compiler will issue a warning about discarding a result value.
then undefined because the variables will get overwritten.
.. _builtinfunctions:
Built-in Functions
------------------
There's a set of predefined functions in the language. These are fixed and can't be redefined in user code.
You can use them in expressions and the compiler will evaluate them at compile-time if possible.
Math
^^^^
abs (x)
Returns the absolute value of a number (integer or floating point).
min (x, y)
Returns the smallest of x and y. Supported for integer types only, for floats use ``floats.minf()`` instead.
max (x, y)
Returns the largest of x and y. Supported for integer types only, for floats use ``floats.maxf()`` instead.
clamp (value, minimum, maximum)
Returns the value restricted to the given minimum and maximum.
Supported for integer types only, for floats use ``floats.clampf()`` instead.
sgn (x)
Get the sign of the value (integer or floating point).
The result is a byte: -1, 0 or 1 (negative, zero, positive).
sqrt (w)
Returns the square root of the number.
Supports unsigned integer (result is ubyte) and floating point numbers.
To do the reverse - squaring a number - just write ``x*x``.
divmod (dividend, divisor, quotient, remainder)
Performs division only once and returns both quotient and remainder in a single call, where using '/' and '%' separately
would perform the division operation twice.
All values are ubytes or all are uwords.
The last two arguments must be variables to receive the quotient and remainder results, respectively.
Array operations
^^^^^^^^^^^^^^^^
any (x)
true if any of the values in the array value x is 'true' (not zero), else false.
all (x)
true if all of the values in the array value x are 'true' (not zero), else false.
len (x)
Number of values in the array value x, or the number of characters in a string (excluding the 0-byte).
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte. See sizeof().
Note: lengths of strings and arrays are determined at compile-time! If your program modifies the actual
length of the string during execution, the value of len(s) may no longer be correct!
(use the ``string.length`` routine if you want to dynamically determine the length by counting to the
first 0-byte)
reverse (array)
Reverse the values in the array (in-place).
Can be used after sort() to sort an array in descending order.
sort (array)
Sort the array in ascending order (in-place)
Supported are arrays of bytes or word values.
Sorting a floating-point array is not supported right now, as a general sorting routine for this will
be extremely slow. Either build one yourself or find another solution that doesn't require sorting.
Finally, note that sorting an array with strings in it will not do what you might think;
it considers the array as just an array of integer words and sorts the string *pointers* accordingly.
Sorting strings alphabetically has to be programmed yourself if you need it.
Miscellaneous
^^^^^^^^^^^^^
cmp (x,y)
Compare the integer value x to integer value y. Doesn't return a value or boolean result, only sets the processor's status bits!
You can use a conditional jumps (``if_cc`` etcetera) to act on this.
Normally you should just use a comparison expression (``x < y``)
lsb (x)
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
msb (x)
Get the most significant byte of the word x.
mkword (msb, lsb)
Efficiently create a word value from two bytes (the msb and the lsb). Avoids multiplication and shifting.
So mkword($80, $22) results in $8022.
.. note::
The arguments to the mkword() function are in 'natural' 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)
peek (address)
same as @(address) - reads the byte at the given address in memory.
peekw (address)
reads the word value at the given address in memory. Word is read as usual little-endian lsb/msb byte order.
peekf (address)
reads the float value at the given address in memory. On CBM machines, this reads 5 bytes.
poke (address, value)
same as @(address)=value - writes the byte value at the given address in memory.
pokew (address, value)
writes the word value at the given address in memory, in usual little-endian lsb/msb byte order.
pokef (address, value)
writes the float value at the given address in memory. On CBM machines, this writes 5 bytes.
pokemon (address, value)
Like poke(), but also returns the previous value in the given address.
Also doesn't have anything to do with a certain video game.
rol (x)
Rotate the bits in x (byte or word) one position to the left.
This uses the CPU's rotate semantics: bit 0 will be set to the current value of the Carry flag,
while the highest bit will become the new Carry flag value.
(essentially, it is a 9-bit or 17-bit rotation)
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can rol a memory location directly by using the direct memory access syntax, so like ``rol(@($5000))``
rol2 (x)
Like ``rol`` but now as 8-bit or 16-bit rotation.
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can rol a memory location directly by using the direct memory access syntax, so like ``rol2(@($5000))``
ror (x)
Rotate the bits in x (byte or word) one position to the right.
This uses the CPU's rotate semantics: the highest bit will be set to the current value of the Carry flag,
while bit 0 will become the new Carry flag value.
(essentially, it is a 9-bit or 17-bit rotation)
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can ror a memory location directly by using the direct memory access syntax, so like ``ror(@($5000))``
ror2 (x)
Like ``ror`` but now as 8-bit or 16-bit rotation.
It uses some extra logic to not consider the carry flag as extra rotation bit.
Modifies in-place, doesn't return a value (so can't be used in an expression).
You can ror a memory location directly by using the direct memory access syntax, so like ``ror2(@($5000))``
setlsb (x, value)
Sets the least significant byte of word variable x to a new value. Leaves the MSB untouched.
setmsb (x, value)
Sets the most significant byte of word variable x to a new value. Leaves the LSB untouched.
sizeof (name) ; sizeof (number)
Number of bytes that the object 'name', or the number 'number' occupies in memory.
This is a constant determined by the data type of
the object. For instance, for a variable of type uword, the sizeof is 2.
For an 10 element array of floats, it is 50 (on the C64, where a float is 5 bytes).
Note: usually you will be interested in the number of elements in an array, use len() for that.
memory (name, size, alignment)
Returns the address of the first location of a statically "reserved" block of memory of the given size in bytes,
with the given name. The block is uninitialized memory, it is *not* set to zero!
If you specify an alignment value >1, it means the block of memory will
be aligned to such a dividable address in memory, for instance an alignment of $100 means the
memory block is aligned on a page boundary, and $2 means word aligned (even addresses).
Requesting the address of such a named memory block again later with
the same name, will result in the same address as before.
When reusing blocks in that way, it is required that the size argument is the same,
otherwise you'll get a compilation error.
This routine can be used to "reserve" parts of the memory where a normal byte array variable would
not suffice; for instance if you need more than 256 consecutive bytes.
The return value is just a simple uword address so it cannot be used as an array in your program.
You can only treat it as a pointer or use it in inline assembly.
call (address) -> uword
Calls a subroutine given by its memory address. You cannot pass arguments directly,
although it is ofcourse possible to do this via the global ``cx16.r0...`` registers for example.
It is assumed the subroutine returns a word value (in AY), if it does not, just add void to the call to ignore the result value.
This function effectively creates an "indirect JSR" if you use it on a ``uword`` pointer variable.
But because it doesn't handle bank switching etcetera by itself,
it is a lot faster than ``callfar``. And it works on other systems than just the Commander X16.
callfar (bank, address, argumentword) -> uword ; NOTE: specific to cx16 target for now
Calls an assembly routine in another bank on the Commander X16 (using its ``JSRFAR`` routine)
Be aware that ram OR rom bank may be changed depending on the address it jumps to!
The argumentword will be loaded into the A+Y registers before calling the routine.
The uword value that the routine returns in the A+Y registers, will be returned.
NOTE: this routine is very inefficient, so don't use it to call often. Set the bank yourself
or even write a custom tailored trampoline routine if you need to. Or use ``call`` if you can.
syscall (callnr), syscall1 (callnr, arg), syscall2 (callnr, arg1, arg2), syscall3 (callnr, arg1, arg2, arg3)
Functions for doing a system call on targets that support this. Currently no actual target
uses this though except, possibly, the experimental code generation target!
The regular 6502 based compiler targets just use a subroutine call to asmsub Kernal routines at
specific memory locations. So these builtin function calls are not useful yet except for
experimentation in new code generation targets.
rsave
Saves all registers including status (or only X) on the stack
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* saved,
but you can use ``cx16.save_virtual_registers()`` for that.
rrestore
Restore all registers including status (or only X) back from the cpu hardware stack
Note: the 16 bit 'virtual' registers of the Commander X16 are *not* restored,
but you can use ``cx16.restore_virtual_registers()`` for that.
Library routines
----------------
There are many routines available in the compiler libraries.
Some are used internally by the compiler as well.
Library routines and builtin functions
--------------------------------------
There are many routines available in the compiler libraries or as builtin functions.
The most important ones can be found in the :doc:`libraries` chapter.
There's too many to list here, just have a look through the source code
of the library modules to see what's there.
(They can be found in the compiler/res directory)
The example programs also use a small set of the library routines, you can study
their source code to see how they might be used.

View File

@ -1,6 +1,8 @@
TODO
====
Get rid of any() and all() as builtin functions? Replace them with regular subroutines in buffer.p8 for example?
See open issues on github.
IR: add SEC and CLC instructions in place of call to sys.set_carry() and sys.clear_carry(). (check more inline sub calls that should be a single instruction?)
@ -78,7 +80,6 @@ Compiler:
- Zig-like defer to execute a statement/anonymousscope when subroutine exits? (problem is, we have jump insructions and inline asm , where we lose track of when exactly the subroutine exits...)
- generate WASM to eventually run prog8 on a browser canvas? Use binaryen toolkit and/or my binaryen kotlin library?
- implement split words arrays all()
- implement split words arrays sort()
Libraries:

View File

@ -1,69 +0,0 @@
%import textio
%zeropage basicsafe
; Note: this program can be compiled for multiple target systems.
main {
sub start() {
ubyte[] uba = [10,0,2,8,5,4,3,9]
uword[] uwa = [1000,0,200,8000,50,40000,3,900]
byte[] ba = [-10,0,-2,8,5,4,-3,9,-99]
word[] wa = [-1000,0,-200,8000,50,31111,3,-900]
txt.print("original\n")
print_arrays()
sort(uba)
sort(uwa)
sort(ba)
sort(wa)
txt.print("sorted\n")
print_arrays()
reverse(uba)
reverse(uwa)
reverse(ba)
reverse(wa)
txt.print("reversed\n")
print_arrays()
;test_stack.test()
return
sub print_arrays() {
ubyte ub
uword uw
byte bb
word ww
for ub in uba {
txt.print_ub(ub)
txt.chrout(',')
}
txt.nl()
for uw in uwa {
txt.print_uw(uw)
txt.chrout(',')
}
txt.nl()
for bb in ba {
txt.print_b(bb)
txt.chrout(',')
}
txt.nl()
for ww in wa {
txt.print_w(ww)
txt.chrout(',')
}
txt.nl()
txt.nl()
}
}
}

View File

@ -1,55 +1,23 @@
%import textio
%import buffers
%zeropage basicsafe
%option no_sysinit
main {
sub start() {
ringbuffer256.init()
smallringbuffer.init()
cx16.r0L = ringbuffer256.get()
if_cs {
txt.print_ub(cx16.r0L)
smallringbuffer.put(123)
txt.print_ub(smallringbuffer.get())
txt.nl()
smallringbuffer.putw(12345)
txt.print_uw(smallringbuffer.getw())
txt.nl()
} else {
txt.print("buffer empty\n")
}
}
}
ringbuffer256 {
uword size
ubyte head
ubyte tail
ubyte[256] buffer
sub init() {
size = head = 0
tail = 255
}
sub add(ubyte value) -> bool {
if size==256
return false
buffer[head] = value
head++
size++
}
sub get() -> ubyte {
if size==0 {
sys.clear_carry()
return 0
}
size--
tail++
sys.set_carry()
return buffer[tail]
}
}
;
;main {

View File

@ -14,7 +14,7 @@
<keywords keywords="&amp;;-&gt;;@;and;as;asmsub;break;clobbers;continue;do;downto;else;false;for;goto;if;if_cc;if_cs;if_eq;if_mi;if_ne;if_neg;if_nz;if_pl;if_pos;if_vc;if_vs;if_z;in;inline;not;or;repeat;return;romsub;step;sub;to;true;unroll;until;when;while;xor;~" ignore_case="false" />
<keywords2 keywords="%address;%asm;%asmbinary;%asminclude;%breakpoint;%encoding;%import;%ir;%launcher;%option;%output;%zeropage;%zpallowed;%zpreserved;atascii:;cp437:;default:;iso16:;iso5:;iso:;petscii:;sc:" />
<keywords3 keywords="@nozp;@requirezp;@shared;@split;@zp;bool;byte;const;float;str;ubyte;uword;void;word" />
<keywords4 keywords="abs;all;any;call;callfar;clamp;cmp;divmod;len;lsb;max;memory;min;mkword;msb;peek;peekf;peekw;poke;pokef;pokew;reverse;rol;rol2;ror;ror2;rrestore;rrestorex;rsave;rsavex;setlsb;setmsb;sgn;sizeof;sort;sqrt;swap;|&gt;" />
<keywords4 keywords="abs;all;any;call;callfar;clamp;cmp;divmod;len;lsb;max;memory;min;mkword;msb;peek;peekf;peekw;poke;pokef;pokew;rol;rol2;ror;ror2;rrestore;rrestorex;rsave;rsavex;setlsb;setmsb;sgn;sizeof;sqrt" />
</highlighting>
<extensionMap>
<mapping ext="p8" />

View File

@ -27,7 +27,7 @@
<Keywords name="Keywords1">void const&#x000D;&#x000A;str&#x000D;&#x000A;byte ubyte bool&#x000D;&#x000A;word uword&#x000D;&#x000A;float&#x000D;&#x000A;zp shared split requirezp nozp</Keywords>
<Keywords name="Keywords2">%address&#x000D;&#x000A;%asm&#x000D;&#x000A;%ir&#x000D;&#x000A;%asmbinary&#x000D;&#x000A;%asminclude&#x000D;&#x000A;%breakpoint&#x000D;&#x000A;%encoding&#x000D;&#x000A;%import&#x000D;&#x000A;%launcher&#x000D;&#x000A;%option&#x000D;&#x000A;%output&#x000D;&#x000A;%zeropage&#x000D;&#x000A;%zpreserved&#x000D;&#x000A;%zpallowed</Keywords>
<Keywords name="Keywords3">inline sub asmsub romsub&#x000D;&#x000A;clobbers&#x000D;&#x000A;asm&#x000D;&#x000A;if&#x000D;&#x000A;when else&#x000D;&#x000A;if_cc if_cs if_eq if_mi if_neg if_nz if_pl if_pos if_vc if_vs if_z&#x000D;&#x000A;for in step do while repeat unroll&#x000D;&#x000A;break continue return goto</Keywords>
<Keywords name="Keywords4">abs all any call callfar clamp cmp divmod len lsb lsl lsr memory mkword min max msb peek peekw peekf poke pokew pokef rsave rsavex rrestore rrestorex reverse rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof sort sqrtw swap</Keywords>
<Keywords name="Keywords4">abs all any call callfar clamp cmp divmod len lsb lsl lsr memory mkword min max msb peek peekw peekf poke pokew pokef rsave rsavex rrestore rrestorex rnd rndw rol rol2 ror ror2 setlsb setmsb sgn sizeof sqrtw</Keywords>
<Keywords name="Keywords5">true false&#x000D;&#x000A;not and or xor&#x000D;&#x000A;as to downto |&gt;</Keywords>
<Keywords name="Keywords6"></Keywords>
<Keywords name="Keywords7"></Keywords>