added lsb, msb functions.

fixed missing errormessages.
changed some zp options.
This commit is contained in:
Irmen de Jong 2018-09-06 21:13:49 +02:00
parent 0b4135698e
commit 3933fdab13
16 changed files with 222 additions and 183 deletions

View File

@ -177,6 +177,10 @@ for instance.
.. todo::
matrix datatype
.. todo::
There must be a way to tell the compiler which variables you require to be in Zeropage:
``zeropage`` modifier keyword on vardecl perhaps?
Variables that represent CPU hardware registers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -203,6 +207,13 @@ address you specified, and setting the varible will directly modify that memory
memory word SCREENCOLORS = $d020 ; a 16-bit word at the addres $d020-$d021
.. note::
Directly accessing random memory locations is not yet supported without the
intermediate step of declaring a memory-mapped variable for the memory location.
The advantages of this however, is that it's clearer what the memory location
stands for, and the compiler also knows the data type.
Integers
^^^^^^^^
@ -460,6 +471,12 @@ len(x)
Number of values in the non-scalar (array or matrix) value x.
(This is different from the number of *bytes* in memory if the datatype isn't byte)
lsb(x)
Get the least significant byte of the word x.
msb(x)
Get the most significant byte of the word x.
any(x)
1 ('true') if any of the values in the non-scalar (array or matrix) value x is 'true' (not zero), else 0 ('false')

View File

@ -55,10 +55,14 @@ Directives
.. data:: %zeropage <style>
Level: module.
Global setting, select ZeroPage handling style. Defaults to ``compatible``.
Global setting, select ZeroPage handling style. Defaults to ``kernalsafe``.
- style ``compatible`` -- only use the few 'free' addresses in the ZP, and don't change anything else.
This allows full use of BASIC and KERNAL ROM routines including default IRQs during normal system operation.
- style ``kernalsafe`` -- use the part of the ZP that is 'free' or only used by BASIC routines,
and don't change anything else. This allows full use of KERNAL ROM routines (but not BASIC routines),
including default IRQs during normal system operation.
- style ``basicsafe`` -- the most restricted mode; only use the handful 'free' addresses in the ZP, and don't
touch change anything else. This allows full use of BASIC and KERNAL ROM routines including default IRQs
during normal system operation.
- style ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
except the few addresses mentioned above that are used by the system's IRQ routine.
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
@ -66,9 +70,6 @@ Directives
It is also not possible to cleanly exit the program, other than resetting the machine.
This option makes programs smaller and faster because many more variables can
be stored in the ZP, which is more efficient.
- style ``full-restore`` -- like ``full``, but makes a backup copy of the original values at program start.
These are restored (except for the software jiffy clock in ``$a0``--``$a2``)
when the program exits, and allows it to exit back to the BASIC prompt.
Also read :ref:`zeropage`.
@ -192,20 +193,6 @@ Values in the source code are written using *value literals*. In the table of th
data types below you can see how they should be written.
Range expression
----------------
A special value is the *range expression* ( ``<startvalue> to <endvalue>`` )
which represents a range of numbers or characters,
from the starting value to (and including) the ending value.
If used in the place of a literal value, it expands into the actual array of values::
byte[100] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
.. todo::
this may be used later in the for-loop as well. Add 'step' to range expression as well?
Variable declarations
^^^^^^^^^^^^^^^^^^^^^
@ -267,7 +254,7 @@ Memory mapped variables
^^^^^^^^^^^^^^^^^^^^^^^
The ``memory`` keyword is used in front of a data type keyword, to say that no storage
should be allocated by the compiler. Instead, the value assigned to the variable (mandatory now!)
should be allocated by the compiler. Instead, the (mandatory) value assigned to the variable
should be the *memory address* where the value is located::
memory byte BORDER = $d020
@ -292,6 +279,21 @@ The following names are reserved, they have a special meaning::
AX AY XY ; 16-bit pseudo register pairs
Range expression
^^^^^^^^^^^^^^^^
A special value is the *range expression* ( ``<startvalue> to <endvalue>`` )
which represents a range of numbers or characters,
from the starting value to (and including) the ending value.
If used in the place of a literal value, it expands into the actual array of values::
byte[100] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
.. todo::
this may be used later in the for-loop as well. Add 'step' to range expression as well?
Operators
---------

View File

@ -77,19 +77,22 @@ Theoretically they can all be used in a program, with the follwoing limitations:
- it's more convenient and safe to let IL65 allocate these addresses for you and just
use symbolic names in the program code.
Here is the list of the remaining free-to-use ZP addresses with BASIC and KERNAL active in the Commodore-64:
.. todo::
There must be a way to tell the compiler which variables you require to be in Zeropage:
``zeropage`` modifier keyword on vardecl perhaps?
``$02``, ``$03``, ``$04``, ``$05``, ``$06``, ``$2a``, ``$52``,
``$f7 - $f8``, ``$f9 - $fa``, ``$fb - $fc``, ``$fd - $fe``
*The six reserved addresses mentioned earliser are subtracted from this set,* leaving you with
just *five* 1-byte and *two* 2-byte usable ZP 'registers' for use by the program.
**IL65 knows about all of this.** It will use the free ZP addresses to place its ZP variables in,
IL65 knows what addresses are safe to use in the various ZP handling configurations.
It will use the free ZP addresses to place its ZP variables in,
until they're all used up. If instructed to output a program that takes over the entire
machine, (almost) all of the ZP addresses are suddenly available and will be used.
IL65 can also generate a special routine that saves and restores the ZP to let the program run
and return safely back to the system afterwards - you don't have to take care of that yourself.
**ZeroPage handling is configurable:**
There's a global program directive to specify the way the compiler
treats the ZP for the program. The default is to be reasonably restrictive to use the
part of the ZP that is not used by the C64's kernal routines.
It's possible to claim the whole ZP as well (by disabling the operating system or kernal).
If you want, it's also possible to be more restricive and stay clear of the addresses used by BASIC routines too.
IRQs and the ZeroPage
@ -100,21 +103,12 @@ The normal IRQ routine in the C-64's kernal will read and write several addresse
``$a0 - $a2``; ``$91``; ``$c0``; ``$c5``; ``$cb``; ``$f5 - $f6``
These addresses will never be used by the compiler for ZP variables, so variables will
These addresses will *never* be used by the compiler for ZP variables, so variables will
not interfere with the IRQ routine and vice versa. This is true for the normal ZP mode but also
for the mode where the whole system and ZP have been taken over.
So the normal IRQ vector can still run and will be when the program is started!
ZeroPage handling is configurable
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There's a global program directive to specify the way the compiler
treats the ZP for the program. The default is to be restrictive to just
the few free locations mentioned above, where most of the ZP is considered a no-go zone by the compiler.
It's possible to claim the whole ZP as well (by disabling the operating system or kernal),
and even ask for a save/restore of the original values to be able to cleanly exit back to a BASIC prompt.
CPU

View File

@ -3,14 +3,13 @@
%import mathlib
~ main {
sub start() -> () {
str name = "?" * 80
str guess = "?" * 80
byte secretnumber = 0
byte attempts_left = 10
memory word freadstr_arg = $22 ; argument for FREADSTR
start:
c64.init_system()
c64.VMCSB |= 2 ; lowercase charset
@ -72,4 +71,5 @@ game_over:
goodbye:
c64scr.print_string("\nThanks for playing. Bye!\n")
return
}
}

View File

@ -11,6 +11,13 @@
}
~ main $c003 {
word lsb1 = lsb($ea31)
word msb1 = msb($ea31)
byte lsb2 = lsb($ea31)
byte msb2 = msb($ea31)
word lsb3 = lsb($ea)
word msb3 = msb($ea)
const word len1 = len([1,2,3,wa1, wa2, ws1, all1])
const word wa1 = ceil(abs(-999.22))
const byte wa2 = abs(-99)
@ -22,7 +29,7 @@
const word ws2 = ceil(avg([1,2,3,4.9]))
const word ws3 = round(sum([1,2,3,4.9]))
const word any1 = any([0,0,0,0,0,22,0,0])
const word any2 = any([2+sin(2), 2])
const word any2 = any([2+sin(2.0), 2])
const word all1 = all([0,0,0,0,0,22,0,0])
const word all2 = all([0.0])
const word all3 = all([wa1, wa2, ws1, all1])
@ -48,7 +55,7 @@
memory byte cderp = min([$ffdd])+ (1/1)
memory byte cderpA = min([$ffdd, 10, 20, 30])
memory byte cderpB = min([1, 2.2, 4.4, 100])
memory byte derp2 = 2+$ffdd+round(10*sin(3))
memory byte derp2 = 2+$ffdd+round(10*sin(3.1))
const byte hopla=55-33
const byte hopla3=100+(-hopla)
const byte hopla4 = 100-hopla
@ -99,10 +106,9 @@
if(6==6) {
A=sin(X)
A=sin([X])
X=max([1,2,Y])
X=min([1,2,Y])
X=lsl(1.2)
X=lsl(12)
X=lsl(Y)
P_carry(0)
P_carry(1)

View File

@ -40,7 +40,7 @@ fun main(args: Array<String>) {
val compilerOptions = CompilationOptions(
if(outputType==null) OutputType.PRG else OutputType.valueOf(outputType),
if(launcherType==null) LauncherType.BASIC else LauncherType.valueOf(launcherType),
if(zpType==null) ZeropageType.COMPATIBLE else ZeropageType.valueOf(zpType),
if(zpType==null) ZeropageType.KERNALSAFE else ZeropageType.valueOf(zpType),
options.contains(DirectiveArg(null, "enable_floats", null))
)

View File

@ -779,6 +779,8 @@ class FunctionCall(override var target: IdentifierReference, override var arglis
"sum" -> builtinSum(arglist, position, namespace)
"avg" -> builtinAvg(arglist, position, namespace)
"len" -> builtinLen(arglist, position, namespace)
"lsb" -> builtinLsb(arglist, position, namespace)
"msb" -> builtinMsb(arglist, position, namespace)
"any" -> builtinAny(arglist, position, namespace)
"all" -> builtinAll(arglist, position, namespace)
"floor" -> builtinFloor(arglist, position, namespace)

View File

@ -1,7 +1,7 @@
package il65.ast
import il65.compiler.CompilationOptions
import il65.functions.BuiltIns
import il65.functions.BuiltinFunctionNames
import il65.parser.ParsingFailedError
/**
@ -81,8 +81,8 @@ class AstChecker(private val globalNamespace: INameScope, val compilerOptions: C
// if(subroutine.parent !is Block)
// err("subroutines can only be defined in a block (not in other scopes)")
if(BuiltIns.contains(subroutine.name))
err("cannot override a built-in function")
if(BuiltinFunctionNames.contains(subroutine.name))
err("cannot redefine a built-in function")
val uniqueNames = subroutine.parameters.map { it.name }.toSet()
if(uniqueNames.size!=subroutine.parameters.size)
@ -224,10 +224,10 @@ class AstChecker(private val globalNamespace: INameScope, val compilerOptions: C
"%zeropage" -> {
if(directive.parent !is Module) err("this directive may only occur at module level")
if(directive.args.size!=1 ||
directive.args[0].name != "compatible" &&
directive.args[0].name != "full" &&
directive.args[0].name != "full-restore")
err("invalid zp directive style, expected compatible, full or full-restore")
directive.args[0].name != "basicsafe" &&
directive.args[0].name != "kernalsafe" &&
directive.args[0].name != "full")
err("invalid zp directive style, expected basicsafe, kernalsafe, or full")
}
"%address" -> {
if(directive.parent !is Module) err("this directive may only occur at module level")

View File

@ -1,5 +1,6 @@
package il65.ast
import il65.functions.BuiltinFunctionNames
import il65.parser.ParsingFailedError
/**
@ -20,13 +21,6 @@ fun Module.checkIdentifiers(): MutableMap<String, IStatement> {
}
val BuiltinFunctionNames = setOf(
"P_carry", "P_irqd", "rol", "ror", "rol2", "ror2", "lsl", "lsr",
"sin", "cos", "abs", "acos", "asin", "tan", "atan",
"log", "log10", "sqrt", "rad", "deg", "round", "floor", "ceil",
"max", "min", "avg", "sum", "len", "any", "all")
class AstIdentifiersChecker : IAstProcessor {
private val checkResult: MutableList<AstException> = mutableListOf()

View File

@ -130,9 +130,9 @@ enum class LauncherType {
}
enum class ZeropageType {
COMPATIBLE,
FULL,
FULL_RESTORE
BASICSAFE,
KERNALSAFE,
FULL
}

View File

@ -19,14 +19,25 @@ class Zeropage(private val options: CompilationOptions) {
val free = mutableListOf<Int>()
init {
if(options.zeropage==ZeropageType.FULL || options.zeropage==ZeropageType.FULL_RESTORE) {
if(options.zeropage==ZeropageType.FULL) {
free.addAll(0x04 .. 0xfa)
free.add(0xff)
free.removeAll(listOf(0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6)) // these are updated by IRQ
} else {
if(options.zeropage==ZeropageType.KERNALSAFE) {
// add the Zp addresses that are just used by BASIC routines to the free list
free.addAll(listOf(0x09, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
0x47, 0x48, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x53, 0x6f, 0x70))
}
// add the Zp addresses not even used by BASIC
// these are valid for the C-64 (when no RS232 I/O is performed):
// ($02, $03, $fb-$fc, $fd-$fe are reserved as scratch addresses for various routines)
free.addAll(listOf(0x04, 0x05, 0x06, 0x2a, 0x52, 0xf7, 0xf8, 0xf9, 0xfa))
// KNOWN WORKING FREE: 0x04, 0x05, 0x06, 0x2a, 0x52, 0xf7, 0xf8, 0xf9, 0xfa))
free.addAll(listOf(0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0d, 0x0e,
0x12, 0x2a, 0x52, 0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
0xb5, 0xb6, 0xf7, 0xf8, 0xf9, 0xfa))
}
assert(!free.contains(Zeropage.SCRATCH_B1))
assert(!free.contains(Zeropage.SCRATCH_B2))
@ -42,7 +53,9 @@ class Zeropage(private val options: CompilationOptions) {
val size =
if(vardecl.arrayspec!=null) {
println("${vardecl.position} warning: allocating a large value in zeropage")
if(vardecl.position!=null)
print(vardecl.position)
println(" warning: allocating a large value (array) in zeropage")
val y = (vardecl.arrayspec.y as? LiteralValue)?.intvalue
if(y==null) {
// 1 dimensional array
@ -65,7 +78,9 @@ class Zeropage(private val options: CompilationOptions) {
DataType.WORD -> 2
DataType.FLOAT -> {
if (options.floats) {
println("${vardecl.position} warning: allocating a large value in zeropage")
if(vardecl.position!=null)
print(vardecl.position)
println(" warning: allocating a large value (float) in zeropage")
5
} else throw CompilerException("floating point option not enabled")
}

View File

@ -5,10 +5,11 @@ import kotlin.math.abs
import kotlin.math.floor
val BuiltIns = listOf(
"sin", "cos", "abs", "acos", "asin", "tan", "atan", "log", "log10",
"sqrt", "max", "min", "round", "rad", "deg", "avg", "sum"
)
val BuiltinFunctionNames = setOf(
"P_carry", "P_irqd", "rol", "ror", "rol2", "ror2", "lsl", "lsr",
"sin", "cos", "abs", "acos", "asin", "tan", "atan",
"log", "log10", "sqrt", "rad", "deg", "round", "floor", "ceil",
"max", "min", "avg", "sum", "len", "any", "all", "lsb", "msb")
class NotConstArgumentException: AstException("not a const argument to a built-in function")
@ -17,43 +18,40 @@ class NotConstArgumentException: AstException("not a const argument to a built-i
private fun oneDoubleArg(args: List<IExpression>, position: Position?, namespace:INameScope, function: (arg: Double)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
if(!constval.isFloat)
throw SyntaxError("built-in function requires one floating point argument", position)
val float = args[0].constValue(namespace)?.asNumericValue?.toDouble()
if(float!=null) {
val float = constval.asNumericValue?.toDouble()!!
val result = numericLiteral(function(float), args[0].position)
result.position = args[0].position
return result
}
else
throw NotConstArgumentException()
}
private fun oneDoubleArgOutputInt(args: List<IExpression>, position: Position?, namespace:INameScope, function: (arg: Double)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one floating point argument", position)
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
if(!constval.isFloat)
throw SyntaxError("built-in function requires one floating point argument", position)
val float = args[0].constValue(namespace)?.asNumericValue?.toDouble()
if(float!=null) {
val float = constval.asNumericValue?.toDouble()!!
val result = LiteralValue(function(float).toInt())
result.position = args[0].position
return result
}
else
throw NotConstArgumentException()
}
private fun oneIntArgOutputInt(args: List<IExpression>, position: Position?, namespace:INameScope, function: (arg: Int)->Number): LiteralValue {
if(args.size!=1)
throw SyntaxError("built-in function requires one integer argument", position)
val constval = args[0].constValue(namespace) ?: throw NotConstArgumentException()
if(!constval.isInteger)
throw SyntaxError("built-in function requires one integer argument", position)
val integer = args[0].constValue(namespace)?.asNumericValue?.toInt()
if(integer!=null) {
val integer = constval.asNumericValue?.toInt()!!
val result = LiteralValue(function(integer).toInt())
result.position = args[0].position
return result
}
else
throw NotConstArgumentException()
}
private fun collectionArgOutputNumber(args: List<IExpression>, position: Position?, namespace:INameScope,
@ -153,6 +151,13 @@ fun builtinAbs(args: List<IExpression>, position: Position?, namespace:INameScop
return result
}
fun builtinLsb(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x and 255 }
fun builtinMsb(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x ushr 8 and 255}
fun builtinLsl(args: List<IExpression>, position: Position?, namespace:INameScope): LiteralValue
= oneIntArgOutputInt(args, position, namespace) { x: Int -> x shl 1 }

View File

@ -60,8 +60,8 @@ class ExpressionOptimizer(private val globalNamespace: INameScope) : IAstProcess
fun addError(x: AstException) {
// check that we don't add the same error more than once
if(!reportedErrorMessages.contains(x.message)) {
reportedErrorMessages.add(x.message)
if(!reportedErrorMessages.contains(x.toString())) {
reportedErrorMessages.add(x.toString())
errors.add(x)
}
}

View File

@ -57,6 +57,8 @@ enum class Opcode {
OR,
XOR,
INV,
LSB,
MSB,
// logical operations (?)
@ -375,6 +377,22 @@ data class Value(val type: DataType, private val numericvalue: Number?, val stri
else -> throw VmExecutionException("dec can only work on byte/word/float")
}
}
fun lsb(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt() and 255)
DataType.WORD -> Value(DataType.WORD, wordval!! and 255)
else -> throw VmExecutionException("not can only work on byte/word")
}
}
fun msb(): Value {
return when(type) {
DataType.BYTE -> Value(DataType.BYTE, byteval!!.toInt() ushr 8 and 255)
DataType.WORD -> Value(DataType.WORD, wordval!! ushr 8 and 255)
else -> throw VmExecutionException("not can only work on byte/word")
}
}
}
@ -468,9 +486,9 @@ class Program (prog: MutableList<Instruction>,
Instruction(opcode, callLabel = args)
}
Opcode.SYSCALL -> {
val parts = args!!.split(' ')
val call = Syscall.valueOf(parts[0])
val callValue = if(parts.size==2) getArgValue(parts[1]) else null
val syscallparts = args!!.split(' ')
val call = Syscall.valueOf(syscallparts[0])
val callValue = if(parts.size==2) getArgValue(syscallparts[1]) else null
val callValues = if(callValue==null) emptyList() else listOf(callValue)
Instruction(opcode, Value(DataType.BYTE, call.callNr), callValues)
}
@ -966,6 +984,14 @@ class StackVm(val traceOutputFile: String?) {
val variable = variables[varname] ?: throw VmExecutionException("unknown variable: $varname")
variables[varname] = variable.dec()
}
Opcode.LSB -> {
val v = evalstack.pop()
evalstack.push(v.lsb())
}
Opcode.MSB -> {
val v = evalstack.pop()
evalstack.push(v.msb())
}
}
if(traceOutput!=null) {

View File

@ -11,6 +11,8 @@ import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ -108,7 +110,7 @@ class TestCompiler {
class TestZeropage {
@Test
fun testNames() {
val zp = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.COMPATIBLE, false))
val zp = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, false))
assertFailsWith<AssertionError> {
zp.allocate(VarDecl(VarDeclType.MEMORY, DataType.BYTE, null, "", null))
@ -134,18 +136,31 @@ class TestZeropage {
}
@Test
fun testCompatibleAllocation() {
val zp = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.COMPATIBLE, true))
assert(zp.available() == 9)
fun testFreeSpaces() {
val zp1 = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, true))
assertEquals(23, zp1.available())
val zp2 = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.KERNALSAFE, true))
assertEquals(71, zp2.available())
val zp3 = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, true))
assertEquals(239, zp3.available())
}
@Test
fun testBasicsafeAllocation() {
val zp = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, true))
assertEquals(23, zp.available())
zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, null, "", null))
assertFailsWith<CompilerException> {
// in regular zp there aren't 5 sequential bytes free
// in regular zp there aren't 5 sequential bytes free after we take the first sequence
zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, null, "", null))
}
for (i in 0 until zp.available()) {
val loc = zp.allocate(VarDecl(VarDeclType.VAR, DataType.BYTE, null, "", null))
assert(loc > 0)
assertTrue(loc > 0)
}
assert(zp.available() == 0)
assertEquals(0, zp.available())
assertFailsWith<CompilerException> {
zp.allocate(VarDecl(VarDeclType.VAR, DataType.BYTE, null, "", null))
}
@ -157,17 +172,17 @@ class TestZeropage {
@Test
fun testFullAllocation() {
val zp = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.FULL, true))
assert(zp.available() == 239)
assertEquals(239, zp.available())
val loc = zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, null, "", null))
assert(loc > 3)
assert(!zp.free.contains(loc))
assertTrue(loc > 3)
assertFalse(zp.free.contains(loc))
val num = zp.available() / 5
val rest = zp.available() % 5
for(i in 0..num-4) {
zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, null, "", null))
}
assert(zp.available() == 19)
assertEquals(19,zp.available())
assertFailsWith<CompilerException> {
// can't allocate because no more sequential bytes, only fragmented
@ -180,7 +195,7 @@ class TestZeropage {
zp.allocate(VarDecl(VarDeclType.VAR, DataType.WORD, null, "", null))
zp.allocate(VarDecl(VarDeclType.VAR, DataType.WORD, null, "", null))
assert(zp.available() == 1)
assertEquals(1, zp.available())
zp.allocate(VarDecl(VarDeclType.VAR, DataType.BYTE, null, "", null))
assertFailsWith<CompilerException> {
// no more space
@ -190,16 +205,24 @@ class TestZeropage {
@Test
fun testEfficientAllocation() {
// free = [0x04, 0x05, 0x06, 0x2a, 0x52, 0xf7, 0xf8, 0xf9, 0xfa]
val zp = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.COMPATIBLE, true))
assert(zp.available()==9)
assert(0x2a == zp.allocate(VarDecl(VarDeclType.VAR, DataType.BYTE, null, "", null)))
assert(0x52 == zp.allocate(VarDecl(VarDeclType.VAR, DataType.BYTE, null, "", null)))
assert(0x04 == zp.allocate(VarDecl(VarDeclType.VAR, DataType.WORD, null, "", null)))
assert(0xf7 == zp.allocate(VarDecl(VarDeclType.VAR, DataType.WORD, null, "", null)))
assert(0x06 == zp.allocate(VarDecl(VarDeclType.VAR, DataType.BYTE, null, "", null)))
assert(0xf9 == zp.allocate(VarDecl(VarDeclType.VAR, DataType.WORD, null, "", null)))
assert(zp.available()==0)
// free = (0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0d, 0x0e,
// 0x12, 0x2a, 0x52, 0x94, 0x95, 0xa7, 0xa8, 0xa9, 0xaa,
// 0xb5, 0xb6, 0xf7, 0xf8, 0xf9, 0xfa))
val zp = Zeropage(CompilationOptions(OutputType.RAW, LauncherType.NONE, ZeropageType.BASICSAFE, true))
assertEquals(23, zp.available())
assertEquals(0x04, zp.allocate(VarDecl(VarDeclType.VAR, DataType.FLOAT, null, "", null)))
assertEquals(0x09, zp.allocate(VarDecl(VarDeclType.VAR, DataType.BYTE, null, "", null)))
assertEquals(0x12, zp.allocate(VarDecl(VarDeclType.VAR, DataType.BYTE, null, "", null)))
assertEquals(0x0d, zp.allocate(VarDecl(VarDeclType.VAR, DataType.WORD, null, "", null)))
assertEquals(0x94, zp.allocate(VarDecl(VarDeclType.VAR, DataType.WORD, null, "", null)))
assertEquals(0x2a, zp.allocate(VarDecl(VarDeclType.VAR, DataType.BYTE, null, "", null)))
assertEquals(0xa7, zp.allocate(VarDecl(VarDeclType.VAR, DataType.WORD, null, "", null)))
assertEquals(0xa9, zp.allocate(VarDecl(VarDeclType.VAR, DataType.WORD, null, "", null)))
assertEquals(0xb5, zp.allocate(VarDecl(VarDeclType.VAR, DataType.WORD, null, "", null)))
assertEquals(0xf7, zp.allocate(VarDecl(VarDeclType.VAR, DataType.WORD, null, "", null)))
assertEquals(0xf9, zp.allocate(VarDecl(VarDeclType.VAR, DataType.WORD, null, "", null)))
assertEquals(0x52, zp.allocate(VarDecl(VarDeclType.VAR, DataType.BYTE, null, "", null)))
assertEquals(0, zp.available())
}
}

View File

@ -1,45 +0,0 @@
; backup/restore the ZeroPage
; this is in a separate file so it can be omitted completely if it's not needed.
_il65_save_zeropage
lda #%00101111
sta _il65_zp_backup ; default value for $00
lda #%00100111
sta _il65_zp_backup+1 ; default value for $01
ldx #2
sei
- lda $00,x
sta _il65_zp_backup,x
inx
bne -
cli
rts
_il65_restore_zeropage
php
pha
txa
pha
sei
lda $a0 ; save the current jiffy clock
sta _il65_zp_backup+$a0
lda $a1
sta _il65_zp_backup+$a1
lda $a2
sta _il65_zp_backup+$a2
ldx #0
- lda _il65_zp_backup,x
sta $00,x
inx
bne -
cli
pla
tax
pla
plp
rts
_il65_zp_backup
.fill 256