mirror of
https://github.com/irmen/prog8.git
synced 2025-01-11 13:29:45 +00:00
added lsb, msb functions.
fixed missing errormessages. changed some zp options.
This commit is contained in:
parent
0b4135698e
commit
3933fdab13
@ -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')
|
||||
|
||||
|
@ -54,23 +54,24 @@ Directives
|
||||
|
||||
.. data:: %zeropage <style>
|
||||
|
||||
Level: module.
|
||||
Global setting, select ZeroPage handling style. Defaults to ``compatible``.
|
||||
Level: module.
|
||||
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 ``full`` -- claim the whole ZP for variables for the program, overwriting everything,
|
||||
except the few addresses mentioned above that are used by the system's IRQ routine.
|
||||
Even though the default IRQ routine is still active, it is impossible to use most BASIC and KERNAL ROM routines.
|
||||
This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
|
||||
It is also not possible to cleanly exit the program, other than resetting the machine.
|
||||
This option makes programs smaller and faster because many more variables can
|
||||
be stored in the ZP, which is more efficient.
|
||||
- style ``full-restore`` -- like ``full``, but makes a backup copy of the original values at program start.
|
||||
These are restored (except for the software jiffy clock in ``$a0``--``$a2``)
|
||||
when the program exits, and allows it to exit back to the BASIC prompt.
|
||||
- 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.
|
||||
This includes many floating point operations and several utility routines that do I/O, such as ``print_string``.
|
||||
It is also not possible to cleanly exit the program, other than resetting the machine.
|
||||
This option makes programs smaller and faster because many more variables can
|
||||
be stored in the ZP, which is more efficient.
|
||||
|
||||
Also read :ref:`zeropage`.
|
||||
Also read :ref:`zeropage`.
|
||||
|
||||
|
||||
.. data:: %address <address>
|
||||
@ -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
|
||||
---------
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
@ -43,7 +42,7 @@ ask_guess:
|
||||
c64scr.print_string("\nYou have ")
|
||||
c64scr.print_byte_decimal(attempts_left)
|
||||
c64scr.print_string(" guess")
|
||||
if(attempts_left>0) c64scr.print_string("es")
|
||||
if(attempts_left>0) c64scr.print_string("es")
|
||||
|
||||
c64scr.print_string(" left.\nWhat is your next guess? ")
|
||||
Y = c64scr.input_chars(guess)
|
||||
@ -52,17 +51,17 @@ ask_guess:
|
||||
c64.FREADSTR(A)
|
||||
AY = c64flt.GETADRAY()
|
||||
if(A==secretnumber) {
|
||||
c64scr.print_string("\nThat's my number, impressive!\n")
|
||||
goto goodbye
|
||||
c64scr.print_string("\nThat's my number, impressive!\n")
|
||||
goto goodbye
|
||||
}
|
||||
c64scr.print_string("That is too ")
|
||||
if(A > secretnumber)
|
||||
c64scr.print_string("low!\n")
|
||||
c64scr.print_string("low!\n")
|
||||
else
|
||||
c64scr.print_string("high!\n")
|
||||
c64scr.print_string("high!\n")
|
||||
|
||||
attempts_left--
|
||||
if(attempts_left>0) goto ask_guess
|
||||
if(attempts_left>0) goto ask_guess
|
||||
|
||||
game_over:
|
||||
c64scr.print_string("\nToo bad! It was: ")
|
||||
@ -72,4 +71,5 @@ game_over:
|
||||
goodbye:
|
||||
c64scr.print_string("\nThanks for playing. Bye!\n")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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()
|
||||
|
||||
|
@ -130,9 +130,9 @@ enum class LauncherType {
|
||||
}
|
||||
|
||||
enum class ZeropageType {
|
||||
COMPATIBLE,
|
||||
FULL,
|
||||
FULL_RESTORE
|
||||
BASICSAFE,
|
||||
KERNALSAFE,
|
||||
FULL
|
||||
}
|
||||
|
||||
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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 result = numericLiteral(function(float), args[0].position)
|
||||
result.position = args[0].position
|
||||
return result
|
||||
}
|
||||
else
|
||||
throw NotConstArgumentException()
|
||||
val float = constval.asNumericValue?.toDouble()!!
|
||||
val result = numericLiteral(function(float), args[0].position)
|
||||
result.position = args[0].position
|
||||
return result
|
||||
}
|
||||
|
||||
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 result = LiteralValue(function(float).toInt())
|
||||
result.position = args[0].position
|
||||
return result
|
||||
}
|
||||
else
|
||||
throw NotConstArgumentException()
|
||||
val float = constval.asNumericValue?.toDouble()!!
|
||||
val result = LiteralValue(function(float).toInt())
|
||||
result.position = args[0].position
|
||||
return result
|
||||
}
|
||||
|
||||
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 result = LiteralValue(function(integer).toInt())
|
||||
result.position = args[0].position
|
||||
return result
|
||||
}
|
||||
else
|
||||
throw NotConstArgumentException()
|
||||
val integer = constval.asNumericValue?.toInt()!!
|
||||
val result = LiteralValue(function(integer).toInt())
|
||||
result.position = args[0].position
|
||||
return result
|
||||
}
|
||||
|
||||
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 }
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user