mirror of
https://github.com/irmen/prog8.git
synced 2024-08-14 11:29:08 +00:00
cleanups, fix scope of certain generated nodes in for loops
This commit is contained in:
parent
d55bbcf706
commit
904e317781
@ -1,5 +1,4 @@
|
||||
%import c64utils
|
||||
%import mathlib
|
||||
%option enable_floats
|
||||
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
%import c64utils
|
||||
%import mathlib
|
||||
%option enable_floats
|
||||
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
%import c64utils
|
||||
%import mathlib
|
||||
%option enable_floats
|
||||
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
%import c64utils
|
||||
%import mathlib
|
||||
%option enable_floats
|
||||
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
%import c64utils
|
||||
%import mathlib
|
||||
%option enable_floats
|
||||
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
%import c64utils
|
||||
%import mathlib
|
||||
|
||||
~ main {
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
%import c64utils
|
||||
%import mathlib
|
||||
%option enable_floats
|
||||
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
%import c64utils
|
||||
%import mathlib
|
||||
|
||||
~ main {
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
%import c64utils
|
||||
%import mathlib
|
||||
|
||||
~ main {
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
%import c64utils
|
||||
%import mathlib
|
||||
|
||||
~ main {
|
||||
|
||||
|
@ -43,7 +43,7 @@
|
||||
;vm_gfx_line(x, 130, i*10.w, 199, 6)
|
||||
}
|
||||
|
||||
rotate_vertices(flt(irq.global_time) / 30.0)
|
||||
rotate_vertices(irq.global_time as float / 30.0)
|
||||
draw_edges()
|
||||
}
|
||||
}
|
||||
@ -81,11 +81,11 @@
|
||||
sub draw_edges() {
|
||||
|
||||
sub toscreenx(float x, float z) -> word {
|
||||
return fintw(x/(4.2+z) * flt(height)) + width // 2
|
||||
return x/(4.2+z) * (height as float) as word + width // 2
|
||||
}
|
||||
|
||||
sub toscreeny(float y, float z) -> word {
|
||||
return fintw(y/(4.2+z) * flt(height)) + height // 2
|
||||
return y/(4.2+z) * (height as float) as word + height // 2
|
||||
}
|
||||
|
||||
; draw all edges of the object
|
@ -42,7 +42,7 @@
|
||||
vm_gfx_line(i*2+width//2-width//10, 130, i*10.w, 199, 6)
|
||||
}
|
||||
|
||||
rotate_vertices(flt(irq.global_time) / 30.0)
|
||||
rotate_vertices(irq.global_time as float / 30.0)
|
||||
draw_edges()
|
||||
}
|
||||
}
|
||||
@ -80,11 +80,11 @@
|
||||
sub draw_edges() {
|
||||
|
||||
sub toscreenx(float x, float z) -> word {
|
||||
return fintw(x/(4.2+z) * flt(height)) + width // 2
|
||||
return x/(4.2+z) * (height as float) as word + width // 2
|
||||
}
|
||||
|
||||
sub toscreeny(float y, float z) -> word {
|
||||
return fintw(y/(4.2+z) * flt(height)) + height // 2
|
||||
return y/(4.2+z) * (height as float) as word + height // 2
|
||||
}
|
||||
|
||||
; draw all edges of the object
|
||||
|
@ -23,7 +23,7 @@
|
||||
|
||||
; use loop to write characters
|
||||
str bye = "Goodbye!\n"
|
||||
for ubyte c in 0 to len(bye) { ; @TODO fix compiler crash. Parent of assignment to c should be the anonymous scope 'c' is in, instead of the for loop itself.
|
||||
for ubyte c in 0 to len(bye) {
|
||||
c64.CHROUT(bye[c])
|
||||
}
|
||||
|
||||
|
@ -14,10 +14,10 @@
|
||||
c64.TIME_LO=0
|
||||
|
||||
for ubyte pixely in 0 to height-1 {
|
||||
float yy = flt(pixely)/height/0.4-1.0
|
||||
float yy = (pixely as float)/0.4/height-1.0
|
||||
|
||||
for ubyte pixelx in 0 to width-1 {
|
||||
float xx = flt(pixelx)/width/0.3-2.0
|
||||
float xx = (pixelx as float)/0.3/width-2.0
|
||||
|
||||
float xsquared = 0.0
|
||||
float ysquared = 0.0
|
||||
@ -37,7 +37,7 @@
|
||||
}
|
||||
c64.CHROUT('\n')
|
||||
}
|
||||
float duration = floor((c64.TIME_LO + c64.TIME_MID*256.0 + c64.TIME_HI*65536.0)/60.0)
|
||||
float duration = floor(((c64.TIME_LO as float) + 256.0*(c64.TIME_MID as float) + 65536.0*(c64.TIME_HI as float))/60.0)
|
||||
c64scr.print("finished in ")
|
||||
c64flt.print_f(duration)
|
||||
c64scr.print(" seconds!\n")
|
||||
|
@ -12,10 +12,10 @@
|
||||
vm_gfx_text(2, 1, 1, "Calculating Mandelbrot Fractal...")
|
||||
|
||||
for ubyte pixely in yoffset to yoffset+height-1 {
|
||||
float yy = flt((pixely-yoffset))/height/3.6+0.4
|
||||
float yy = (pixely-yoffset as float)/3.6/height+0.4
|
||||
|
||||
for uword pixelx in xoffset to xoffset+width-1 {
|
||||
float xx = flt((pixelx-xoffset))/width/3.0+0.2
|
||||
float xx = (pixelx-xoffset as float)/3.0/width+0.2
|
||||
|
||||
float xsquared = 0.0
|
||||
float ysquared = 0.0
|
||||
|
@ -1,5 +1,4 @@
|
||||
%import c64utils
|
||||
%import mathlib
|
||||
|
||||
; The classic number guessing game.
|
||||
; This version uses mostly high level subroutine calls and loops.
|
||||
|
@ -28,9 +28,9 @@
|
||||
}
|
||||
|
||||
sub screenx(float x) -> ubyte {
|
||||
return (x * width/2.2) + width//2 as ubyte
|
||||
return (x * width/2.2) + width/2.0 as ubyte
|
||||
}
|
||||
sub screeny(float y) -> ubyte {
|
||||
return (y * height/2.2) + height//2 as ubyte
|
||||
return (y * height/2.2) + height/2.0 as ubyte
|
||||
}
|
||||
}
|
||||
|
@ -23,13 +23,13 @@
|
||||
}
|
||||
|
||||
sub screenx(float x) -> word {
|
||||
; return (x/4.1* (width as float) as word) + width // 2 ; @todo fix calculation
|
||||
;return ((x/4.1* (width as float)) + 160.0) as word ;width // 2 ; @todo fix calculation
|
||||
float wf = width
|
||||
return (x/4.1* wf + wf / 2) as word
|
||||
return (x/4.1* wf + wf / 2.0) as word
|
||||
}
|
||||
sub screeny(float y) -> word {
|
||||
;return (y/4.1 * (height as float) as word) + height // 2 ; @todo fix calculation
|
||||
float hf = height
|
||||
return (y/4.1 * hf + hf/ 2) as word
|
||||
return (y/4.1 * hf + hf/ 2.0) as word
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
%import c64utils
|
||||
;%import mathlib
|
||||
;%option enable_floats
|
||||
|
||||
~ main {
|
||||
@ -8,6 +7,11 @@
|
||||
|
||||
;c64scr.PLOT(screenx(x), screeny(y)) ; @todo fix argument calculation of parameters ???!!!
|
||||
|
||||
sub screenx(float x) -> word {
|
||||
;return ((x/4.1* (width as float)) + 160.0) as word ;width // 2 ; @todo fix calculation
|
||||
float wf = width
|
||||
return (x/4.1* wf + wf / 2.0) as word
|
||||
}
|
||||
|
||||
sub start() {
|
||||
|
||||
|
@ -867,6 +867,7 @@ class BinaryExpression(var left: IExpression, var operator: String, var right: I
|
||||
else -> throw FatalAstException("invalid rightDt $rightDt")
|
||||
}
|
||||
DataType.FLOAT -> DataType.FLOAT
|
||||
null -> DataType.FLOAT
|
||||
else -> throw FatalAstException("invalid leftDt $leftDt")
|
||||
}
|
||||
} else if(leftDt==null || rightDt==null) null else arithmeticOpDt(leftDt, rightDt)
|
||||
@ -1434,19 +1435,8 @@ class FunctionCall(override var target: IdentifierReference,
|
||||
|
||||
private fun constValue(namespace: INameScope, heap: HeapValues, withDatatypeCheck: Boolean): LiteralValue? {
|
||||
// if the function is a built-in function and the args are consts, should try to const-evaluate!
|
||||
// lenghts of arrays and strings are constants that are determined at compile time!
|
||||
if(target.nameInSource.size>1) return null
|
||||
if(target.nameInSource[0]=="len" && arglist.size==1) {
|
||||
val arg=arglist[0]
|
||||
if(arg is IdentifierReference) {
|
||||
val target=arg.targetStatement(namespace)
|
||||
if(target!=null) {
|
||||
if (arg.resultingDatatype(namespace, heap) in StringDatatypes) {
|
||||
// len on strings should be dynamic, all other cases are a compile-time constant
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
var resultValue: LiteralValue? = null
|
||||
val func = BuiltinFunctions[target.nameInSource[0]]
|
||||
|
@ -61,6 +61,16 @@ class AstIdentifiersChecker(val heap: HeapValues) : IAstProcessor {
|
||||
return super.process(block)
|
||||
}
|
||||
|
||||
override fun process(functionCall: FunctionCall): IExpression {
|
||||
if(functionCall.target.nameInSource.size==1 && functionCall.target.nameInSource[0]=="lsb") {
|
||||
// lsb(...) is just an alias for type cast to ubyte, so replace with "... as ubyte"
|
||||
val typecast = TypecastExpression(functionCall.arglist.single(), DataType.UBYTE, functionCall.position)
|
||||
typecast.linkParents(functionCall.parent)
|
||||
return typecast
|
||||
}
|
||||
return functionCall
|
||||
}
|
||||
|
||||
override fun process(decl: VarDecl): IStatement {
|
||||
// first, check if there are datatype errors on the vardecl
|
||||
decl.datatypeErrors.forEach { checkResult.add(it) }
|
||||
|
@ -462,7 +462,7 @@ private class StatementTranslator(private val prog: IntermediateProgram,
|
||||
// word + word -> word
|
||||
// a combination with a float will be float (but give a warning about this!)
|
||||
|
||||
val floatWarning = "byte or word value implicitly converted to float. Suggestion: use explicit flt() conversion, a float number, or revert to integer arithmetic"
|
||||
val floatWarning = "byte or word value implicitly converted to float. Suggestion: use explicit cast as float, a float number, or revert to integer arithmetic"
|
||||
|
||||
return when(leftDt) {
|
||||
DataType.UBYTE -> {
|
||||
@ -725,8 +725,7 @@ private class StatementTranslator(private val prog: IntermediateProgram,
|
||||
else -> throw CompilerException("wrong datatype for $funcname()")
|
||||
}
|
||||
}
|
||||
"msb" -> prog.instr(Opcode.MSB)
|
||||
"lsb" -> TODO("lsb -> uw2ub or w2ub?")
|
||||
"msb" -> prog.instr(Opcode.MSB) // note: LSB is not a function, it's just an alias for the cast "... as ubyte"
|
||||
"lsl" -> {
|
||||
val arg = args.single()
|
||||
val dt = arg.resultingDatatype(namespace, heap)
|
||||
@ -1794,7 +1793,7 @@ private class StatementTranslator(private val prog: IntermediateProgram,
|
||||
}
|
||||
|
||||
val startAssignment = Assignment(listOf(makeAssignmentTarget()), null, range.from, range.position)
|
||||
startAssignment.linkParents(range.parent)
|
||||
startAssignment.linkParents(body)
|
||||
translate(startAssignment)
|
||||
|
||||
val loopLabel = makeLabel("loop")
|
||||
@ -1826,7 +1825,7 @@ private class StatementTranslator(private val prog: IntermediateProgram,
|
||||
AnonymousScope(mutableListOf(Jump(null, null, breakLabel, range.position)), range.position),
|
||||
AnonymousScope(mutableListOf(), range.position),
|
||||
range.position)
|
||||
ifstmt.linkParents(range.parent)
|
||||
ifstmt.linkParents(body)
|
||||
translate(ifstmt)
|
||||
} else {
|
||||
// Step is a variable. We can't optimize anything...
|
||||
@ -1836,7 +1835,7 @@ private class StatementTranslator(private val prog: IntermediateProgram,
|
||||
translate(body)
|
||||
prog.label(continueLabel)
|
||||
val lvTarget = makeAssignmentTarget()
|
||||
lvTarget.linkParents(range.parent)
|
||||
lvTarget.linkParents(body)
|
||||
val targetStatement: VarDecl? =
|
||||
if(lvTarget.identifier!=null) {
|
||||
lvTarget.identifier.targetStatement(namespace) as VarDecl
|
||||
@ -1850,7 +1849,7 @@ private class StatementTranslator(private val prog: IntermediateProgram,
|
||||
1 -> {
|
||||
// LV++
|
||||
val postIncr = PostIncrDecr(lvTarget, "++", range.position)
|
||||
postIncr.linkParents(range.parent)
|
||||
postIncr.linkParents(body)
|
||||
translate(postIncr)
|
||||
if(lvTarget.register!=null)
|
||||
prog.instr(Opcode.PUSH_VAR_BYTE, callLabel =lvTarget.register.toString())
|
||||
@ -1863,13 +1862,13 @@ private class StatementTranslator(private val prog: IntermediateProgram,
|
||||
AnonymousScope(mutableListOf(Jump(null, null, loopLabel, range.position)), range.position),
|
||||
AnonymousScope(mutableListOf(), range.position),
|
||||
range.position)
|
||||
branch.linkParents(range.parent)
|
||||
branch.linkParents(body)
|
||||
translate(branch)
|
||||
}
|
||||
-1 -> {
|
||||
// LV--
|
||||
val postIncr = PostIncrDecr(makeAssignmentTarget(), "--", range.position)
|
||||
postIncr.linkParents(range.parent)
|
||||
postIncr.linkParents(body)
|
||||
translate(postIncr)
|
||||
TODO("signed numbers and/or special condition are needed for decreasing for loop. Try an increasing loop and/or constant loop values instead? At: ${range.position}") // fix with signed numbers
|
||||
}
|
||||
|
@ -679,8 +679,8 @@ class AsmGen(val options: CompilationOptions, val program: IntermediateProgram,
|
||||
Opcode.CAST_B_TO_F -> " jsr prog8_lib.stack_b2float"
|
||||
Opcode.CAST_UW_TO_F -> " jsr prog8_lib.stack_uw2float"
|
||||
Opcode.CAST_W_TO_F -> " jsr prog8_lib.stack_w2float"
|
||||
Opcode.CAST_F_TO_UB -> " jsr prog8_lib.stack_float2ub"
|
||||
Opcode.CAST_F_TO_B -> " jsr prog8_lib.stack_float2b"
|
||||
Opcode.CAST_F_TO_UB -> " jsr prog8_lib.stack_float2uw"
|
||||
Opcode.CAST_F_TO_B -> " jsr prog8_lib.stack_float2w"
|
||||
Opcode.CAST_F_TO_UW -> " jsr prog8_lib.stack_float2uw"
|
||||
Opcode.CAST_F_TO_W -> " jsr prog8_lib.stack_float2w"
|
||||
Opcode.CAST_UB_TO_UW, Opcode.CAST_UB_TO_W -> " lda #0 | sta ${ESTACK_HI+1},x" // clear the msb
|
||||
|
@ -48,8 +48,7 @@ It consists of compilation options and other directives, imports of other module
|
||||
and source code for one or more code blocks.
|
||||
|
||||
Prog8 has a couple of *LIBRARY* modules that are defined in special internal files provided by the compiler:
|
||||
``c64lib``, ``prog8lib``, ``mathlib``.
|
||||
You should not overwrite these or reuse their names.
|
||||
``c64lib``, ``c64utils``, ``prog8lib``. You should not overwrite these or reuse their names.
|
||||
|
||||
|
||||
.. _debugging:
|
||||
|
@ -543,6 +543,8 @@ sum(x)
|
||||
len(x)
|
||||
Number of values in the array value x, or the number of characters in a string (excluding the size or 0-byte).
|
||||
Note: this can be different from the number of *bytes* in memory if the datatype isn't a byte.
|
||||
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(string) may no longer be correct!
|
||||
|
||||
lsb(x)
|
||||
Get the least significant byte of the word x. Equivalent to the cast "x as ubyte".
|
||||
|
@ -281,6 +281,132 @@ asmsub str2byte(str string @ AY) -> clobbers(Y) -> (byte @ A) {
|
||||
|
||||
; @todo string to 32 bit unsigned integer http://www.6502.org/source/strings/ascii-to-32bit.html
|
||||
|
||||
%asm {{
|
||||
; copy memory UP from (SCRATCH_ZPWORD1) to (SCRATCH_ZPWORD2) of length X/Y (16-bit, X=lo, Y=hi)
|
||||
; clobbers register A,X,Y
|
||||
memcopy16_up .proc
|
||||
source = SCRATCH_ZPWORD1
|
||||
dest = SCRATCH_ZPWORD2
|
||||
length = SCRATCH_ZPB1 ; (and SCRATCH_ZPREG)
|
||||
|
||||
stx length
|
||||
sty length+1
|
||||
|
||||
ldx length ; move low byte of length into X
|
||||
bne + ; jump to start if X > 0
|
||||
dec length ; subtract 1 from length
|
||||
+ ldy #0 ; set Y to 0
|
||||
- lda (source),y ; set A to whatever (source) points to offset by Y
|
||||
sta (dest),y ; move A to location pointed to by (dest) offset by Y
|
||||
iny ; increment Y
|
||||
bne + ; if Y<>0 then (rolled over) then still moving bytes
|
||||
inc source+1 ; increment hi byte of source
|
||||
inc dest+1 ; increment hi byte of dest
|
||||
+ dex ; decrement X (lo byte counter)
|
||||
bne - ; if X<>0 then move another byte
|
||||
dec length ; weve moved 255 bytes, dec length
|
||||
bpl - ; if length is still positive go back and move more
|
||||
rts ; done
|
||||
.pend
|
||||
|
||||
|
||||
; copy memory UP from (SCRATCH_ZPWORD1) to (AY) with length X (1 to 256, 0 meaning 256)
|
||||
; destination must not overlap, or be before start, then overlap is possible.
|
||||
; clobbers A, X, Y
|
||||
|
||||
memcopy .proc
|
||||
sta SCRATCH_ZPWORD2
|
||||
sty SCRATCH_ZPWORD2+1
|
||||
ldy #0
|
||||
- lda (SCRATCH_ZPWORD1), y
|
||||
sta (SCRATCH_ZPWORD2), y
|
||||
iny
|
||||
dex
|
||||
bne -
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
; fill memory from (SCRATCH_ZPWORD1), length XY, with value in A.
|
||||
; clobbers X, Y
|
||||
memset .proc
|
||||
stx SCRATCH_ZPB1
|
||||
sty SCRATCH_ZPREG
|
||||
ldy #0
|
||||
ldx SCRATCH_ZPREG
|
||||
beq _lastpage
|
||||
|
||||
_fullpage sta (SCRATCH_ZPWORD1),y
|
||||
iny
|
||||
bne _fullpage
|
||||
inc SCRATCH_ZPWORD1+1 ; next page
|
||||
dex
|
||||
bne _fullpage
|
||||
|
||||
_lastpage ldy SCRATCH_ZPB1
|
||||
beq +
|
||||
- dey
|
||||
sta (SCRATCH_ZPWORD1),y
|
||||
bne -
|
||||
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
|
||||
; fill memory from (SCRATCH_ZPWORD1) number of words in SCRATCH_ZPWORD2, with word value in AY.
|
||||
; clobbers A, X, Y
|
||||
memsetw .proc
|
||||
sta _mod1+1 ; self-modify
|
||||
sty _mod1b+1 ; self-modify
|
||||
sta _mod2+1 ; self-modify
|
||||
sty _mod2b+1 ; self-modify
|
||||
ldx SCRATCH_ZPWORD1
|
||||
stx SCRATCH_ZPB1
|
||||
ldx SCRATCH_ZPWORD1+1
|
||||
inx
|
||||
stx SCRATCH_ZPREG ; second page
|
||||
|
||||
ldy #0
|
||||
ldx SCRATCH_ZPWORD2+1
|
||||
beq _lastpage
|
||||
|
||||
_fullpage
|
||||
_mod1 lda #0 ; self-modified
|
||||
sta (SCRATCH_ZPWORD1),y ; first page
|
||||
sta (SCRATCH_ZPB1),y ; second page
|
||||
iny
|
||||
_mod1b lda #0 ; self-modified
|
||||
sta (SCRATCH_ZPWORD1),y ; first page
|
||||
sta (SCRATCH_ZPB1),y ; second page
|
||||
iny
|
||||
bne _fullpage
|
||||
inc SCRATCH_ZPWORD1+1 ; next page pair
|
||||
inc SCRATCH_ZPWORD1+1 ; next page pair
|
||||
inc SCRATCH_ZPB1+1 ; next page pair
|
||||
inc SCRATCH_ZPB1+1 ; next page pair
|
||||
dex
|
||||
bne _fullpage
|
||||
|
||||
_lastpage ldx SCRATCH_ZPWORD2
|
||||
beq _done
|
||||
|
||||
ldy #0
|
||||
-
|
||||
_mod2 lda #0 ; self-modified
|
||||
sta (SCRATCH_ZPWORD1), y
|
||||
inc SCRATCH_ZPWORD1
|
||||
bne _mod2b
|
||||
inc SCRATCH_ZPWORD1+1
|
||||
_mod2b lda #0 ; self-modified
|
||||
sta (SCRATCH_ZPWORD1), y
|
||||
inc SCRATCH_ZPWORD1
|
||||
bne +
|
||||
inc SCRATCH_ZPWORD1+1
|
||||
+ dex
|
||||
bne -
|
||||
_done rts
|
||||
.pend
|
||||
}}
|
||||
|
||||
} ; ------ end of block c64utils
|
||||
|
||||
|
@ -1,255 +0,0 @@
|
||||
; Prog8 integer math library for 6502
|
||||
; (floating point math is done via the C-64's BASIC ROM routines)
|
||||
;
|
||||
; some more interesting routines can be found here:
|
||||
; http://6502org.wikidot.com/software-math
|
||||
; http://codebase64.org/doku.php?id=base:6502_6510_maths
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
|
||||
~ math {
|
||||
; note: the following ZP scratch registers must be the same as in c64lib
|
||||
memory ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
|
||||
memory ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
|
||||
memory uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
||||
memory uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
||||
|
||||
|
||||
|
||||
asmsub multiply_bytes (ubyte byte1 @ A, ubyte byte2 @ Y) -> clobbers() -> (ubyte @ A) {
|
||||
; ---- multiply 2 bytes, result as byte in A (signed or unsigned)
|
||||
%asm {{
|
||||
sta SCRATCH_ZPB1 ; num1
|
||||
sty SCRATCH_ZPREG ; num2
|
||||
lda #0
|
||||
beq _enterloop
|
||||
_doAdd clc
|
||||
adc SCRATCH_ZPB1
|
||||
_loop asl SCRATCH_ZPB1
|
||||
_enterloop lsr SCRATCH_ZPREG
|
||||
bcs _doAdd
|
||||
bne _loop
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub multiply_bytes_16 (ubyte byte1 @ A, ubyte byte2 @ Y) -> clobbers(X) -> (uword @ AY) {
|
||||
; ---- multiply 2 bytes, result as word in A/Y (unsigned)
|
||||
%asm {{
|
||||
sta SCRATCH_ZPB1
|
||||
sty SCRATCH_ZPREG
|
||||
lda #0
|
||||
ldx #8
|
||||
lsr SCRATCH_ZPB1
|
||||
- bcc +
|
||||
clc
|
||||
adc SCRATCH_ZPREG
|
||||
+ ror a
|
||||
ror SCRATCH_ZPB1
|
||||
dex
|
||||
bne -
|
||||
tay
|
||||
lda SCRATCH_ZPB1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub multiply_words (uword number @ AY) -> clobbers(A,X) -> () {
|
||||
; ---- multiply two 16-bit words into a 32-bit result (signed and unsigned)
|
||||
; input: A/Y = first 16-bit number, SCRATCH_ZPWORD1 in ZP = second 16-bit number
|
||||
; output: multiply_words.result 4-bytes/32-bits product, LSB order (low-to-high)
|
||||
|
||||
%asm {{
|
||||
sta SCRATCH_ZPWORD2
|
||||
sty SCRATCH_ZPWORD2+1
|
||||
|
||||
mult16 lda #$00
|
||||
sta multiply_words_result+2 ; clear upper bits of product
|
||||
sta multiply_words_result+3
|
||||
ldx #16 ; for all 16 bits...
|
||||
- lsr SCRATCH_ZPWORD1+1 ; divide multiplier by 2
|
||||
ror SCRATCH_ZPWORD1
|
||||
bcc +
|
||||
lda multiply_words_result+2 ; get upper half of product and add multiplicand
|
||||
clc
|
||||
adc SCRATCH_ZPWORD2
|
||||
sta multiply_words_result+2
|
||||
lda multiply_words_result+3
|
||||
adc SCRATCH_ZPWORD2+1
|
||||
+ ror a ; rotate partial product
|
||||
sta multiply_words_result+3
|
||||
ror multiply_words_result+2
|
||||
ror multiply_words_result+1
|
||||
ror multiply_words_result
|
||||
dex
|
||||
bne -
|
||||
rts
|
||||
|
||||
multiply_words_result .byte 0,0,0,0
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub divmod_bytes (ubyte number @ X, ubyte divisor @ Y) -> clobbers() -> (ubyte @ X, ubyte @ A) {
|
||||
; ---- divide X by Y, result quotient in X, remainder in A (unsigned)
|
||||
; division by zero will result in quotient = 255 and remainder = original number
|
||||
%asm {{
|
||||
stx SCRATCH_ZPB1
|
||||
sty SCRATCH_ZPREG
|
||||
|
||||
lda #0
|
||||
ldx #8
|
||||
asl SCRATCH_ZPB1
|
||||
- rol a
|
||||
cmp SCRATCH_ZPREG
|
||||
bcc +
|
||||
sbc SCRATCH_ZPREG
|
||||
+ rol SCRATCH_ZPB1
|
||||
dex
|
||||
bne -
|
||||
|
||||
ldx SCRATCH_ZPB1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub divmod_words (uword divisor @ AY) -> clobbers(X) -> (uword @ AY) {
|
||||
; ---- divide two words (16 bit each) into 16 bit results
|
||||
; input: SCRATCH_ZPWORD1 in ZP: 16 bit number, A/Y: 16 bit divisor
|
||||
; output: SCRATCH_ZPWORD1 in ZP: 16 bit result, A/Y: 16 bit remainder
|
||||
; division by zero will result in quotient = 65535 and remainder = divident
|
||||
|
||||
%asm {{
|
||||
remainder = SCRATCH_ZPB1
|
||||
|
||||
sta SCRATCH_ZPWORD2
|
||||
sty SCRATCH_ZPWORD2+1
|
||||
lda #0 ;preset remainder to 0
|
||||
sta remainder
|
||||
sta remainder+1
|
||||
ldx #16 ;repeat for each bit: ...
|
||||
|
||||
- asl SCRATCH_ZPWORD1 ;number lb & hb*2, msb -> Carry
|
||||
rol SCRATCH_ZPWORD1+1
|
||||
rol remainder ;remainder lb & hb * 2 + msb from carry
|
||||
rol remainder+1
|
||||
lda remainder
|
||||
sec
|
||||
sbc SCRATCH_ZPWORD2 ;substract divisor to see if it fits in
|
||||
tay ;lb result -> Y, for we may need it later
|
||||
lda remainder+1
|
||||
sbc SCRATCH_ZPWORD2+1
|
||||
bcc + ;if carry=0 then divisor didn't fit in yet
|
||||
|
||||
sta remainder+1 ;else save substraction result as new remainder,
|
||||
sty remainder
|
||||
inc SCRATCH_ZPWORD1 ;and INCrement result cause divisor fit in 1 times
|
||||
|
||||
+ dex
|
||||
bne -
|
||||
|
||||
lda remainder ; copy remainder to ZPWORD2 result register
|
||||
sta SCRATCH_ZPWORD2
|
||||
lda remainder+1
|
||||
sta SCRATCH_ZPWORD2+1
|
||||
|
||||
lda SCRATCH_ZPWORD1 ; load division result in A/Y
|
||||
ldy SCRATCH_ZPWORD1+1
|
||||
|
||||
rts
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub randseed (uword seed @ AY) -> clobbers(A, Y) -> () {
|
||||
; ---- reset the random seeds for the byte and word random generators
|
||||
; default starting values are: A=$2c Y=$9e
|
||||
%asm {{
|
||||
sta randword._seed
|
||||
sty randword._seed+1
|
||||
stx SCRATCH_ZPREG
|
||||
clc
|
||||
adc #14
|
||||
sta randbyte._seed
|
||||
ora #$80 ; make negative
|
||||
jsr c64.FREADSA
|
||||
jsr c64.RND ; reseed the float rng using the (negative) number in A
|
||||
ldx SCRATCH_ZPREG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub randbyte () -> clobbers() -> (ubyte @ A) {
|
||||
; ---- 8-bit pseudo random number generator into A
|
||||
|
||||
%asm {{
|
||||
lda _seed
|
||||
beq +
|
||||
asl a
|
||||
beq ++ ;if the input was $80, skip the EOR
|
||||
bcc ++
|
||||
+ eor _magic ; #$1d ; could be self-modifying code to set new magic
|
||||
+ sta _seed
|
||||
rts
|
||||
|
||||
_seed .byte $3a
|
||||
_magic .byte $1d
|
||||
_magiceors .byte $1d, $2b, $2d, $4d, $5f, $63, $65, $69
|
||||
.byte $71, $87, $8d, $a9, $c3, $cf, $e7, $f5
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub randword () -> clobbers() -> (uword @ AY) {
|
||||
; ---- 16 bit pseudo random number generator into AY
|
||||
|
||||
%asm {{
|
||||
lda _seed
|
||||
beq _lowZero ; $0000 and $8000 are special values to test for
|
||||
|
||||
; Do a normal shift
|
||||
asl _seed
|
||||
lda _seed+1
|
||||
rol a
|
||||
bcc _noEor
|
||||
|
||||
_doEor ; high byte is in A
|
||||
eor _magic+1 ; #>magic ; could be self-modifying code to set new magic
|
||||
sta _seed+1
|
||||
lda _seed
|
||||
eor _magic ; #<magic ; could be self-modifying code to set new magic
|
||||
sta _seed
|
||||
ldy _seed+1
|
||||
rts
|
||||
|
||||
_lowZero lda _seed+1
|
||||
beq _doEor ; High byte is also zero, so apply the EOR
|
||||
; For speed, you could store 'magic' into 'seed' directly
|
||||
; instead of running the EORs
|
||||
|
||||
; wasn't zero, check for $8000
|
||||
asl a
|
||||
beq _noEor ; if $00 is left after the shift, then it was $80
|
||||
bcs _doEor ; else, do the EOR based on the carry bit as usual
|
||||
|
||||
_noEor sta _seed+1
|
||||
tay
|
||||
lda _seed
|
||||
rts
|
||||
|
||||
|
||||
_seed .word $2c9e
|
||||
_magic .word $3f1d
|
||||
_magiceors .word $3f1d, $3f81, $3fa5, $3fc5, $4075, $409d, $40cd, $4109
|
||||
.word $413f, $414b, $4153, $4159, $4193, $4199, $41af, $41bb
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -125,6 +125,30 @@ stack_uw2float .proc
|
||||
jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
stack_float2w .proc
|
||||
jsr pop_float_fac1
|
||||
stx SCRATCH_ZPREGX
|
||||
jsr c64.AYINT
|
||||
ldx SCRATCH_ZPREGX
|
||||
lda $64
|
||||
sta ESTACK_HI,x
|
||||
lda $65
|
||||
sta ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
stack_float2uw .proc
|
||||
jsr pop_float_fac1
|
||||
stx SCRATCH_ZPREGX
|
||||
jsr c64.GETADR
|
||||
ldx SCRATCH_ZPREGX
|
||||
sta ESTACK_HI,x
|
||||
tya
|
||||
sta ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
push_float .proc
|
||||
; ---- push mflpt5 in A/Y onto stack
|
||||
@ -1005,29 +1029,6 @@ func_ceil .proc
|
||||
+ jmp push_fac1_as_result
|
||||
.pend
|
||||
|
||||
func_fintb .proc
|
||||
jsr pop_float_fac1
|
||||
stx SCRATCH_ZPREGX
|
||||
jsr c64.AYINT
|
||||
lda $65
|
||||
sta ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
func_fintw .proc
|
||||
jsr pop_float_fac1
|
||||
stx SCRATCH_ZPREGX
|
||||
jsr c64.AYINT
|
||||
lda $64
|
||||
sta ESTACK_HI,x
|
||||
lda $65
|
||||
sta ESTACK_LO,x
|
||||
dex
|
||||
rts
|
||||
.pend
|
||||
|
||||
|
||||
|
||||
peek_address .proc
|
||||
; -- peek address on stack into SCRATCH_ZPWORD1
|
||||
@ -1487,73 +1488,255 @@ func_rndf .proc
|
||||
_rndf_rnum5 .byte 0,0,0,0,0
|
||||
.pend
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
; @todo python code for a str-to-ubyte function that doesn't use the basic rom:
|
||||
;def str2ubyte(s, slen):
|
||||
; hundreds_map = {
|
||||
; 0: 0,
|
||||
; 1: 100,
|
||||
; 2: 200
|
||||
; }
|
||||
; digitvalue = 0
|
||||
; result = 0
|
||||
; if slen==0:
|
||||
; return digitvalue
|
||||
; digitvalue = ord(s[slen-1])-48
|
||||
; slen -= 1
|
||||
; if slen==0:
|
||||
; return digitvalue
|
||||
; result = digitvalue
|
||||
; digitvalue = 10 * (ord(s[slen-1])-48)
|
||||
; result += digitvalue
|
||||
; slen -= 1
|
||||
; if slen==0:
|
||||
; return result
|
||||
; digitvalue = hundreds_map[ord(s[slen-1])-48]
|
||||
; result += digitvalue
|
||||
; return result
|
||||
|
||||
func_str2uword .proc
|
||||
;-- convert string (address on stack) to uword number (also used by str2ubyte)
|
||||
lda ESTACK_LO+1,x
|
||||
sta $22
|
||||
lda ESTACK_HI+1,x
|
||||
sta $23
|
||||
jsr _strlen2233
|
||||
tya
|
||||
stx SCRATCH_ZPREG
|
||||
jsr c64.FREADSTR ; string to fac1
|
||||
jsr c64.GETADR ; fac1 to unsigned word in Y/A
|
||||
ldx SCRATCH_ZPREG
|
||||
sta ESTACK_HI+1,x
|
||||
tya
|
||||
sta ESTACK_LO+1,x
|
||||
~ math {
|
||||
|
||||
; some more interesting routines can be found here:
|
||||
; http://6502org.wikidot.com/software-math
|
||||
; http://codebase64.org/doku.php?id=base:6502_6510_maths
|
||||
;
|
||||
; note: the following ZP scratch registers must be the same as in c64lib
|
||||
memory ubyte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
|
||||
memory ubyte SCRATCH_ZPREG = $03 ; scratch register in ZP
|
||||
memory uword SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
||||
memory uword SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
||||
|
||||
|
||||
|
||||
asmsub multiply_bytes (ubyte byte1 @ A, ubyte byte2 @ Y) -> clobbers() -> (ubyte @ A) {
|
||||
; ---- multiply 2 bytes, result as byte in A (signed or unsigned)
|
||||
%asm {{
|
||||
sta SCRATCH_ZPB1 ; num1
|
||||
sty SCRATCH_ZPREG ; num2
|
||||
lda #0
|
||||
beq _enterloop
|
||||
_doAdd clc
|
||||
adc SCRATCH_ZPB1
|
||||
_loop asl SCRATCH_ZPB1
|
||||
_enterloop lsr SCRATCH_ZPREG
|
||||
bcs _doAdd
|
||||
bne _loop
|
||||
rts
|
||||
_strlen2233
|
||||
;-- return the length of the (zero-terminated) string at $22/$23, in Y
|
||||
ldy #0
|
||||
- lda ($22),y
|
||||
beq +
|
||||
iny
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub multiply_bytes_16 (ubyte byte1 @ A, ubyte byte2 @ Y) -> clobbers(X) -> (uword @ AY) {
|
||||
; ---- multiply 2 bytes, result as word in A/Y (unsigned)
|
||||
%asm {{
|
||||
sta SCRATCH_ZPB1
|
||||
sty SCRATCH_ZPREG
|
||||
lda #0
|
||||
ldx #8
|
||||
lsr SCRATCH_ZPB1
|
||||
- bcc +
|
||||
clc
|
||||
adc SCRATCH_ZPREG
|
||||
+ ror a
|
||||
ror SCRATCH_ZPB1
|
||||
dex
|
||||
bne -
|
||||
+ rts
|
||||
.pend
|
||||
|
||||
func_str2word .proc
|
||||
tay
|
||||
lda SCRATCH_ZPB1
|
||||
rts
|
||||
.warn "str2word not implemented"
|
||||
.pend
|
||||
}}
|
||||
}
|
||||
|
||||
func_str2byte .proc
|
||||
asmsub multiply_words (uword number @ AY) -> clobbers(A,X) -> () {
|
||||
; ---- multiply two 16-bit words into a 32-bit result (signed and unsigned)
|
||||
; input: A/Y = first 16-bit number, SCRATCH_ZPWORD1 in ZP = second 16-bit number
|
||||
; output: multiply_words.result 4-bytes/32-bits product, LSB order (low-to-high)
|
||||
|
||||
%asm {{
|
||||
sta SCRATCH_ZPWORD2
|
||||
sty SCRATCH_ZPWORD2+1
|
||||
|
||||
mult16 lda #$00
|
||||
sta multiply_words_result+2 ; clear upper bits of product
|
||||
sta multiply_words_result+3
|
||||
ldx #16 ; for all 16 bits...
|
||||
- lsr SCRATCH_ZPWORD1+1 ; divide multiplier by 2
|
||||
ror SCRATCH_ZPWORD1
|
||||
bcc +
|
||||
lda multiply_words_result+2 ; get upper half of product and add multiplicand
|
||||
clc
|
||||
adc SCRATCH_ZPWORD2
|
||||
sta multiply_words_result+2
|
||||
lda multiply_words_result+3
|
||||
adc SCRATCH_ZPWORD2+1
|
||||
+ ror a ; rotate partial product
|
||||
sta multiply_words_result+3
|
||||
ror multiply_words_result+2
|
||||
ror multiply_words_result+1
|
||||
ror multiply_words_result
|
||||
dex
|
||||
bne -
|
||||
rts
|
||||
.warn "str2byte not implemented"
|
||||
.pend
|
||||
|
||||
func_str2float .proc
|
||||
rts
|
||||
.warn "str2float not implemented"
|
||||
.pend
|
||||
|
||||
multiply_words_result .byte 0,0,0,0
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub divmod_bytes (ubyte number @ X, ubyte divisor @ Y) -> clobbers() -> (ubyte @ X, ubyte @ A) {
|
||||
; ---- divide X by Y, result quotient in X, remainder in A (unsigned)
|
||||
; division by zero will result in quotient = 255 and remainder = original number
|
||||
%asm {{
|
||||
stx SCRATCH_ZPB1
|
||||
sty SCRATCH_ZPREG
|
||||
|
||||
lda #0
|
||||
ldx #8
|
||||
asl SCRATCH_ZPB1
|
||||
- rol a
|
||||
cmp SCRATCH_ZPREG
|
||||
bcc +
|
||||
sbc SCRATCH_ZPREG
|
||||
+ rol SCRATCH_ZPB1
|
||||
dex
|
||||
bne -
|
||||
|
||||
ldx SCRATCH_ZPB1
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub divmod_words (uword divisor @ AY) -> clobbers(X) -> (uword @ AY) {
|
||||
; ---- divide two words (16 bit each) into 16 bit results
|
||||
; input: SCRATCH_ZPWORD1 in ZP: 16 bit number, A/Y: 16 bit divisor
|
||||
; output: SCRATCH_ZPWORD1 in ZP: 16 bit result, A/Y: 16 bit remainder
|
||||
; division by zero will result in quotient = 65535 and remainder = divident
|
||||
|
||||
%asm {{
|
||||
remainder = SCRATCH_ZPB1
|
||||
|
||||
sta SCRATCH_ZPWORD2
|
||||
sty SCRATCH_ZPWORD2+1
|
||||
lda #0 ;preset remainder to 0
|
||||
sta remainder
|
||||
sta remainder+1
|
||||
ldx #16 ;repeat for each bit: ...
|
||||
|
||||
- asl SCRATCH_ZPWORD1 ;number lb & hb*2, msb -> Carry
|
||||
rol SCRATCH_ZPWORD1+1
|
||||
rol remainder ;remainder lb & hb * 2 + msb from carry
|
||||
rol remainder+1
|
||||
lda remainder
|
||||
sec
|
||||
sbc SCRATCH_ZPWORD2 ;substract divisor to see if it fits in
|
||||
tay ;lb result -> Y, for we may need it later
|
||||
lda remainder+1
|
||||
sbc SCRATCH_ZPWORD2+1
|
||||
bcc + ;if carry=0 then divisor didn't fit in yet
|
||||
|
||||
sta remainder+1 ;else save substraction result as new remainder,
|
||||
sty remainder
|
||||
inc SCRATCH_ZPWORD1 ;and INCrement result cause divisor fit in 1 times
|
||||
|
||||
+ dex
|
||||
bne -
|
||||
|
||||
lda remainder ; copy remainder to ZPWORD2 result register
|
||||
sta SCRATCH_ZPWORD2
|
||||
lda remainder+1
|
||||
sta SCRATCH_ZPWORD2+1
|
||||
|
||||
lda SCRATCH_ZPWORD1 ; load division result in A/Y
|
||||
ldy SCRATCH_ZPWORD1+1
|
||||
|
||||
rts
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub randseed (uword seed @ AY) -> clobbers(A, Y) -> () {
|
||||
; ---- reset the random seeds for the byte and word random generators
|
||||
; default starting values are: A=$2c Y=$9e
|
||||
%asm {{
|
||||
sta randword._seed
|
||||
sty randword._seed+1
|
||||
stx SCRATCH_ZPREG
|
||||
clc
|
||||
adc #14
|
||||
sta randbyte._seed
|
||||
ora #$80 ; make negative
|
||||
jsr c64.FREADSA
|
||||
jsr c64.RND ; reseed the float rng using the (negative) number in A
|
||||
ldx SCRATCH_ZPREG
|
||||
rts
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
asmsub randbyte () -> clobbers() -> (ubyte @ A) {
|
||||
; ---- 8-bit pseudo random number generator into A
|
||||
|
||||
%asm {{
|
||||
lda _seed
|
||||
beq +
|
||||
asl a
|
||||
beq ++ ;if the input was $80, skip the EOR
|
||||
bcc ++
|
||||
+ eor _magic ; #$1d ; could be self-modifying code to set new magic
|
||||
+ sta _seed
|
||||
rts
|
||||
|
||||
_seed .byte $3a
|
||||
_magic .byte $1d
|
||||
_magiceors .byte $1d, $2b, $2d, $4d, $5f, $63, $65, $69
|
||||
.byte $71, $87, $8d, $a9, $c3, $cf, $e7, $f5
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
asmsub randword () -> clobbers() -> (uword @ AY) {
|
||||
; ---- 16 bit pseudo random number generator into AY
|
||||
|
||||
%asm {{
|
||||
lda _seed
|
||||
beq _lowZero ; $0000 and $8000 are special values to test for
|
||||
|
||||
; Do a normal shift
|
||||
asl _seed
|
||||
lda _seed+1
|
||||
rol a
|
||||
bcc _noEor
|
||||
|
||||
_doEor ; high byte is in A
|
||||
eor _magic+1 ; #>magic ; could be self-modifying code to set new magic
|
||||
sta _seed+1
|
||||
lda _seed
|
||||
eor _magic ; #<magic ; could be self-modifying code to set new magic
|
||||
sta _seed
|
||||
ldy _seed+1
|
||||
rts
|
||||
|
||||
_lowZero lda _seed+1
|
||||
beq _doEor ; High byte is also zero, so apply the EOR
|
||||
; For speed, you could store 'magic' into 'seed' directly
|
||||
; instead of running the EORs
|
||||
|
||||
; wasn't zero, check for $8000
|
||||
asl a
|
||||
beq _noEor ; if $00 is left after the shift, then it was $80
|
||||
bcs _doEor ; else, do the EOR based on the carry bit as usual
|
||||
|
||||
_noEor sta _seed+1
|
||||
tay
|
||||
lda _seed
|
||||
rts
|
||||
|
||||
|
||||
_seed .word $2c9e
|
||||
_magic .word $3f1d
|
||||
_magiceors .word $3f1d, $3f81, $3fa5, $3fc5, $4075, $409d, $40cd, $4109
|
||||
.word $413f, $414b, $4153, $4159, $4193, $4199, $41af, $41bb
|
||||
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,276 +0,0 @@
|
||||
; Prog8 internal library routines - always included by the compiler
|
||||
;
|
||||
; Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
;
|
||||
; indent format: TABS, size=8
|
||||
|
||||
|
||||
~ prog8_lib {
|
||||
; note: the following ZP scratch registers must be the same as in c64lib
|
||||
memory byte SCRATCH_ZPB1 = $02 ; scratch byte 1 in ZP
|
||||
memory byte SCRATCH_ZPREG = $03 ; scratch register in ZP
|
||||
memory word SCRATCH_ZPWORD1 = $fb ; scratch word in ZP ($fb/$fc)
|
||||
memory word SCRATCH_ZPWORD2 = $fd ; scratch word in ZP ($fd/$fe)
|
||||
|
||||
|
||||
%asm {{
|
||||
; ---- jmp (indirect) routines for register pairs containing the indirect address
|
||||
; @todo still needed??
|
||||
jsr_indirect_nozpuse_AX
|
||||
sta jsr_indirect_tmp
|
||||
stx jsr_indirect_tmp+1
|
||||
jmp (jsr_indirect_tmp)
|
||||
jsr_indirect_nozpuse_AY
|
||||
sta jsr_indirect_tmp
|
||||
sty jsr_indirect_tmp+1
|
||||
jmp (jsr_indirect_tmp)
|
||||
jsr_indirect_nozpuse_XY
|
||||
stx jsr_indirect_tmp
|
||||
sty jsr_indirect_tmp+1
|
||||
jmp (jsr_indirect_tmp)
|
||||
jsr_indirect_tmp
|
||||
.byte 0, 0
|
||||
|
||||
|
||||
jsr_indirect_AX
|
||||
sta SCRATCH_ZPB1
|
||||
stx SCRATCH_ZPREG
|
||||
jmp (SCRATCH_ZPB1)
|
||||
jsr_indirect_AY
|
||||
sta SCRATCH_ZPB1
|
||||
sty SCRATCH_ZPREG
|
||||
jmp (SCRATCH_ZPB1)
|
||||
jsr_indirect_XY
|
||||
stx SCRATCH_ZPB1
|
||||
sty SCRATCH_ZPREG
|
||||
jmp (SCRATCH_ZPB1)
|
||||
|
||||
|
||||
; copy memory UP from (SCRATCH_ZPWORD1) to (SCRATCH_ZPWORD2) of length X/Y (16-bit, X=lo, Y=hi)
|
||||
; clobbers register A,X,Y
|
||||
memcopy16_up
|
||||
source = SCRATCH_ZPWORD1
|
||||
dest = SCRATCH_ZPWORD2
|
||||
length = SCRATCH_ZPB1 ; (and SCRATCH_ZPREG)
|
||||
|
||||
stx length
|
||||
sty length+1
|
||||
|
||||
ldx length ; move low byte of length into X
|
||||
bne + ; jump to start if X > 0
|
||||
dec length ; subtract 1 from length
|
||||
+ ldy #0 ; set Y to 0
|
||||
- lda (source),y ; set A to whatever (source) points to offset by Y
|
||||
sta (dest),y ; move A to location pointed to by (dest) offset by Y
|
||||
iny ; increment Y
|
||||
bne + ; if Y<>0 then (rolled over) then still moving bytes
|
||||
inc source+1 ; increment hi byte of source
|
||||
inc dest+1 ; increment hi byte of dest
|
||||
+ dex ; decrement X (lo byte counter)
|
||||
bne - ; if X<>0 then move another byte
|
||||
dec length ; weve moved 255 bytes, dec length
|
||||
bpl - ; if length is still positive go back and move more
|
||||
rts ; done
|
||||
|
||||
|
||||
; copy memory UP from (SCRATCH_ZPWORD1) to (AY) with length X (1 to 256, 0 meaning 256)
|
||||
; destination must not overlap, or be before start, then overlap is possible.
|
||||
; clobbers A, X, Y
|
||||
|
||||
memcopy
|
||||
sta SCRATCH_ZPWORD2
|
||||
sty SCRATCH_ZPWORD2+1
|
||||
ldy #0
|
||||
- lda (SCRATCH_ZPWORD1), y
|
||||
sta (SCRATCH_ZPWORD2), y
|
||||
iny
|
||||
dex
|
||||
bne -
|
||||
rts
|
||||
|
||||
|
||||
; fill memory from (SCRATCH_ZPWORD1), length XY, with value in A.
|
||||
; clobbers X, Y
|
||||
memset stx SCRATCH_ZPB1
|
||||
sty SCRATCH_ZPREG
|
||||
ldy #0
|
||||
ldx SCRATCH_ZPREG
|
||||
beq _lastpage
|
||||
|
||||
_fullpage sta (SCRATCH_ZPWORD1),y
|
||||
iny
|
||||
bne _fullpage
|
||||
inc SCRATCH_ZPWORD1+1 ; next page
|
||||
dex
|
||||
bne _fullpage
|
||||
|
||||
_lastpage ldy SCRATCH_ZPB1
|
||||
beq +
|
||||
- dey
|
||||
sta (SCRATCH_ZPWORD1),y
|
||||
bne -
|
||||
|
||||
+ rts
|
||||
|
||||
|
||||
|
||||
; fill memory from (SCRATCH_ZPWORD1) number of words in SCRATCH_ZPWORD2, with word value in AY.
|
||||
; clobbers A, X, Y
|
||||
memsetw
|
||||
sta _mod1+1 ; self-modify
|
||||
sty _mod1b+1 ; self-modify
|
||||
sta _mod2+1 ; self-modify
|
||||
sty _mod2b+1 ; self-modify
|
||||
ldx SCRATCH_ZPWORD1
|
||||
stx SCRATCH_ZPB1
|
||||
ldx SCRATCH_ZPWORD1+1
|
||||
inx
|
||||
stx SCRATCH_ZPREG ; second page
|
||||
|
||||
ldy #0
|
||||
ldx SCRATCH_ZPWORD2+1
|
||||
beq _lastpage
|
||||
|
||||
_fullpage
|
||||
_mod1 lda #0 ; self-modified
|
||||
sta (SCRATCH_ZPWORD1),y ; first page
|
||||
sta (SCRATCH_ZPB1),y ; second page
|
||||
iny
|
||||
_mod1b lda #0 ; self-modified
|
||||
sta (SCRATCH_ZPWORD1),y ; first page
|
||||
sta (SCRATCH_ZPB1),y ; second page
|
||||
iny
|
||||
bne _fullpage
|
||||
inc SCRATCH_ZPWORD1+1 ; next page pair
|
||||
inc SCRATCH_ZPWORD1+1 ; next page pair
|
||||
inc SCRATCH_ZPB1+1 ; next page pair
|
||||
inc SCRATCH_ZPB1+1 ; next page pair
|
||||
dex
|
||||
bne _fullpage
|
||||
|
||||
_lastpage ldx SCRATCH_ZPWORD2
|
||||
beq _done
|
||||
|
||||
ldy #0
|
||||
-
|
||||
_mod2 lda #0 ; self-modified
|
||||
sta (SCRATCH_ZPWORD1), y
|
||||
inc SCRATCH_ZPWORD1
|
||||
bne _mod2b
|
||||
inc SCRATCH_ZPWORD1+1
|
||||
_mod2b lda #0 ; self-modified
|
||||
sta (SCRATCH_ZPWORD1), y
|
||||
inc SCRATCH_ZPWORD1
|
||||
bne +
|
||||
inc SCRATCH_ZPWORD1+1
|
||||
+ dex
|
||||
bne -
|
||||
_done rts
|
||||
|
||||
|
||||
; increments/decrements a byte referenced by indirect register pair by 1
|
||||
; clobbers A/Y, Carry flag determines incr or decr
|
||||
incrdecr_deref_byte_reg_AX
|
||||
sta SCRATCH_ZPWORD1
|
||||
stx SCRATCH_ZPWORD1+1
|
||||
bcc incr_deref_byte
|
||||
bcs decr_deref_byte
|
||||
incrdecr_deref_byte_reg_AY
|
||||
sta SCRATCH_ZPWORD1
|
||||
sty SCRATCH_ZPWORD1+1
|
||||
bcc incr_deref_byte
|
||||
bcs decr_deref_byte
|
||||
incrdecr_deref_byte_reg_XY
|
||||
stx SCRATCH_ZPWORD1
|
||||
sty SCRATCH_ZPWORD1+1
|
||||
bcs decr_deref_byte
|
||||
|
||||
incr_deref_byte
|
||||
ldy #0
|
||||
lda (SCRATCH_ZPWORD1), y
|
||||
adc #1 ; carry's cleared already
|
||||
sta (SCRATCH_ZPWORD1), y
|
||||
rts
|
||||
decr_deref_byte
|
||||
ldy #0
|
||||
lda (SCRATCH_ZPWORD1), y
|
||||
sbc #1 ; carry's set already
|
||||
sta (SCRATCH_ZPWORD1), y
|
||||
rts
|
||||
|
||||
; increments/decrements a word referenced by indirect register pair by 1
|
||||
; clobbers A/Y, Carry flag determines incr or decr
|
||||
incrdecr_deref_word_reg_AX
|
||||
sta SCRATCH_ZPWORD1
|
||||
stx SCRATCH_ZPWORD1+1
|
||||
bcc incr_deref_word
|
||||
bcs decr_deref_word
|
||||
incrdecr_deref_word_reg_AY
|
||||
sta SCRATCH_ZPWORD1
|
||||
sty SCRATCH_ZPWORD1+1
|
||||
bcc incr_deref_word
|
||||
bcs decr_deref_word
|
||||
incrdecr_deref_word_reg_XY
|
||||
stx SCRATCH_ZPWORD1
|
||||
sty SCRATCH_ZPWORD1+1
|
||||
bcs decr_deref_word
|
||||
|
||||
incr_deref_word
|
||||
ldy #0
|
||||
lda (SCRATCH_ZPWORD1), y
|
||||
adc #1 ; carry's cleared already
|
||||
sta (SCRATCH_ZPWORD1), y
|
||||
bcc +
|
||||
iny
|
||||
lda (SCRATCH_ZPWORD1), y
|
||||
adc #0 ; carry is set
|
||||
sta (SCRATCH_ZPWORD1), y
|
||||
+ rts
|
||||
|
||||
decr_deref_word
|
||||
ldy #0
|
||||
lda (SCRATCH_ZPWORD1), y
|
||||
bne +
|
||||
pha
|
||||
iny
|
||||
lda (SCRATCH_ZPWORD1), y
|
||||
sbc #1 ; carry's set already
|
||||
sta (SCRATCH_ZPWORD1), y
|
||||
dey
|
||||
pla
|
||||
+ sec
|
||||
sbc #1
|
||||
sta (SCRATCH_ZPWORD1), y
|
||||
rts
|
||||
|
||||
|
||||
; shift bits in A right by X positions
|
||||
lsr_A_by_X
|
||||
cpx #8
|
||||
bcc _shift
|
||||
lda #0 ; x >=8, result always 0
|
||||
rts
|
||||
_shift cpx #0
|
||||
beq +
|
||||
- lsr a
|
||||
dex
|
||||
bne -
|
||||
+ rts
|
||||
|
||||
|
||||
; shift bits in A left by X positions
|
||||
asl_A_by_X
|
||||
cpx #8
|
||||
bcc _shift
|
||||
lda #0 ; x >=8, result always 0
|
||||
rts
|
||||
_shift cpx #0
|
||||
beq +
|
||||
- asl a
|
||||
dex
|
||||
bne -
|
||||
+ rts
|
||||
|
||||
|
||||
}}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
This is the old Python version of an early prototype of the language and compiler.
|
||||
It was able to produce actual running c64 binary programs, but only
|
||||
from a very limited language subset.
|
||||
|
@ -1 +0,0 @@
|
||||
# package
|
@ -1,8 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
from . import main
|
||||
main.main()
|
@ -1 +0,0 @@
|
||||
# package
|
@ -1,75 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
This is the 6502 assembly code generator (directly from the parse tree)
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import attr
|
||||
from typing import Set, Callable
|
||||
from ...plyparse import Scope, AstNode
|
||||
from ...compile import Zeropage
|
||||
|
||||
|
||||
@attr.s(repr=False, cmp=False)
|
||||
class Context:
|
||||
out = attr.ib(type=Callable)
|
||||
stmt = attr.ib(type=AstNode)
|
||||
scope = attr.ib(type=Scope)
|
||||
floats_enabled = attr.ib(type=bool)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def preserving_registers(registers: Set[str], scope: Scope, out: Callable, loads_a_within: bool=False, force_preserve: bool=False):
|
||||
# this sometimes clobbers a ZP scratch register and is therefore NOT safe to use in interrupts
|
||||
# see http://6502.org/tutorials/register_preservation.html
|
||||
if not scope.save_registers and not force_preserve:
|
||||
yield
|
||||
return
|
||||
if registers == {'A'}:
|
||||
out("\t\tpha")
|
||||
yield
|
||||
out("\t\tpla")
|
||||
elif registers:
|
||||
if not loads_a_within:
|
||||
out("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
|
||||
if 'A' in registers:
|
||||
out("\t\tpha")
|
||||
if 'X' in registers:
|
||||
out("\t\ttxa\n\t\tpha")
|
||||
if 'Y' in registers:
|
||||
out("\t\ttya\n\t\tpha")
|
||||
if not loads_a_within:
|
||||
out("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
|
||||
yield
|
||||
if 'X' in registers and 'Y' in registers:
|
||||
if 'A' not in registers:
|
||||
out("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
|
||||
out("\t\tpla\n\t\ttay")
|
||||
out("\t\tpla\n\t\ttax")
|
||||
out("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
|
||||
else:
|
||||
out("\t\tpla\n\t\ttay")
|
||||
out("\t\tpla\n\t\ttax")
|
||||
else:
|
||||
if 'Y' in registers:
|
||||
if 'A' not in registers:
|
||||
out("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
|
||||
out("\t\tpla\n\t\ttay")
|
||||
out("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
|
||||
else:
|
||||
out("\t\tpla\n\t\ttay")
|
||||
if 'X' in registers:
|
||||
if 'A' not in registers:
|
||||
out("\t\tsta ${:02x}".format(Zeropage.SCRATCH_B2))
|
||||
out("\t\tpla\n\t\ttax")
|
||||
out("\t\tlda ${:02x}".format(Zeropage.SCRATCH_B2))
|
||||
else:
|
||||
out("\t\tpla\n\t\ttax")
|
||||
if 'A' in registers:
|
||||
out("\t\tpla")
|
||||
else:
|
||||
yield
|
||||
|
||||
|
@ -1,585 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
This is the code generator for assignment statements.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
from typing import Callable
|
||||
from . import preserving_registers, Context
|
||||
from ..shared import CodeGenerationError, to_hex
|
||||
from ...plyparse import Scope, Assignment, AugAssignment, Register, LiteralValue, SymbolName, VarDef, Dereference
|
||||
from ...datatypes import REGISTER_BYTES, VarType, DataType
|
||||
from ...compile import Zeropage
|
||||
|
||||
|
||||
def generate_assignment(ctx: Context) -> None:
|
||||
assert isinstance(ctx.stmt, Assignment)
|
||||
assert not isinstance(ctx.stmt.right, Assignment), "assignment should have been flattened"
|
||||
ctx.out("\v\t\t\t; " + ctx.stmt.lineref)
|
||||
ctx.out("\v; @todo assignment: {} = {}".format(ctx.stmt.left, ctx.stmt.right))
|
||||
# @todo assignment
|
||||
|
||||
|
||||
def generate_aug_assignment(ctx: Context) -> None:
|
||||
# for instance: value += 33
|
||||
# (note that with += and -=, values 0..255 usually occur as the more efficient incrdecr statements instead)
|
||||
# left: Register, SymbolName, or Dereference. right: Expression/LiteralValue
|
||||
out = ctx.out
|
||||
stmt = ctx.stmt
|
||||
assert isinstance(stmt, AugAssignment)
|
||||
out("\v\t\t\t; " + stmt.lineref)
|
||||
lvalue = stmt.left
|
||||
rvalue = stmt.right
|
||||
if isinstance(lvalue, Register):
|
||||
if isinstance(rvalue, LiteralValue):
|
||||
if type(rvalue.value) is int:
|
||||
assert rvalue.value >= 0, "augassign value can't be < 0"
|
||||
_generate_aug_reg_int(out, lvalue, stmt.operator, rvalue.value, "", ctx.scope)
|
||||
else:
|
||||
raise CodeGenerationError("constant integer literal or variable required for now", rvalue) # XXX
|
||||
elif isinstance(rvalue, SymbolName):
|
||||
symdef = ctx.scope.lookup(rvalue.name)
|
||||
if isinstance(symdef, VarDef):
|
||||
if symdef.vartype == VarType.CONST:
|
||||
if symdef.datatype.isinteger():
|
||||
assert symdef.value.const_value() >= 0, "augassign value can't be <0" # type: ignore
|
||||
_generate_aug_reg_int(out, lvalue, stmt.operator, symdef.value.const_value(), "", ctx.scope) # type: ignore
|
||||
else:
|
||||
raise CodeGenerationError("aug. assignment value must be integer", rvalue)
|
||||
elif symdef.datatype == DataType.BYTE:
|
||||
_generate_aug_reg_int(out, lvalue, stmt.operator, 0, symdef.name, ctx.scope)
|
||||
else:
|
||||
raise CodeGenerationError("variable must be of type byte for now", rvalue) # XXX
|
||||
else:
|
||||
raise CodeGenerationError("can only use variable name as symbol for aug assign rvalue", rvalue)
|
||||
elif isinstance(rvalue, Register):
|
||||
if lvalue.datatype == DataType.BYTE and rvalue.datatype == DataType.WORD:
|
||||
raise CodeGenerationError("cannot assign a combined 16-bit register to a single 8-bit register", rvalue)
|
||||
_generate_aug_reg_reg(out, lvalue, stmt.operator, rvalue, ctx.scope)
|
||||
elif isinstance(rvalue, Dereference):
|
||||
print("warning: {}: using indirect/dereferece is very costly".format(rvalue.sourceref))
|
||||
if rvalue.datatype != DataType.BYTE:
|
||||
raise CodeGenerationError("aug. assignment value must be a byte for now", rvalue)
|
||||
if isinstance(rvalue.operand, (LiteralValue, SymbolName)):
|
||||
if isinstance(rvalue.operand, LiteralValue):
|
||||
what = to_hex(rvalue.operand.value)
|
||||
else:
|
||||
symdef = rvalue.my_scope().lookup(rvalue.operand.name)
|
||||
if isinstance(symdef, VarDef) and symdef.vartype == VarType.MEMORY:
|
||||
what = to_hex(symdef.value.value) # type: ignore
|
||||
else:
|
||||
what = rvalue.operand.name
|
||||
out("\vpha\n\vtya\n\vpha") # save A, Y on stack
|
||||
out("\vlda " + what)
|
||||
out("\vsta il65_lib.SCRATCH_ZPWORD1")
|
||||
out("\vlda {:s}+1".format(what))
|
||||
out("\vsta il65_lib.SCRATCH_ZPWORD1+1")
|
||||
out("\vldy #0")
|
||||
out("\vlda (il65_lib.SCRATCH_ZPWORD1), y")
|
||||
a_reg = Register(name="A", sourceref=stmt.sourceref) # type: ignore
|
||||
if 'A' in lvalue.name:
|
||||
raise CodeGenerationError("can't yet use register A in this aug assign lhs", lvalue.sourceref) # @todo
|
||||
_generate_aug_reg_reg(out, lvalue, stmt.operator, a_reg, ctx.scope)
|
||||
if lvalue.name in REGISTER_BYTES:
|
||||
out("\vst{:s} il65_lib.SCRATCH_ZP1".format(lvalue.name.lower()))
|
||||
else:
|
||||
out("\vst{:s} il65_lib.SCRATCH_ZP1".format(lvalue.name[0].lower()))
|
||||
out("\vst{:s} il65_lib.SCRATCH_ZP2".format(lvalue.name[1].lower()))
|
||||
out("\vpla\n\vtay\n\vpla") # restore A, Y from stack
|
||||
if lvalue.name in REGISTER_BYTES:
|
||||
out("\vld{:s} il65_lib.SCRATCH_ZP1".format(lvalue.name.lower()))
|
||||
else:
|
||||
out("\vld{:s} il65_lib.SCRATCH_ZP2".format(lvalue.name[0].lower()))
|
||||
out("\vld{:s} il65_lib.SCRATCH_ZP2".format(lvalue.name[1].lower()))
|
||||
elif isinstance(rvalue.operand, Register):
|
||||
assert rvalue.operand.datatype == DataType.WORD
|
||||
reg = rvalue.operand.name
|
||||
out("\vst{:s} il65_lib.SCRATCH_ZPWORD1".format(reg[0].lower()))
|
||||
out("\vst{:s} il65_lib.SCRATCH_ZPWORD1+1".format(reg[1].lower()))
|
||||
out("\vpha\n\vtya\n\vpha") # save A, Y on stack
|
||||
out("\vldy #0")
|
||||
out("\vlda (il65_lib.SCRATCH_ZPWORD1), y")
|
||||
a_reg = Register(name="A", sourceref=stmt.sourceref) # type: ignore
|
||||
if 'A' in lvalue.name:
|
||||
raise CodeGenerationError("can't yet use register A in this aug assign lhs", lvalue.sourceref) # @todo
|
||||
_generate_aug_reg_reg(out, lvalue, stmt.operator, a_reg, ctx.scope)
|
||||
if lvalue.name != 'X':
|
||||
if lvalue.name in REGISTER_BYTES:
|
||||
out("\vst{:s} il65_lib.SCRATCH_ZP1".format(lvalue.name.lower()))
|
||||
else:
|
||||
out("\vst{:s} il65_lib.SCRATCH_ZP1".format(lvalue.name[0].lower()))
|
||||
out("\vst{:s} il65_lib.SCRATCH_ZP2".format(lvalue.name[1].lower()))
|
||||
out("\vpla\n\vtay\n\vpla") # restore A, Y from stack
|
||||
if lvalue.name != 'X':
|
||||
if lvalue.name in REGISTER_BYTES:
|
||||
out("\vld{:s} il65_lib.SCRATCH_ZP1".format(lvalue.name.lower()))
|
||||
else:
|
||||
out("\vld{:s} il65_lib.SCRATCH_ZP1".format(lvalue.name[0].lower()))
|
||||
out("\vld{:s} il65_lib.SCRATCH_ZP2".format(lvalue.name[1].lower()))
|
||||
else:
|
||||
raise CodeGenerationError("invalid dereference operand type", rvalue)
|
||||
else:
|
||||
raise CodeGenerationError("invalid rvalue type", rvalue)
|
||||
elif isinstance(lvalue, SymbolName):
|
||||
raise NotImplementedError("symbolname augassign", lvalue) # XXX
|
||||
else:
|
||||
raise CodeGenerationError("aug. assignment only implemented for registers and symbols for now", lvalue, stmt.sourceref) # XXX
|
||||
|
||||
|
||||
def _generate_aug_reg_int(out: Callable, lvalue: Register, operator: str, rvalue: int, rname: str, scope: Scope) -> None:
|
||||
if rname:
|
||||
right_str = rname
|
||||
else:
|
||||
# an immediate value is provided in rvalue
|
||||
right_str = "#" + str(rvalue)
|
||||
if operator == "+=":
|
||||
assert 0 <= rvalue <= 255, "+= value should be 0..255 for now at " + str(lvalue.sourceref) # @todo
|
||||
if rvalue > 0:
|
||||
_gen_aug_plus_reg_int(lvalue, out, right_str, scope)
|
||||
elif operator == "-=":
|
||||
assert 0 <= rvalue <= 255, "-= value should be 0..255 for now at " + str(lvalue.sourceref) # @todo
|
||||
if rvalue > 0:
|
||||
_gen_aug_minus_reg_int(lvalue, out, right_str, scope)
|
||||
elif operator == "&=":
|
||||
assert 0 <= rvalue <= 255, "&= value should be 0..255 for now at " + str(lvalue.sourceref) # @todo
|
||||
if rvalue == 0:
|
||||
# output '=0'
|
||||
assignment = Assignment(sourceref=lvalue.sourceref) # type: ignore
|
||||
assignment.nodes.append(lvalue)
|
||||
assignment.nodes.append(LiteralValue(value=0, sourceref=lvalue.sourceref)) # type: ignore
|
||||
ctx = Context(out=out, stmt=assignment, scope=scope, floats_enabled=False) # type: ignore
|
||||
generate_assignment(ctx)
|
||||
else:
|
||||
_gen_aug_and_reg_int(lvalue, out, right_str, scope)
|
||||
elif operator == "|=":
|
||||
assert 0 <= rvalue <= 255, "|= value should be 0..255 for now at " + str(lvalue.sourceref) # @todo
|
||||
if rvalue > 0:
|
||||
_gen_aug_or_reg_int(lvalue, out, right_str, scope)
|
||||
elif operator == "^=":
|
||||
assert 0 <= rvalue <= 255, "^= value should be 0..255 for now at " + str(lvalue.sourceref) # @todo
|
||||
if rvalue > 0:
|
||||
_gen_aug_xor_reg_int(lvalue, out, right_str, scope)
|
||||
elif operator == ">>=":
|
||||
_gen_aug_shiftright_reg_int(lvalue, out, rname, rvalue, scope)
|
||||
elif operator == "<<=":
|
||||
_gen_aug_shiftleft_reg_int(lvalue, out, rname, rvalue, scope)
|
||||
else:
|
||||
raise ValueError("invalid operator: " + operator, str(lvalue.sourceref)) # @todo implement more operators such as *=, /=
|
||||
|
||||
|
||||
def _generate_aug_reg_reg(out: Callable, lvalue: Register, operator: str, rvalue: Register, scope: Scope) -> None:
|
||||
if operator == "+=":
|
||||
_gen_aug_plus_reg_reg(lvalue, out, rvalue, scope)
|
||||
elif operator == "-=":
|
||||
_gen_aug_minus_reg_reg(lvalue, out, rvalue, scope)
|
||||
elif operator == "&=":
|
||||
_gen_aug_and_reg_reg(lvalue, out, rvalue, scope)
|
||||
elif operator == "|=":
|
||||
_gen_aug_or_reg_reg(lvalue, out, rvalue, scope)
|
||||
elif operator == "^=":
|
||||
_gen_aug_xor_reg_reg(lvalue, out, rvalue, scope)
|
||||
elif operator == ">>=":
|
||||
_gen_aug_shiftright_reg_reg(lvalue, out, rvalue, scope)
|
||||
elif operator == "<<=":
|
||||
_gen_aug_shiftleft_reg_reg(lvalue, out, rvalue, scope)
|
||||
else:
|
||||
raise ValueError("invalid operator: " + operator, str(lvalue.sourceref)) # @todo implement more operators such as *=, /=
|
||||
|
||||
|
||||
def _gen_aug_shiftleft_reg_int(lvalue: Register, out: Callable, rname: str, rvalue: int, scope: Scope) -> None:
|
||||
if rname:
|
||||
assert lvalue.name in REGISTER_BYTES, "only single registers for now" # @todo <<=.word
|
||||
if lvalue.name == "A":
|
||||
preserve_regs = {'X'}
|
||||
elif lvalue.name == "X":
|
||||
preserve_regs = {'A'}
|
||||
out("\vtxa")
|
||||
elif lvalue.name == "Y":
|
||||
preserve_regs = {'A', 'X'}
|
||||
out("\vtya")
|
||||
with preserving_registers(preserve_regs, scope, out):
|
||||
out("\vldx " + rname)
|
||||
out("\vjsr il65_lib.asl_A_by_X")
|
||||
# put A back into target register
|
||||
if lvalue.name == "X":
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
out("\vtay")
|
||||
else:
|
||||
def shifts_A(times: int) -> None:
|
||||
if times >= 8:
|
||||
out("\vlda #0")
|
||||
else:
|
||||
for _ in range(min(8, times)):
|
||||
out("\vasl a")
|
||||
|
||||
if lvalue.name == "A":
|
||||
shifts_A(rvalue)
|
||||
elif lvalue.name == "X":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
shifts_A(rvalue)
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtya")
|
||||
shifts_A(rvalue)
|
||||
out("\vtay")
|
||||
else:
|
||||
raise CodeGenerationError("unsupported register for aug assign", str(lvalue)) # @todo <<=.word
|
||||
|
||||
|
||||
def _gen_aug_shiftright_reg_int(lvalue: Register, out: Callable, rname: str, rvalue: int, scope: Scope) -> None:
|
||||
if rname:
|
||||
assert lvalue.name in REGISTER_BYTES, "only single registers for now" # @todo >>=.word
|
||||
if lvalue.name == "A":
|
||||
preserve_regs = {'X'}
|
||||
elif lvalue.name == "X":
|
||||
preserve_regs = {'A'}
|
||||
out("\vtxa")
|
||||
elif lvalue.name == "Y":
|
||||
preserve_regs = {'A', 'X'}
|
||||
out("\vtya")
|
||||
with preserving_registers(preserve_regs, scope, out):
|
||||
out("\vldx " + rname)
|
||||
out("\vjsr il65_lib.lsr_A_by_X")
|
||||
# put A back into target register
|
||||
if lvalue.name == "X":
|
||||
out("\vtax")
|
||||
if lvalue.name == "Y":
|
||||
out("\vtay")
|
||||
else:
|
||||
def shifts_A(times: int) -> None:
|
||||
if times >= 8:
|
||||
out("\vlda #0")
|
||||
else:
|
||||
for _ in range(min(8, times)):
|
||||
out("\vlsr a")
|
||||
|
||||
if lvalue.name == "A":
|
||||
shifts_A(rvalue)
|
||||
elif lvalue.name == "X":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
shifts_A(rvalue)
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtya")
|
||||
shifts_A(rvalue)
|
||||
out("\vtay")
|
||||
else:
|
||||
raise CodeGenerationError("unsupported register for aug assign", str(lvalue)) # @todo >>=.word
|
||||
|
||||
|
||||
def _gen_aug_xor_reg_int(lvalue: Register, out: Callable, right_str: str, scope: Scope) -> None:
|
||||
if lvalue.name == "A":
|
||||
out("\veor " + right_str)
|
||||
elif lvalue.name == "X":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\veor " + right_str)
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtya")
|
||||
out("\veor " + right_str)
|
||||
out("\vtay")
|
||||
else:
|
||||
raise CodeGenerationError("unsupported register for aug assign", str(lvalue)) # @todo ^=.word
|
||||
|
||||
|
||||
def _gen_aug_or_reg_int(lvalue: Register, out: Callable, right_str: str, scope: Scope) -> None:
|
||||
if lvalue.name == "A":
|
||||
out("\vora " + right_str)
|
||||
elif lvalue.name == "X":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vora " + right_str)
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtya")
|
||||
out("\vora " + right_str)
|
||||
out("\vtay")
|
||||
else:
|
||||
raise CodeGenerationError("unsupported register for aug assign", str(lvalue)) # @todo |=.word
|
||||
|
||||
|
||||
def _gen_aug_and_reg_int(lvalue: Register, out: Callable, right_str: str, scope: Scope) -> None:
|
||||
if lvalue.name == "A":
|
||||
out("\vand " + right_str)
|
||||
elif lvalue.name == "X":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vand " + right_str)
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtya")
|
||||
out("\vand " + right_str)
|
||||
out("\vtay")
|
||||
else:
|
||||
raise CodeGenerationError("unsupported register for aug assign", str(lvalue)) # @todo &=.word
|
||||
|
||||
|
||||
def _gen_aug_minus_reg_int(lvalue: Register, out: Callable, right_str: str, scope: Scope) -> None:
|
||||
if lvalue.name == "A":
|
||||
out("\vsec")
|
||||
out("\vsbc " + right_str)
|
||||
elif lvalue.name == "X":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vsec")
|
||||
out("\vsbc " + right_str)
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtya")
|
||||
out("\vsec")
|
||||
out("\vsbc " + right_str)
|
||||
out("\vtay")
|
||||
elif lvalue.name == "AX":
|
||||
out("\vsec")
|
||||
out("\vsbc " + right_str)
|
||||
out("\vbcs +")
|
||||
out("\vdex")
|
||||
out("+")
|
||||
elif lvalue.name == "AY":
|
||||
out("\vsec")
|
||||
out("\vsbc " + right_str)
|
||||
out("\vbcs +")
|
||||
out("\vdey")
|
||||
out("+")
|
||||
elif lvalue.name == "XY":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vsec")
|
||||
out("\vsbc " + right_str)
|
||||
out("\vtax")
|
||||
out("\vbcs +")
|
||||
out("\vdey")
|
||||
out("+")
|
||||
else:
|
||||
raise ValueError("invalid register", str(lvalue))
|
||||
|
||||
|
||||
def _gen_aug_plus_reg_int(lvalue: Register, out: Callable, right_str: str, scope: Scope) -> None:
|
||||
if lvalue.name == "A":
|
||||
out("\vclc")
|
||||
out("\vadc " + right_str)
|
||||
elif lvalue.name == "X":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vclc")
|
||||
out("\vadc " + right_str)
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtya")
|
||||
out("\vclc")
|
||||
out("\vadc " + right_str)
|
||||
out("\vtay")
|
||||
elif lvalue.name == "AX":
|
||||
out("\vclc")
|
||||
out("\vadc " + right_str)
|
||||
out("\vbcc +")
|
||||
out("\vinx")
|
||||
out("+")
|
||||
elif lvalue.name == "AY":
|
||||
out("\vclc")
|
||||
out("\vadc " + right_str)
|
||||
out("\vbcc +")
|
||||
out("\viny")
|
||||
out("+")
|
||||
elif lvalue.name == "XY":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vclc")
|
||||
out("\vadc " + right_str)
|
||||
out("\vtax")
|
||||
out("\vbcc +")
|
||||
out("\viny")
|
||||
out("+")
|
||||
else:
|
||||
raise ValueError("invalid register", str(lvalue))
|
||||
|
||||
|
||||
def _gen_aug_shiftleft_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
|
||||
if rvalue.name not in REGISTER_BYTES:
|
||||
raise CodeGenerationError("unsupported rvalue register for aug assign", str(rvalue)) # @todo <<=.word
|
||||
if lvalue.name == "A":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
out("\vasl " + to_hex(Zeropage.SCRATCH_B1))
|
||||
elif lvalue.name == "X":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vasl " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtya")
|
||||
out("\vasl " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtay")
|
||||
else:
|
||||
raise CodeGenerationError("unsupported lvalue register for aug assign", str(lvalue)) # @todo <<=.word
|
||||
|
||||
|
||||
def _gen_aug_shiftright_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
|
||||
if rvalue.name not in REGISTER_BYTES:
|
||||
raise CodeGenerationError("unsupported rvalue register for aug assign", str(rvalue)) # @todo >>=.word
|
||||
if lvalue.name == "A":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
out("\vlsr " + to_hex(Zeropage.SCRATCH_B1))
|
||||
elif lvalue.name == "X":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vlsr " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtya")
|
||||
out("\vlsr " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtay")
|
||||
else:
|
||||
raise CodeGenerationError("unsupported lvalue register for aug assign", str(lvalue)) # @todo >>=.word
|
||||
|
||||
|
||||
def _gen_aug_xor_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
|
||||
if rvalue.name not in REGISTER_BYTES:
|
||||
raise CodeGenerationError("unsupported rvalue register for aug assign", str(rvalue)) # @todo ^=.word
|
||||
if lvalue.name == "A":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
out("\veor " + to_hex(Zeropage.SCRATCH_B1))
|
||||
elif lvalue.name == "X":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\veor " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtya")
|
||||
out("\veor " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtay")
|
||||
else:
|
||||
raise CodeGenerationError("unsupported lvalue register for aug assign", str(lvalue)) # @todo ^=.word
|
||||
|
||||
|
||||
def _gen_aug_or_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
|
||||
if rvalue.name not in REGISTER_BYTES:
|
||||
raise CodeGenerationError("unsupported rvalue register for aug assign", str(rvalue)) # @todo |=.word
|
||||
if lvalue.name == "A":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
out("\vora " + to_hex(Zeropage.SCRATCH_B1))
|
||||
elif lvalue.name == "X":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vora " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtya")
|
||||
out("\vora " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtay")
|
||||
else:
|
||||
raise CodeGenerationError("unsupported lvalue register for aug assign", str(lvalue)) # @todo |=.word
|
||||
|
||||
|
||||
def _gen_aug_and_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
|
||||
if rvalue.name not in REGISTER_BYTES:
|
||||
raise CodeGenerationError("unsupported rvalue register for aug assign", str(rvalue)) # @todo &=.word
|
||||
if lvalue.name == "A":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
out("\vand " + to_hex(Zeropage.SCRATCH_B1))
|
||||
elif lvalue.name == "X":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vand " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtya")
|
||||
out("\vand " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtay")
|
||||
else:
|
||||
raise CodeGenerationError("unsupported lvalue register for aug assign", str(lvalue)) # @todo &=.word
|
||||
|
||||
|
||||
def _gen_aug_minus_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
|
||||
if rvalue.name not in REGISTER_BYTES:
|
||||
raise CodeGenerationError("unsupported rvalue register for aug assign", str(rvalue)) # @todo -=.word
|
||||
if lvalue.name == "A":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
out("\vsec")
|
||||
out("\vsbc " + to_hex(Zeropage.SCRATCH_B1))
|
||||
elif lvalue.name == "X":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vsec")
|
||||
out("\vsbc " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtya")
|
||||
out("\vsec")
|
||||
out("\vsbc " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtay")
|
||||
else:
|
||||
raise CodeGenerationError("unsupported lvalue register for aug assign", str(lvalue)) # @todo -=.word
|
||||
|
||||
|
||||
def _gen_aug_plus_reg_reg(lvalue: Register, out: Callable, rvalue: Register, scope: Scope) -> None:
|
||||
if rvalue.name not in REGISTER_BYTES:
|
||||
raise CodeGenerationError("unsupported rvalue register for aug assign", str(rvalue)) # @todo +=.word
|
||||
out("\vst{:s} {:s}".format(rvalue.name.lower(), to_hex(Zeropage.SCRATCH_B1)))
|
||||
if lvalue.name == "A":
|
||||
out("\vclc")
|
||||
out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
|
||||
elif lvalue.name == "X":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vclc")
|
||||
out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtax")
|
||||
elif lvalue.name == "Y":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtya")
|
||||
out("\vclc")
|
||||
out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtay")
|
||||
elif lvalue.name == "AX":
|
||||
out("\vclc")
|
||||
out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vbcc +")
|
||||
out("\vinx")
|
||||
out("+")
|
||||
elif lvalue.name == "AY":
|
||||
out("\vclc")
|
||||
out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vbcc +")
|
||||
out("\viny")
|
||||
out("+")
|
||||
elif lvalue.name == "XY":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vclc")
|
||||
out("\vadc " + to_hex(Zeropage.SCRATCH_B1))
|
||||
out("\vtax")
|
||||
out("\vbcc +")
|
||||
out("\viny")
|
||||
out("+")
|
||||
else:
|
||||
raise ValueError("invalid register", str(lvalue))
|
@ -1,150 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
This is the code generator for gotos and subroutine calls.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
from . import Context
|
||||
from ..shared import CodeGenerationError, to_hex
|
||||
from ...plyparse import Goto, SubCall, LiteralValue, SymbolName, Dereference
|
||||
|
||||
|
||||
def generate_goto(ctx: Context) -> None:
|
||||
stmt = ctx.stmt
|
||||
assert isinstance(stmt, Goto)
|
||||
ctx.out("\v\t\t\t; " + ctx.stmt.lineref)
|
||||
if stmt.condition:
|
||||
if stmt.if_stmt:
|
||||
_gen_goto_cond(ctx, stmt, "true")
|
||||
else:
|
||||
_gen_goto_cond(ctx, stmt, stmt.if_cond)
|
||||
else:
|
||||
if stmt.if_stmt:
|
||||
_gen_goto_special_if(ctx, stmt)
|
||||
else:
|
||||
_gen_goto_unconditional(ctx, stmt)
|
||||
|
||||
|
||||
def _gen_goto_special_if(ctx: Context, stmt: Goto) -> None:
|
||||
# a special if, but no conditional expression
|
||||
if isinstance(stmt.target, Dereference):
|
||||
# dereference is symbol, literal, or register (pair)
|
||||
if isinstance(stmt.target.operand, LiteralValue):
|
||||
targetstr = to_hex(stmt.target.operand.value)
|
||||
elif isinstance(stmt.target.operand, SymbolName):
|
||||
targetstr = stmt.target.operand.name
|
||||
else:
|
||||
# register pair
|
||||
ctx.out("\vst{:s} il65_lib.SCRATCH_ZPWORD1".format(stmt.target.operand.name[0]))
|
||||
ctx.out("\vst{:s} il65_lib.SCRATCH_ZPWORD1+1".format(stmt.target.operand.name[1]))
|
||||
targetstr = "il65_lib.SCRATCH_ZPWORD1"
|
||||
if stmt.if_cond == "true":
|
||||
ctx.out("\vbeq +")
|
||||
ctx.out("\vjmp ({:s})".format(targetstr))
|
||||
ctx.out("+")
|
||||
elif stmt.if_cond in ("not", "zero"):
|
||||
ctx.out("\vbne +")
|
||||
ctx.out("\vjmp ({:s})".format(targetstr))
|
||||
ctx.out("+")
|
||||
elif stmt.if_cond in ("cc", "cs", "vc", "vs", "eq", "ne"):
|
||||
if stmt.if_cond == "cc":
|
||||
ctx.out("\vbcs +")
|
||||
elif stmt.if_cond == "cs":
|
||||
ctx.out("\vbcc +")
|
||||
elif stmt.if_cond == "vc":
|
||||
ctx.out("\vbvs +")
|
||||
elif stmt.if_cond == "vs":
|
||||
ctx.out("\vbvc +")
|
||||
elif stmt.if_cond == "eq":
|
||||
ctx.out("\vbne +")
|
||||
elif stmt.if_cond == "ne":
|
||||
ctx.out("\vbeq +")
|
||||
ctx.out("\vjmp ({:s})".format(targetstr))
|
||||
ctx.out("+")
|
||||
elif stmt.if_cond == "lt":
|
||||
ctx.out("\vbcs +")
|
||||
ctx.out("\vjmp ({:s})".format(targetstr))
|
||||
ctx.out("+")
|
||||
elif stmt.if_cond == "gt":
|
||||
ctx.out("\vbcc +")
|
||||
ctx.out("\vbeq +")
|
||||
ctx.out("\vjmp ({:s})".format(targetstr))
|
||||
ctx.out("+")
|
||||
elif stmt.if_cond == "ge":
|
||||
ctx.out("\vbcc +")
|
||||
ctx.out("\vjmp ({:s})".format(targetstr))
|
||||
ctx.out("+")
|
||||
elif stmt.if_cond == "le":
|
||||
ctx.out("\vbeq +")
|
||||
ctx.out("\vbcs ++")
|
||||
ctx.out("+\t\tjmp ({:s})".format(targetstr))
|
||||
ctx.out("+")
|
||||
else:
|
||||
raise CodeGenerationError("invalid if status " + stmt.if_cond)
|
||||
else:
|
||||
if isinstance(stmt.target, LiteralValue) and type(stmt.target.value) is int:
|
||||
targetstr = to_hex(stmt.target.value)
|
||||
elif isinstance(stmt.target, SymbolName):
|
||||
targetstr = stmt.target.name
|
||||
else:
|
||||
raise CodeGenerationError("invalid goto target type", stmt)
|
||||
if stmt.if_cond == "true":
|
||||
ctx.out("\vbne " + targetstr)
|
||||
elif stmt.if_cond in ("not", "zero"):
|
||||
ctx.out("\vbeq " + targetstr)
|
||||
elif stmt.if_cond in ("cc", "cs", "vc", "vs", "eq", "ne"):
|
||||
ctx.out("\vb{:s} {:s}".format(stmt.if_cond, targetstr))
|
||||
elif stmt.if_cond == "pos":
|
||||
ctx.out("\vbpl " + targetstr)
|
||||
elif stmt.if_cond == "neg":
|
||||
ctx.out("\vbmi " + targetstr)
|
||||
elif stmt.if_cond == "lt":
|
||||
ctx.out("\vbcc " + targetstr)
|
||||
elif stmt.if_cond == "gt":
|
||||
ctx.out("\vbeq +")
|
||||
ctx.out("\vbcs " + targetstr)
|
||||
ctx.out("+")
|
||||
elif stmt.if_cond == "ge":
|
||||
ctx.out("\vbcs " + targetstr)
|
||||
elif stmt.if_cond == "le":
|
||||
ctx.out("\vbcc " + targetstr)
|
||||
ctx.out("\vbeq " + targetstr)
|
||||
else:
|
||||
raise CodeGenerationError("invalid if status " + stmt.if_cond)
|
||||
|
||||
|
||||
def _gen_goto_unconditional(ctx: Context, stmt: Goto) -> None:
|
||||
# unconditional jump to <target>
|
||||
if isinstance(stmt.target, LiteralValue) and type(stmt.target.value) is int:
|
||||
ctx.out("\vjmp " + to_hex(stmt.target.value))
|
||||
elif isinstance(stmt.target, SymbolName):
|
||||
ctx.out("\vjmp " + stmt.target.name)
|
||||
elif isinstance(stmt.target, Dereference):
|
||||
# dereference is symbol, literal, or register (pair)
|
||||
if isinstance(stmt.target.operand, LiteralValue):
|
||||
ctx.out("\vjmp ({:s})".format(to_hex(stmt.target.operand.value)))
|
||||
elif isinstance(stmt.target.operand, SymbolName):
|
||||
ctx.out("\vjmp ({:s})".format(stmt.target.operand.name))
|
||||
else:
|
||||
# register pair
|
||||
ctx.out("\vst{:s} il65_lib.SCRATCH_ZPWORD1".format(stmt.target.operand.name[0]))
|
||||
ctx.out("\vst{:s} il65_lib.SCRATCH_ZPWORD1+1".format(stmt.target.operand.name[1]))
|
||||
ctx.out("\vjmp (il65_lib.SCRATCH_ZPWORD1)")
|
||||
else:
|
||||
raise CodeGenerationError("invalid goto target type", stmt)
|
||||
|
||||
|
||||
def _gen_goto_cond(ctx: Context, stmt: Goto, if_cond: str) -> None:
|
||||
if isinstance(stmt.condition, LiteralValue):
|
||||
pass # @todo if WITH conditional expression
|
||||
else:
|
||||
raise CodeGenerationError("no support for evaluating conditional expression yet", stmt) # @todo
|
||||
|
||||
|
||||
def generate_subcall(ctx: Context) -> None:
|
||||
stmt = ctx.stmt
|
||||
assert isinstance(stmt, SubCall)
|
||||
ctx.out("\v\t\t\t; " + ctx.stmt.lineref)
|
||||
ctx.out("\v; @todo sub call: {}".format(ctx.stmt.target))
|
||||
# @todo subcall
|
@ -1,221 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
This is the assembly code generator (from the parse tree)
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
import os
|
||||
import datetime
|
||||
from typing import TextIO, Callable, no_type_check
|
||||
from . import Context
|
||||
from .variables import generate_block_init, generate_block_vars
|
||||
from .assignment import generate_assignment, generate_aug_assignment
|
||||
from .calls import generate_goto, generate_subcall
|
||||
from .incrdecr import generate_incrdecr
|
||||
from ..shared import CodeGenerationError, to_hex, to_mflpt5, sanitycheck
|
||||
from ...plyparse import (Module, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, ZpOptions,
|
||||
InlineAssembly, Return, Register, Goto, SubCall, Assignment, AugAssignment, IncrDecr)
|
||||
|
||||
|
||||
class Output:
|
||||
def __init__(self, stream: TextIO) -> None:
|
||||
self.stream = stream
|
||||
|
||||
def __call__(self, text, *args, **vargs):
|
||||
# replace '\v' (vertical tab) char by the actual line indent (2 tabs) and write to the stringIo
|
||||
print(text.replace("\v", "\t\t"), *args, file=self.stream, **vargs)
|
||||
|
||||
|
||||
class AssemblyGenerator:
|
||||
def __init__(self, module: Module, enable_floats: bool) -> None:
|
||||
self.module = module
|
||||
self.floats_enabled = enable_floats
|
||||
self.cur_block = None
|
||||
self.output = None # type: Output
|
||||
|
||||
def generate(self, filename: str) -> None:
|
||||
with open(filename, "wt") as stream:
|
||||
output = Output(stream)
|
||||
try:
|
||||
self._generate(output)
|
||||
except Exception as x:
|
||||
output(".error \"****** ABORTED DUE TO ERROR:", x, "\"\n")
|
||||
raise
|
||||
|
||||
def _generate(self, out: Callable) -> None:
|
||||
sanitycheck(self.module)
|
||||
self.header(out)
|
||||
self.blocks(out)
|
||||
out("\t.end")
|
||||
|
||||
def header(self, out: Callable) -> None:
|
||||
out("; 6502 code generated by il65.py - codename 'Sick'")
|
||||
out("; source file:", self.module.sourceref.file)
|
||||
out("; compiled on:", datetime.datetime.now())
|
||||
out("; output options:", self.module.format, self.module.zp_options)
|
||||
out("; assembler syntax is for the 64tasm cross-assembler")
|
||||
out("\n.cpu '6502'\n.enc 'none'\n")
|
||||
assert self.module.address is not None
|
||||
if self.module.format in (ProgramFormat.PRG, ProgramFormat.BASIC):
|
||||
if self.module.format == ProgramFormat.BASIC:
|
||||
if self.module.address != 0x0801:
|
||||
raise CodeGenerationError("BASIC output mode must have load address $0801")
|
||||
out("; ---- basic program with sys call ----")
|
||||
out("* = " + to_hex(self.module.address))
|
||||
year = datetime.datetime.now().year
|
||||
out("\v.word (+), {:d}".format(year))
|
||||
out("\v.null $9e, format(' %d ', _il65_entrypoint), $3a, $8f, ' il65 by idj'")
|
||||
out("+\v.word 0")
|
||||
out("_il65_entrypoint\v; assembly code starts here\n")
|
||||
else:
|
||||
out("; ---- program without sys call ----")
|
||||
out("* = " + to_hex(self.module.address) + "\n")
|
||||
elif self.module.format == ProgramFormat.RAW:
|
||||
out("; ---- raw assembler program ----")
|
||||
out("* = " + to_hex(self.module.address) + "\n")
|
||||
# call the block init methods and jump to the user's main.start entrypoint
|
||||
if self.module.zp_options == ZpOptions.CLOBBER_RESTORE:
|
||||
out("\vjsr _il65_save_zeropage")
|
||||
out("\v; initialize all blocks (reset vars)")
|
||||
if self.module.zeropage():
|
||||
out("\vjsr ZP._il65_init_block")
|
||||
for block in self.module.nodes:
|
||||
if isinstance(block, Block) and block.name != "ZP":
|
||||
out("\vjsr {}._il65_init_block".format(block.name))
|
||||
out("\v; call user code")
|
||||
if self.module.zp_options == ZpOptions.CLOBBER_RESTORE:
|
||||
out("\vjsr {:s}.start".format(self.module.main().label))
|
||||
out("\vcld")
|
||||
out("\vjmp _il65_restore_zeropage\n")
|
||||
# include the assembly code for the save/restore zeropage routines
|
||||
zprestorefile = os.path.join(os.path.split(__file__)[0], "../lib", "restorezp.asm")
|
||||
with open(zprestorefile, "rU") as f:
|
||||
for line in f.readlines():
|
||||
out(line.rstrip("\n"))
|
||||
else:
|
||||
out("\vjmp {:s}.start".format(self.module.main().label))
|
||||
out("")
|
||||
|
||||
@no_type_check
|
||||
def blocks(self, out: Callable) -> None:
|
||||
zpblock = self.module.zeropage()
|
||||
if zpblock:
|
||||
# if there's a Zeropage block, it always goes first
|
||||
self.cur_block = zpblock # type: ignore
|
||||
out("\n; ---- ZeroPage block: '{:s}' ----".format(zpblock.name))
|
||||
out("; file: '{:s}' src l. {:d}\n".format(zpblock.sourceref.file, zpblock.sourceref.line))
|
||||
out("{:s}\t.proc\n".format(zpblock.label))
|
||||
generate_block_init(out, zpblock)
|
||||
generate_block_vars(out, zpblock, True)
|
||||
# there's no code in the ZeroPage block.
|
||||
out("\v.pend\n")
|
||||
for block in sorted(self.module.all_nodes(Block), key=lambda b: b.address or 0):
|
||||
ctx = Context(out=out, stmt=None, scope=block.scope, floats_enabled=self.floats_enabled)
|
||||
if block.name == "ZP":
|
||||
continue # already processed
|
||||
self.cur_block = block
|
||||
out("\n; ---- block: '{:s}' ----".format(block.name))
|
||||
out("; file: '{:s}' src l. {:d}\n".format(block.sourceref.file, block.sourceref.line))
|
||||
if block.address:
|
||||
out(".cerror * > ${0:04x}, 'block address overlaps by ', *-${0:04x},' bytes'".format(block.address))
|
||||
out("* = ${:04x}".format(block.address))
|
||||
out("{:s}\t.proc\n".format(block.label))
|
||||
generate_block_init(out, block)
|
||||
generate_block_vars(out, block)
|
||||
subroutines = list(sub for sub in block.all_nodes(Subroutine) if sub.address is not None)
|
||||
if subroutines:
|
||||
# these are (external) subroutines that are defined by address instead of a scope/code
|
||||
out("; external subroutines")
|
||||
for subdef in subroutines:
|
||||
assert subdef.scope is None
|
||||
out("\v{:s} = {:s}".format(subdef.name, to_hex(subdef.address)))
|
||||
out("; end external subroutines\n")
|
||||
for stmt in block.scope.nodes:
|
||||
if isinstance(stmt, (VarDef, Subroutine)):
|
||||
continue # should have been handled already or will be later
|
||||
ctx.stmt = stmt
|
||||
self.generate_statement(ctx)
|
||||
if block.name == "main" and isinstance(stmt, Label) and stmt.name == "start":
|
||||
# make sure the main.start routine clears the decimal and carry flags as first steps
|
||||
out("\vcld\n\vclc\n\vclv")
|
||||
subroutines = list(sub for sub in block.all_nodes(Subroutine) if sub.address is None)
|
||||
if subroutines:
|
||||
# these are subroutines that are defined by a scope/code
|
||||
out("; -- block subroutines")
|
||||
for subdef in subroutines:
|
||||
assert subdef.scope is not None
|
||||
out("{:s}\v; src l. {:d}".format(subdef.name, subdef.sourceref.line))
|
||||
params = ", ".join("{:s} -> {:s}".format(name or "<unnamed>", registers) for name, registers in subdef.param_spec)
|
||||
returns = ",".join(sorted(register for register in subdef.result_spec if register[-1] != '?'))
|
||||
clobbers = ",".join(sorted(register for register in subdef.result_spec if register[-1] == '?'))
|
||||
out("\v; params: {}\n\v; returns: {} clobbers: {}".format(params or "-", returns or "-", clobbers or "-"))
|
||||
cur_block = self.cur_block
|
||||
self.cur_block = subdef.scope
|
||||
for ctx.stmt in subdef.scope.nodes:
|
||||
self.generate_statement(ctx)
|
||||
self.cur_block = cur_block
|
||||
out("")
|
||||
out("; -- end block subroutines")
|
||||
if block.scope.float_const_values:
|
||||
if not self.floats_enabled:
|
||||
raise CodeGenerationError("floating point numbers not enabled via option")
|
||||
# generate additional float constants that are used in floating point expressions
|
||||
out("\n; -- float constants")
|
||||
for name, value in block.scope.float_const_values.items():
|
||||
out("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(name, *to_mflpt5(value), value))
|
||||
out("\n\v.pend\n")
|
||||
|
||||
@no_type_check
|
||||
def generate_statement(self, ctx: Context) -> None:
|
||||
stmt = ctx.stmt
|
||||
if isinstance(stmt, Label):
|
||||
ctx.out("\n{:s}\v\t\t; {:s}".format(stmt.name, stmt.lineref))
|
||||
elif isinstance(stmt, Return):
|
||||
if stmt.value_A:
|
||||
reg = Register(name="A", sourceref=stmt.sourceref)
|
||||
assignment = Assignment(sourceref=stmt.sourceref)
|
||||
assignment.nodes.append(reg)
|
||||
assignment.nodes.append(stmt.value_A)
|
||||
assignment.mark_lhs()
|
||||
ctx.stmt = assignment
|
||||
generate_assignment(ctx)
|
||||
if stmt.value_X:
|
||||
reg = Register(name="X", sourceref=stmt.sourceref)
|
||||
assignment = Assignment(sourceref=stmt.sourceref)
|
||||
assignment.nodes.append(reg)
|
||||
assignment.nodes.append(stmt.value_X)
|
||||
assignment.mark_lhs()
|
||||
ctx.stmt = assignment
|
||||
generate_assignment(ctx)
|
||||
if stmt.value_Y:
|
||||
reg = Register(name="Y", sourceref=stmt.sourceref)
|
||||
assignment = Assignment(sourceref=stmt.sourceref)
|
||||
assignment.nodes.append(reg)
|
||||
assignment.nodes.append(stmt.value_Y)
|
||||
assignment.mark_lhs()
|
||||
ctx.stmt = assignment
|
||||
generate_assignment(ctx)
|
||||
ctx.out("\vrts")
|
||||
elif isinstance(stmt, InlineAssembly):
|
||||
ctx.out("\n\v; inline asm, " + stmt.lineref)
|
||||
ctx.out(stmt.assembly)
|
||||
ctx.out("\v; end inline asm, " + stmt.lineref + "\n")
|
||||
elif isinstance(stmt, IncrDecr):
|
||||
generate_incrdecr(ctx)
|
||||
elif isinstance(stmt, Goto):
|
||||
generate_goto(ctx)
|
||||
elif isinstance(stmt, SubCall):
|
||||
generate_subcall(ctx)
|
||||
elif isinstance(stmt, Assignment):
|
||||
generate_assignment(ctx)
|
||||
elif isinstance(stmt, AugAssignment):
|
||||
generate_aug_assignment(ctx)
|
||||
elif isinstance(stmt, Directive):
|
||||
if stmt.name == "breakpoint":
|
||||
# put a marker in the source so that we can generate a list of breakpoints later
|
||||
# this label is later extracted from the label dump file to turn it into a breakpoint instruction
|
||||
ctx.out("_il65_breakpoint_{:d}".format(id(stmt)))
|
||||
# other directives are ignored here
|
||||
else:
|
||||
raise NotImplementedError("statement", stmt)
|
@ -1,267 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
This is the code generator for the in-place incr and decr instructions.
|
||||
Incrementing or decrementing variables by a small byte value 0..255
|
||||
is quite frequent and this generates assembly code tweaked for this case.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
from . import Context, preserving_registers
|
||||
from ..shared import CodeGenerationError, to_hex
|
||||
from ...plyparse import VarDef, Register, IncrDecr, SymbolName, Dereference, LiteralValue, scoped_name
|
||||
from ...datatypes import VarType, DataType, REGISTER_BYTES
|
||||
|
||||
|
||||
def generate_incrdecr(ctx: Context) -> None:
|
||||
out = ctx.out
|
||||
stmt = ctx.stmt
|
||||
scope = ctx.scope
|
||||
assert isinstance(stmt, IncrDecr)
|
||||
if stmt.howmuch == 0:
|
||||
return
|
||||
if not 0 <= stmt.howmuch <= 255:
|
||||
raise CodeGenerationError("incr/decr value must be 0..255 - other values should have been converted into an AugAssignment")
|
||||
target = stmt.target # one of Register/SymbolName/Dereference, or a VarDef
|
||||
if isinstance(target, SymbolName):
|
||||
symdef = scope.lookup(target.name)
|
||||
if isinstance(symdef, VarDef):
|
||||
target = symdef # type: ignore
|
||||
else:
|
||||
raise CodeGenerationError("cannot incr/decr this", symdef)
|
||||
howmuch_str = str(stmt.howmuch)
|
||||
|
||||
if isinstance(target, Register):
|
||||
reg = target.name
|
||||
# note: these operations below are all checked to be ok
|
||||
if stmt.operator == "++":
|
||||
if reg == 'A':
|
||||
# a += 1..255
|
||||
out("\vclc")
|
||||
out("\vadc #" + howmuch_str)
|
||||
elif reg in REGISTER_BYTES:
|
||||
if stmt.howmuch == 1:
|
||||
# x/y += 1
|
||||
out("\vin{:s}".format(reg.lower()))
|
||||
else:
|
||||
# x/y += 2..255
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vt{:s}a".format(reg.lower()))
|
||||
out("\vclc")
|
||||
out("\vadc #" + howmuch_str)
|
||||
out("\vta{:s}".format(reg.lower()))
|
||||
elif reg == "AX":
|
||||
# AX += 1..255
|
||||
out("\vclc")
|
||||
out("\vadc #" + howmuch_str)
|
||||
out("\vbcc +")
|
||||
out("\vinx")
|
||||
out("+")
|
||||
elif reg == "AY":
|
||||
# AY += 1..255
|
||||
out("\vclc")
|
||||
out("\vadc # " + howmuch_str)
|
||||
out("\vbcc +")
|
||||
out("\viny")
|
||||
out("+")
|
||||
elif reg == "XY":
|
||||
if stmt.howmuch == 1:
|
||||
# XY += 1
|
||||
out("\vinx")
|
||||
out("\vbne +")
|
||||
out("\viny")
|
||||
out("+")
|
||||
else:
|
||||
# XY += 2..255
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vclc")
|
||||
out("\vadc #" + howmuch_str)
|
||||
out("\vtax")
|
||||
out("\vbcc +")
|
||||
out("\viny")
|
||||
out("+")
|
||||
else:
|
||||
raise CodeGenerationError("invalid incr register: " + reg)
|
||||
else:
|
||||
if reg == 'A':
|
||||
# a -= 1..255
|
||||
out("\vsec")
|
||||
out("\vsbc #" + howmuch_str)
|
||||
elif reg in REGISTER_BYTES:
|
||||
if stmt.howmuch == 1:
|
||||
# x/y -= 1
|
||||
out("\vde{:s}".format(reg.lower()))
|
||||
else:
|
||||
# x/y -= 2..255
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vt{:s}a".format(reg.lower()))
|
||||
out("\vsec")
|
||||
out("\vsbc #" + howmuch_str)
|
||||
out("\vta{:s}".format(reg.lower()))
|
||||
elif reg == "AX":
|
||||
# AX -= 1..255
|
||||
out("\vsec")
|
||||
out("\vsbc #" + howmuch_str)
|
||||
out("\vbcs +")
|
||||
out("\vdex")
|
||||
out("+")
|
||||
elif reg == "AY":
|
||||
# AY -= 1..255
|
||||
out("\vsec")
|
||||
out("\vsbc #" + howmuch_str)
|
||||
out("\vbcs +")
|
||||
out("\vdey")
|
||||
out("+")
|
||||
elif reg == "XY":
|
||||
if stmt.howmuch == 1:
|
||||
# XY -= 1
|
||||
out("\vcpx #0")
|
||||
out("\vbne +")
|
||||
out("\vdey")
|
||||
out("+\t\tdex")
|
||||
else:
|
||||
# XY -= 2..255
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vtxa")
|
||||
out("\vsec")
|
||||
out("\vsbc #" + howmuch_str)
|
||||
out("\vtax")
|
||||
out("\vbcs +")
|
||||
out("\vdey")
|
||||
out("+")
|
||||
else:
|
||||
raise CodeGenerationError("invalid decr register: " + reg)
|
||||
|
||||
elif isinstance(target, VarDef):
|
||||
if target.vartype == VarType.CONST:
|
||||
raise CodeGenerationError("cannot modify a constant", target)
|
||||
what_str = scoped_name(target, scope)
|
||||
if target.datatype == DataType.BYTE:
|
||||
if stmt.howmuch == 1:
|
||||
out("\v{:s} {:s}".format("inc" if stmt.operator == "++" else "dec", what_str))
|
||||
else:
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vlda " + what_str)
|
||||
if stmt.operator == "++":
|
||||
out("\vclc")
|
||||
out("\vadc #" + howmuch_str)
|
||||
else:
|
||||
out("\vsec")
|
||||
out("\vsbc #" + howmuch_str)
|
||||
out("\vsta " + what_str)
|
||||
elif target.datatype == DataType.WORD:
|
||||
if stmt.howmuch == 1:
|
||||
# mem.word +=/-= 1
|
||||
if stmt.operator == "++":
|
||||
out("\vinc " + what_str)
|
||||
out("\vbne +")
|
||||
out("\vinc {:s}+1".format(what_str))
|
||||
out("+")
|
||||
else:
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vlda " + what_str)
|
||||
out("\vbne +")
|
||||
out("\vdec {:s}+1".format(what_str))
|
||||
out("+\t\tdec " + what_str)
|
||||
else:
|
||||
# mem.word +=/-= 2..255
|
||||
if stmt.operator == "++":
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vclc")
|
||||
out("\vlda " + what_str)
|
||||
out("\vadc #" + howmuch_str)
|
||||
out("\vsta " + what_str)
|
||||
out("\vbcc +")
|
||||
out("\vinc {:s}+1".format(what_str))
|
||||
out("+")
|
||||
else:
|
||||
with preserving_registers({'A'}, scope, out):
|
||||
out("\vsec")
|
||||
out("\vlda " + what_str)
|
||||
out("\vsbc #" + howmuch_str)
|
||||
out("\vsta " + what_str)
|
||||
out("\vbcs +")
|
||||
out("\vdec {:s}+1".format(what_str))
|
||||
out("+")
|
||||
elif target.datatype == DataType.FLOAT:
|
||||
if not ctx.floats_enabled:
|
||||
raise CodeGenerationError("floating point numbers not enabled via option")
|
||||
if stmt.howmuch == 1.0:
|
||||
# special case for +/-1
|
||||
with preserving_registers({'A', 'X', 'Y'}, scope, out, loads_a_within=True):
|
||||
out("\vldx #<" + what_str)
|
||||
out("\vldy #>" + what_str)
|
||||
if stmt.operator == "++":
|
||||
out("\vjsr c64flt.float_add_one")
|
||||
else:
|
||||
out("\vjsr c64flt.float_sub_one")
|
||||
elif stmt.howmuch != 0:
|
||||
float_name = scope.define_float_constant(stmt.howmuch)
|
||||
with preserving_registers({'A', 'X', 'Y'}, scope, out, loads_a_within=True):
|
||||
out("\vlda #<" + float_name)
|
||||
out("\vsta c64.SCRATCH_ZPWORD1")
|
||||
out("\vlda #>" + float_name)
|
||||
out("\vsta c64.SCRATCH_ZPWORD1+1")
|
||||
out("\vldx #<" + what_str)
|
||||
out("\vldy #>" + what_str)
|
||||
if stmt.operator == "++":
|
||||
out("\vjsr c64flt.float_add_SW1_to_XY")
|
||||
else:
|
||||
out("\vjsr c64flt.float_sub_SW1_from_XY")
|
||||
else:
|
||||
raise CodeGenerationError("cannot in/decrement memory of type " + str(target.datatype), stmt.howmuch)
|
||||
|
||||
elif isinstance(target, Dereference):
|
||||
if isinstance(target.operand, (LiteralValue, SymbolName)):
|
||||
if isinstance(target.operand, LiteralValue):
|
||||
what = to_hex(target.operand.value)
|
||||
else:
|
||||
symdef = target.my_scope().lookup(target.operand.name)
|
||||
if isinstance(symdef, VarDef) and symdef.vartype == VarType.MEMORY:
|
||||
what = to_hex(symdef.value.value) # type: ignore
|
||||
else:
|
||||
what = target.operand.name
|
||||
if stmt.howmuch == 1:
|
||||
if target.datatype == DataType.FLOAT:
|
||||
if not ctx.floats_enabled:
|
||||
raise CodeGenerationError("floating point numbers not enabled via option")
|
||||
with preserving_registers({'A', 'X', 'Y'}, scope, out, loads_a_within=True):
|
||||
out("\vldx " + what)
|
||||
out("\vldy {:s}+1".format(what))
|
||||
if stmt.operator == "++":
|
||||
out("\vjsr c64flt.float_add_one")
|
||||
else:
|
||||
out("\vjsr c64flt.float_sub_one")
|
||||
else:
|
||||
with preserving_registers({'A', 'Y'}, scope, out, loads_a_within=True):
|
||||
out("\vlda " + what)
|
||||
out("\vldy {:s}+1".format(what))
|
||||
if target.datatype == DataType.BYTE:
|
||||
out("\vclc" if stmt.operator == "++" else "\vsec")
|
||||
out("\vjsr il65_lib.incrdecr_deref_byte_reg_AY")
|
||||
elif target.datatype == DataType.WORD:
|
||||
out("\vclc" if stmt.operator == "++" else "\vsec")
|
||||
out("\vjsr il65_lib.incrdecr_deref_word_reg_AY")
|
||||
else:
|
||||
raise CodeGenerationError("cannot inc/decrement dereferenced literal of type " + str(target.datatype), stmt)
|
||||
else:
|
||||
raise CodeGenerationError("can't inc/dec this by something else as 1 right now", stmt) # XXX
|
||||
elif isinstance(target.operand, Register):
|
||||
assert target.operand.datatype == DataType.WORD
|
||||
reg = target.operand.name
|
||||
if stmt.howmuch == 1:
|
||||
out("\vclc" if stmt.operator == "++" else "\vsec")
|
||||
if target.datatype == DataType.BYTE:
|
||||
with preserving_registers({'A', 'Y'}, scope, out, loads_a_within=True):
|
||||
out("\vjsr il65_lib.incrdecr_deref_byte_reg_" + reg)
|
||||
else:
|
||||
with preserving_registers({'A', 'Y'}, scope, out, loads_a_within=True):
|
||||
out("\vjsr il65_lib.incrdecr_deref_word_reg_" + reg)
|
||||
else:
|
||||
raise CodeGenerationError("can't inc/dec this by something else as 1 right now", stmt) # XXX
|
||||
else:
|
||||
raise TypeError("invalid dereference operand type", target)
|
||||
|
||||
else:
|
||||
raise CodeGenerationError("cannot inc/decrement", target) # @todo support more
|
@ -1,269 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
This is the code generator for variable declarations and initialization.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, Callable, Any, no_type_check
|
||||
from ..shared import to_hex, to_mflpt5, CodeGenerationError
|
||||
from ...plyparse import Block, VarDef, LiteralValue, AddressOf
|
||||
from ...datatypes import DataType, VarType, STRING_DATATYPES
|
||||
|
||||
|
||||
def generate_block_init(out: Callable, block: Block) -> None:
|
||||
# generate the block initializer
|
||||
# @todo add a block initializer subroutine that can contain custom reset/init code? (static initializer)
|
||||
# @todo will be called at program start automatically, so there's no risk of forgetting to call it manually
|
||||
|
||||
def _memset(varname: str, value: int, size: int) -> None:
|
||||
if size > 6:
|
||||
out("\vlda #<" + varname)
|
||||
out("\vsta il65_lib.SCRATCH_ZPWORD1")
|
||||
out("\vlda #>" + varname)
|
||||
out("\vsta il65_lib.SCRATCH_ZPWORD1+1")
|
||||
out("\vlda #" + to_hex(value))
|
||||
out("\vldx #<" + to_hex(size))
|
||||
out("\vldy #>" + to_hex(size))
|
||||
out("\vjsr il65_lib.memset")
|
||||
else:
|
||||
out("\vlda #" + to_hex(value))
|
||||
for i in range(size):
|
||||
out("\vsta {:s}+{:d}".format(varname, i))
|
||||
|
||||
def _memsetw(varname: str, value: int, size: int) -> None:
|
||||
if size > 4:
|
||||
out("\vlda #<" + varname)
|
||||
out("\vsta il65_lib.SCRATCH_ZPWORD1")
|
||||
out("\vlda #>" + varname)
|
||||
out("\vsta il65_lib.SCRATCH_ZPWORD1+1")
|
||||
out("\vlda #<" + to_hex(size))
|
||||
out("\vsta il65_lib.SCRATCH_ZPWORD2")
|
||||
out("\vlda #>" + to_hex(size))
|
||||
out("\vsta il65_lib.SCRATCH_ZPWORD2+1")
|
||||
out("\vlda #<" + to_hex(value))
|
||||
out("\vldx #>" + to_hex(value))
|
||||
out("\vjsr il65_lib.memsetw")
|
||||
else:
|
||||
out("\vlda #<" + to_hex(value))
|
||||
out("\vldy #>" + to_hex(value))
|
||||
for i in range(size):
|
||||
out("\vsta {:s}+{:d}".format(varname, i * 2))
|
||||
out("\vsty {:s}+{:d}".format(varname, i * 2 + 1))
|
||||
|
||||
out("_il65_init_block\v; (re)set vars to initial values")
|
||||
float_inits = {}
|
||||
prev_value_a, prev_value_x = None, None
|
||||
vars_by_datatype = defaultdict(list) # type: Dict[DataType, List[VarDef]]
|
||||
for vardef in block.all_nodes(VarDef):
|
||||
if vardef.vartype == VarType.VAR: # type: ignore
|
||||
vars_by_datatype[vardef.datatype].append(vardef) # type: ignore
|
||||
for bytevar in sorted(vars_by_datatype[DataType.BYTE], key=lambda vd: vd.value):
|
||||
assert isinstance(bytevar.value, LiteralValue) and type(bytevar.value.value) is int
|
||||
if bytevar.value.value != prev_value_a:
|
||||
out("\vlda #${:02x}".format(bytevar.value.value))
|
||||
prev_value_a = bytevar.value.value
|
||||
out("\vsta {:s}".format(bytevar.name))
|
||||
for wordvar in sorted(vars_by_datatype[DataType.WORD], key=lambda vd: vd.value):
|
||||
if isinstance(wordvar.value, AddressOf):
|
||||
raise CodeGenerationError("addressof is not a compile-time constant value", wordvar.sourceref)
|
||||
assert isinstance(wordvar.value, LiteralValue) and type(wordvar.value.value) is int
|
||||
v_hi, v_lo = divmod(wordvar.value.value, 256)
|
||||
if v_hi != prev_value_a:
|
||||
out("\vlda #${:02x}".format(v_hi))
|
||||
prev_value_a = v_hi
|
||||
if v_lo != prev_value_x:
|
||||
out("\vldx #${:02x}".format(v_lo))
|
||||
prev_value_x = v_lo
|
||||
out("\vsta {:s}".format(wordvar.name))
|
||||
out("\vstx {:s}+1".format(wordvar.name))
|
||||
for floatvar in vars_by_datatype[DataType.FLOAT]:
|
||||
assert isinstance(floatvar.value, LiteralValue) and type(floatvar.value.value) in (int, float)
|
||||
fpbytes = to_mflpt5(floatvar.value.value) # type: ignore
|
||||
float_inits[floatvar.name] = (floatvar.name, fpbytes, floatvar.value)
|
||||
for arrayvar in vars_by_datatype[DataType.BYTEARRAY]:
|
||||
assert isinstance(arrayvar.value, LiteralValue) and type(arrayvar.value.value) is int
|
||||
_memset(arrayvar.name, arrayvar.value.value, arrayvar.size[0])
|
||||
for arrayvar in vars_by_datatype[DataType.WORDARRAY]:
|
||||
assert isinstance(arrayvar.value, LiteralValue) and type(arrayvar.value.value) is int
|
||||
_memsetw(arrayvar.name, arrayvar.value.value, arrayvar.size[0])
|
||||
for arrayvar in vars_by_datatype[DataType.MATRIX]:
|
||||
assert isinstance(arrayvar.value, LiteralValue) and type(arrayvar.value.value) is int
|
||||
_memset(arrayvar.name, arrayvar.value.value, arrayvar.size[0] * arrayvar.size[1])
|
||||
if float_inits:
|
||||
out("\vldx #4")
|
||||
out("-")
|
||||
for varname, (vname, b, fv) in sorted(float_inits.items()):
|
||||
out("\vlda _init_float_{:s},x".format(varname))
|
||||
out("\vsta {:s},x".format(vname))
|
||||
out("\vdex")
|
||||
out("\vbpl -")
|
||||
out("\vrts\n")
|
||||
for varname, (vname, fpbytes, fpvalue) in sorted(float_inits.items()):
|
||||
assert isinstance(fpvalue, LiteralValue)
|
||||
out("_init_float_{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(varname, *fpbytes, fpvalue.value))
|
||||
all_string_vars = []
|
||||
for svtype in STRING_DATATYPES:
|
||||
all_string_vars.extend(vars_by_datatype[svtype])
|
||||
for strvar in all_string_vars:
|
||||
# string vars are considered to be a constant, and are statically initialized.
|
||||
_generate_string_var(out, strvar)
|
||||
out("")
|
||||
|
||||
|
||||
def generate_block_vars(out: Callable, block: Block, zeropage: bool=False) -> None:
|
||||
# Generate the block variable storage.
|
||||
# The memory bytes of the allocated variables is set to zero (so it compresses very well),
|
||||
# their actual starting values are set by the block init code.
|
||||
vars_by_vartype = defaultdict(list) # type: Dict[VarType, List[VarDef]]
|
||||
for vardef in block.all_nodes(VarDef):
|
||||
vars_by_vartype[vardef.vartype].append(vardef) # type: ignore
|
||||
out("; constants")
|
||||
for vardef in vars_by_vartype.get(VarType.CONST, []):
|
||||
if vardef.datatype == DataType.FLOAT:
|
||||
out("\v{:s} = {}".format(vardef.name, _numeric_value_str(vardef.value)))
|
||||
elif vardef.datatype in (DataType.BYTE, DataType.WORD):
|
||||
assert isinstance(vardef.value.value, int) # type: ignore
|
||||
out("\v{:s} = {:s}".format(vardef.name, _numeric_value_str(vardef.value, True)))
|
||||
elif vardef.datatype.isstring():
|
||||
# a const string is just a string variable in the generated assembly
|
||||
_generate_string_var(out, vardef)
|
||||
else:
|
||||
raise CodeGenerationError("invalid const type", vardef)
|
||||
out("; memory mapped variables")
|
||||
for vardef in vars_by_vartype.get(VarType.MEMORY, []):
|
||||
# create a definition for variables at a specific place in memory (memory-mapped)
|
||||
assert isinstance(vardef.value.value, int) # type: ignore
|
||||
if vardef.datatype.isnumeric():
|
||||
assert vardef.size == [1]
|
||||
out("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value.value), vardef.datatype.name.lower())) # type: ignore
|
||||
elif vardef.datatype == DataType.BYTEARRAY:
|
||||
assert len(vardef.size) == 1
|
||||
out("\v{:s} = {:s}\t; array of {:d} bytes".format(vardef.name, to_hex(vardef.value.value), vardef.size[0])) # type: ignore
|
||||
elif vardef.datatype == DataType.WORDARRAY:
|
||||
assert len(vardef.size) == 1
|
||||
out("\v{:s} = {:s}\t; array of {:d} words".format(vardef.name, to_hex(vardef.value.value), vardef.size[0])) # type: ignore
|
||||
elif vardef.datatype == DataType.MATRIX:
|
||||
assert len(vardef.size) in (2, 3)
|
||||
if len(vardef.size) == 2:
|
||||
comment = "matrix of {:d} by {:d} = {:d} bytes".format(vardef.size[0], vardef.size[1], vardef.size[0]*vardef.size[1])
|
||||
elif len(vardef.size) == 3:
|
||||
comment = "matrix of {:d} by {:d}, interleave {:d}".format(vardef.size[0], vardef.size[1], vardef.size[2])
|
||||
else:
|
||||
raise CodeGenerationError("matrix size must be 2 or 3 numbers")
|
||||
out("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value.value), comment)) # type: ignore
|
||||
else:
|
||||
raise CodeGenerationError("invalid var type")
|
||||
out("; normal variables - initial values will be set by init code")
|
||||
if zeropage:
|
||||
# zeropage uses the zp_address we've allocated, instead of allocating memory here
|
||||
for vardef in vars_by_vartype.get(VarType.VAR, []):
|
||||
assert vardef.zp_address is not None
|
||||
if vardef.datatype.isstring():
|
||||
raise CodeGenerationError("cannot put strings in the zeropage", vardef.sourceref)
|
||||
if vardef.datatype.isarray():
|
||||
size_str = "size " + str(vardef.size)
|
||||
else:
|
||||
size_str = ""
|
||||
out("\v{:s} = {:s}\t; {:s} {:s}".format(vardef.name, to_hex(vardef.zp_address), vardef.datatype.name.lower(), size_str))
|
||||
else:
|
||||
# create definitions for the variables that takes up empty space and will be initialized at startup
|
||||
string_vars = []
|
||||
for vardef in vars_by_vartype.get(VarType.VAR, []):
|
||||
if vardef.datatype.isnumeric():
|
||||
assert vardef.size == [1]
|
||||
assert isinstance(vardef.value, LiteralValue)
|
||||
if vardef.datatype == DataType.BYTE:
|
||||
out("{:s}\v.byte ?\t; {:s}".format(vardef.name, to_hex(vardef.value.value)))
|
||||
elif vardef.datatype == DataType.WORD:
|
||||
out("{:s}\v.word ?\t; {:s}".format(vardef.name, to_hex(vardef.value.value)))
|
||||
elif vardef.datatype == DataType.FLOAT:
|
||||
out("{:s}\v.fill 5\t\t; float {}".format(vardef.name, vardef.value.value))
|
||||
else:
|
||||
raise CodeGenerationError("weird datatype")
|
||||
elif vardef.datatype in (DataType.BYTEARRAY, DataType.WORDARRAY):
|
||||
assert len(vardef.size) == 1
|
||||
if vardef.datatype == DataType.BYTEARRAY:
|
||||
out("{:s}\v.fill {:d}\t\t; bytearray".format(vardef.name, vardef.size[0]))
|
||||
elif vardef.datatype == DataType.WORDARRAY:
|
||||
out("{:s}\v.fill {:d}*2\t\t; wordarray".format(vardef.name, vardef.size[0]))
|
||||
else:
|
||||
raise CodeGenerationError("invalid datatype", vardef.datatype)
|
||||
elif vardef.datatype == DataType.MATRIX:
|
||||
assert len(vardef.size) == 2
|
||||
out("{:s}\v.fill {:d}\t\t; matrix {:d}*{:d} bytes"
|
||||
.format(vardef.name, vardef.size[0] * vardef.size[1], vardef.size[0], vardef.size[1]))
|
||||
elif vardef.datatype.isstring():
|
||||
string_vars.append(vardef)
|
||||
else:
|
||||
raise CodeGenerationError("unknown variable type " + str(vardef.datatype))
|
||||
# string vars are considered to be a constant, and are not re-initialized.
|
||||
out("")
|
||||
|
||||
|
||||
@no_type_check
|
||||
def _generate_string_var(out: Callable, vardef: VarDef) -> None:
|
||||
if vardef.datatype == DataType.STRING:
|
||||
# 0-terminated string
|
||||
out("{:s}\n\v.null {:s}".format(vardef.name, _format_string(str(vardef.value.value))))
|
||||
elif vardef.datatype == DataType.STRING_P:
|
||||
# pascal string
|
||||
out("{:s}\n\v.strp {:s}".format(vardef.name, _format_string(str(vardef.value.value))))
|
||||
elif vardef.datatype == DataType.STRING_S:
|
||||
# 0-terminated string in screencode encoding
|
||||
out(".enc 'screen'")
|
||||
out("{:s}\n\v.null {:s}".format(vardef.name, _format_string(str(vardef.value.value), True)))
|
||||
out(".enc 'none'")
|
||||
elif vardef.datatype == DataType.STRING_PS:
|
||||
# 0-terminated pascal string in screencode encoding
|
||||
out(".enc 'screen'")
|
||||
out("{:s}n\v.strp {:s}".format(vardef.name, _format_string(str(vardef.value.value), True)))
|
||||
out(".enc 'none'")
|
||||
|
||||
|
||||
def _format_string(value: str, screencodes: bool = False) -> str:
|
||||
if len(value) == 1 and screencodes:
|
||||
if value[0].isprintable() and ord(value[0]) < 128:
|
||||
return "'{:s}'".format(value[0])
|
||||
else:
|
||||
return str(ord(value[0]))
|
||||
result = '"'
|
||||
for char in value:
|
||||
if char in "{}":
|
||||
result += '", {:d}, "'.format(ord(char))
|
||||
elif char.isprintable() and ord(char) < 128:
|
||||
result += char
|
||||
else:
|
||||
if screencodes:
|
||||
result += '", {:d}, "'.format(ord(char))
|
||||
else:
|
||||
if char == '\f':
|
||||
result += "{clear}"
|
||||
elif char == '\b':
|
||||
result += "{delete}"
|
||||
elif char == '\n':
|
||||
result += "{cr}"
|
||||
elif char == '\r':
|
||||
result += "{down}"
|
||||
elif char == '\t':
|
||||
result += "{tab}"
|
||||
else:
|
||||
result += '", {:d}, "'.format(ord(char))
|
||||
return result + '"'
|
||||
|
||||
|
||||
def _numeric_value_str(value: Any, as_hex: bool=False) -> str:
|
||||
if isinstance(value, LiteralValue):
|
||||
value = value.value
|
||||
if isinstance(value, bool):
|
||||
return "1" if value else "0"
|
||||
if type(value) is int:
|
||||
if as_hex:
|
||||
return to_hex(value)
|
||||
return str(value)
|
||||
if isinstance(value, (int, float)):
|
||||
if as_hex:
|
||||
raise TypeError("cannot output float as hex")
|
||||
return str(value)
|
||||
raise TypeError("no numeric representation possible", value)
|
@ -1,87 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
Shared logic for the code generators.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
|
||||
import math
|
||||
from ..datatypes import FLOAT_MAX_POSITIVE, FLOAT_MAX_NEGATIVE
|
||||
from ..plyparse import Module, Label, Block, Directive, VarDef
|
||||
from ..plylex import print_bold
|
||||
|
||||
|
||||
class CodeGenerationError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def sanitycheck(module: Module) -> None:
|
||||
for label in module.all_nodes(Label):
|
||||
if label.name == "start" and label.my_scope().name == "main": # type: ignore
|
||||
break
|
||||
else:
|
||||
print_bold("ERROR: program entry point is missing ('start' label in 'main' block)\n")
|
||||
raise SystemExit(1)
|
||||
all_blocknames = [b.name for b in module.all_nodes(Block)] # type: ignore
|
||||
unique_blocknames = set(all_blocknames)
|
||||
if len(all_blocknames) != len(unique_blocknames):
|
||||
for name in unique_blocknames:
|
||||
all_blocknames.remove(name)
|
||||
raise CodeGenerationError("there are duplicate block names", all_blocknames)
|
||||
zpblock = module.zeropage()
|
||||
if zpblock:
|
||||
# ZP block contains no code?
|
||||
for stmt in zpblock.scope.nodes:
|
||||
if not isinstance(stmt, (Directive, VarDef)):
|
||||
raise CodeGenerationError("ZP block can only contain directive and var")
|
||||
|
||||
|
||||
def to_hex(number: int) -> str:
|
||||
# 0..15 -> "0".."15"
|
||||
# 16..255 -> "$10".."$ff"
|
||||
# 256..65536 -> "$0100".."$ffff"
|
||||
assert type(number) is int
|
||||
if number is None:
|
||||
raise ValueError("number")
|
||||
if 0 <= number < 16:
|
||||
return str(number)
|
||||
if 0 <= number < 0x100:
|
||||
return "${:02x}".format(number)
|
||||
if 0 <= number < 0x10000:
|
||||
return "${:04x}".format(number)
|
||||
raise OverflowError(number)
|
||||
|
||||
|
||||
def to_mflpt5(number: float) -> bytearray:
|
||||
# algorithm here https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
||||
number = float(number)
|
||||
if number < FLOAT_MAX_NEGATIVE or number > FLOAT_MAX_POSITIVE:
|
||||
raise OverflowError("floating point number out of 5-byte mflpt range", number)
|
||||
if number == 0.0:
|
||||
return bytearray([0, 0, 0, 0, 0])
|
||||
if number < 0.0:
|
||||
sign = 0x80000000
|
||||
number = -number
|
||||
else:
|
||||
sign = 0x00000000
|
||||
mant, exp = math.frexp(number)
|
||||
exp += 128
|
||||
if exp < 1:
|
||||
# underflow, use zero instead
|
||||
return bytearray([0, 0, 0, 0, 0])
|
||||
if exp > 255:
|
||||
raise OverflowError("floating point number out of 5-byte mflpt range", number)
|
||||
mant = sign | int(mant * 0x100000000) & 0x7fffffff
|
||||
return bytearray([exp]) + int.to_bytes(mant, 4, "big")
|
||||
|
||||
|
||||
def mflpt5_to_float(mflpt: bytearray) -> float:
|
||||
# algorithm here https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
|
||||
if mflpt == bytearray([0, 0, 0, 0, 0]):
|
||||
return 0.0
|
||||
exp = mflpt[0] - 128
|
||||
sign = mflpt[1] & 0x80
|
||||
number = 0x80000000 | int.from_bytes(mflpt[1:], "big")
|
||||
number = float(number) * 2**exp / 0x100000000
|
||||
return -number if sign else number
|
@ -1,8 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
This is the tinyvm stack based program generator
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
|
@ -1,91 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
This is the tinyvm stack based program generator.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
# @todo
|
||||
|
||||
import os
|
||||
import datetime
|
||||
import pickle
|
||||
from typing import BinaryIO, List, Tuple, Dict, Optional
|
||||
from ..shared import CodeGenerationError, sanitycheck
|
||||
from ...plyparse import Module, Block, Scope, VarDef, Expression, LiteralValue
|
||||
from ...datatypes import VarType, DataType
|
||||
import tinyvm.core
|
||||
import tinyvm.program
|
||||
|
||||
|
||||
class AssemblyGenerator:
|
||||
def __init__(self, module: Module, enable_floats: bool) -> None:
|
||||
self.module = module
|
||||
self.floats_enabled = enable_floats
|
||||
|
||||
def generate(self, filename: str) -> None:
|
||||
with open(filename+".pickle", "wb") as stream:
|
||||
self._generate(stream)
|
||||
|
||||
def _generate(self, out: BinaryIO) -> None:
|
||||
sanitycheck(self.module)
|
||||
program = self.header()
|
||||
program.blocks = self.blocks(self.module.nodes[0], None) # type: ignore
|
||||
pickle.dump(program, out, pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
def header(self) -> tinyvm.program.Program:
|
||||
return tinyvm.program.Program([])
|
||||
|
||||
def blocks(self, scope: Scope, parentblock_vm: Optional[tinyvm.program.Block]) -> List[tinyvm.program.Block]:
|
||||
blocks = []
|
||||
for node in scope.nodes:
|
||||
if isinstance(node, Block):
|
||||
variables = self.make_vars(node)
|
||||
labels, instructions = self.make_instructions(node)
|
||||
vmblock = tinyvm.program.Block(node.name, parentblock_vm, variables, instructions, labels)
|
||||
print(vmblock)
|
||||
blocks.append(vmblock)
|
||||
vmblock.blocks = self.blocks(node.nodes[0], vmblock) # type: ignore
|
||||
return blocks
|
||||
|
||||
def make_vars(self, block: Block) -> List[tinyvm.program.Variable]:
|
||||
variables = []
|
||||
for vardef in block.all_nodes(VarDef):
|
||||
assert isinstance(vardef, VarDef)
|
||||
dtype = self.translate_datatype(vardef.datatype)
|
||||
value = self.translate_value(vardef.value, dtype)
|
||||
if vardef.vartype == VarType.CONST:
|
||||
const = True
|
||||
elif vardef.vartype == VarType.VAR:
|
||||
const = False
|
||||
else:
|
||||
raise CodeGenerationError("unsupported vartype", vardef.vartype)
|
||||
variables.append(tinyvm.program.Variable(vardef.name, dtype, value, const))
|
||||
return variables
|
||||
|
||||
def make_instructions(self, block: Block) -> Tuple[Dict[str, tinyvm.program.Instruction], List[tinyvm.program.Instruction]]:
|
||||
# returns a dict with the labels (named instruction pointers),
|
||||
# and a list of the program instructions.
|
||||
return {}, []
|
||||
|
||||
def translate_datatype(self, datatype: DataType) -> tinyvm.core.DataType:
|
||||
table = {
|
||||
DataType.BYTE: tinyvm.core.DataType.BYTE,
|
||||
DataType.WORD: tinyvm.core.DataType.WORD,
|
||||
DataType.FLOAT: tinyvm.core.DataType.FLOAT,
|
||||
DataType.BYTEARRAY: tinyvm.core.DataType.ARRAY_BYTE,
|
||||
DataType.WORDARRAY: tinyvm.core.DataType.ARRAY_WORD,
|
||||
DataType.MATRIX: tinyvm.core.DataType.MATRIX_BYTE
|
||||
}
|
||||
dt = table.get(datatype, None)
|
||||
if dt:
|
||||
return dt
|
||||
raise CodeGenerationError("unsupported datatype", datatype)
|
||||
|
||||
def translate_value(self, expr: Expression, dtypehint: Optional[tinyvm.core.DataType]) -> tinyvm.program.Value:
|
||||
if isinstance(expr, LiteralValue):
|
||||
dtype = dtypehint or tinyvm.core.DataType.guess_datatype_for(expr.value)
|
||||
return tinyvm.program.Value(dtype, expr.value)
|
||||
else:
|
||||
raise CodeGenerationError("cannot yet generate value for expression node", expr)
|
||||
|
@ -1,592 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
This is the compiler of the IL65 code, that prepares the parse tree for code generation.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
import linecache
|
||||
from typing import no_type_check, Set, List, Dict, Tuple, Optional, Any
|
||||
import attr
|
||||
from .datatypes import DataType, VarType
|
||||
from .plylex import SourceRef, print_bold
|
||||
from .constantfold import ConstantFold
|
||||
from .plyparse import *
|
||||
|
||||
|
||||
class CompileError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class PlyParser:
|
||||
def __init__(self, *, enable_floats: bool=False, imported_module: bool=False) -> None:
|
||||
self.parse_errors = 0
|
||||
self.imported_module = imported_module
|
||||
self.floats_enabled = enable_floats
|
||||
|
||||
def parse_file(self, filename: str) -> Module:
|
||||
print("parsing:", filename)
|
||||
module = None
|
||||
try:
|
||||
module = parse_file(filename, self.lexer_error)
|
||||
self.check_directives_and_const_defs(module)
|
||||
self.apply_directive_options(module)
|
||||
module.scope.define_builtin_functions()
|
||||
self.process_imports(module)
|
||||
self.check_and_merge_zeropages(module)
|
||||
if not self.imported_module:
|
||||
# the following shall only be done on the main module after all imports have been done:
|
||||
self.check_all_symbolnames(module)
|
||||
self.determine_subroutine_usage(module)
|
||||
self.all_parents_connected(module)
|
||||
cf = ConstantFold(module)
|
||||
cf.fold_constants()
|
||||
self.semantic_check(module)
|
||||
self.coerce_values(module)
|
||||
self.check_floats_enabled(module)
|
||||
self.allocate_zeropage_vars(module)
|
||||
except ParseError as x:
|
||||
self.handle_parse_error(x)
|
||||
if self.parse_errors:
|
||||
print_bold("\nNo output; there were {:d} errors.\n".format(self.parse_errors))
|
||||
raise SystemExit(1)
|
||||
return module
|
||||
|
||||
def lexer_error(self, sourceref: SourceRef, fmtstring: str, *args: str) -> None:
|
||||
self.parse_errors += 1
|
||||
self.print_error_sourceline(sourceref)
|
||||
print_bold("ERROR: {}: {}".format(sourceref, fmtstring.format(*args)))
|
||||
|
||||
def _check_last_statement_is_return(self, last_stmt: AstNode) -> None:
|
||||
if isinstance(last_stmt, (Subroutine, Return, Goto)):
|
||||
return
|
||||
if isinstance(last_stmt, Directive) and last_stmt.name == "noreturn":
|
||||
return
|
||||
if isinstance(last_stmt, InlineAssembly):
|
||||
for line in reversed(last_stmt.assembly.splitlines()):
|
||||
line = line.strip()
|
||||
if line.startswith(";"):
|
||||
continue
|
||||
if "jmp " in line or "jmp\t" in line or "rts" in line or "rti" in line:
|
||||
return
|
||||
raise ParseError("last statement in a block/subroutine must be a return or goto, "
|
||||
"(or %noreturn directive to silence this error)", last_stmt.sourceref)
|
||||
|
||||
def check_floats_enabled(self, module: Module) -> None:
|
||||
if self.floats_enabled:
|
||||
return
|
||||
for node in module.all_nodes():
|
||||
if isinstance(node, LiteralValue):
|
||||
if type(node.value) is float:
|
||||
raise ParseError("floating point numbers not enabled via option", node.sourceref)
|
||||
elif isinstance(node, VarDef):
|
||||
if node.datatype == DataType.FLOAT:
|
||||
raise ParseError("floating point numbers not enabled via option", node.sourceref)
|
||||
|
||||
def coerce_values(self, module: Module) -> None:
|
||||
for node in module.all_nodes():
|
||||
try:
|
||||
# note: not processing regular assignments, because they can contain multiple targets of different datatype.
|
||||
# this has to be dealt with anyway later, so we don't bother dealing with it here for just a special case.
|
||||
if isinstance(node, AugAssignment):
|
||||
if node.right.is_compiletime_const():
|
||||
_, node.right = coerce_constant_value(datatype_of(node.left, node.my_scope()), node.right, node.right.sourceref)
|
||||
elif isinstance(node, Goto):
|
||||
if node.condition is not None and node.condition.is_compiletime_const():
|
||||
_, node.nodes[1] = coerce_constant_value(DataType.WORD, node.nodes[1], node.nodes[1].sourceref) # type: ignore
|
||||
elif isinstance(node, Return):
|
||||
if node.value_A is not None and node.value_A.is_compiletime_const():
|
||||
_, node.nodes[0] = coerce_constant_value(DataType.BYTE, node.nodes[0], node.nodes[0].sourceref) # type: ignore
|
||||
if node.value_X is not None and node.value_X.is_compiletime_const():
|
||||
_, node.nodes[1] = coerce_constant_value(DataType.BYTE, node.nodes[1], node.nodes[1].sourceref) # type: ignore
|
||||
if node.value_Y is not None and node.value_Y.is_compiletime_const():
|
||||
_, node.nodes[2] = coerce_constant_value(DataType.BYTE, node.nodes[2], node.nodes[2].sourceref) # type: ignore
|
||||
elif isinstance(node, VarDef):
|
||||
if node.value is not None:
|
||||
if node.value.is_compiletime_const():
|
||||
_, node.value = coerce_constant_value(datatype_of(node, node.my_scope()), node.value, node.value.sourceref)
|
||||
except OverflowError as x:
|
||||
raise ParseError(str(x), node.sourceref)
|
||||
|
||||
def all_parents_connected(self, module: Module) -> None:
|
||||
# check that all parents are connected in all nodes
|
||||
def check(node: AstNode, expected_parent: AstNode) -> None:
|
||||
if node.parent is not expected_parent:
|
||||
print("\nINTERNAL ERROR: parent node invalid of node", node, node.sourceref)
|
||||
print(" current parent:", node.parent)
|
||||
print(" expected parent:", expected_parent, expected_parent.sourceref)
|
||||
raise CompileError("parent node invalid, see message above")
|
||||
for child_node in node.nodes:
|
||||
if isinstance(child_node, AstNode):
|
||||
check(child_node, node)
|
||||
else:
|
||||
raise TypeError("invalid child node type", child_node, " in ", node, " sref", node.sourceref)
|
||||
check(module, None)
|
||||
|
||||
def semantic_check(self, module: Module) -> None:
|
||||
# perform semantic analysis / checks on the syntactic parse tree we have so far
|
||||
# (note: symbol names have already been checked to exist when we start this)
|
||||
previous_stmt = None
|
||||
encountered_block_names = set() # type: Set[str]
|
||||
encountered_blocks = set() # type: Set[Block]
|
||||
for node in module.all_nodes():
|
||||
if isinstance(node, Block):
|
||||
if node in encountered_blocks:
|
||||
raise CompileError("parse tree malformed; block duplicated", node, node.name, node.sourceref)
|
||||
if node.name is None:
|
||||
# blocks without names are possible, in this case their address is specified
|
||||
if node.address is not None:
|
||||
continue
|
||||
else:
|
||||
raise ParseError("block without name must have address", node.sourceref)
|
||||
parentname = (node.parent.name + ".") if node.parent else "" # type: ignore
|
||||
blockname = parentname + node.name
|
||||
if blockname in encountered_block_names:
|
||||
raise CompileError("block names not unique:", blockname)
|
||||
encountered_block_names.add(blockname)
|
||||
encountered_blocks.add(node)
|
||||
if isinstance(node, Scope):
|
||||
if node.nodes and isinstance(node.parent, (Block, Subroutine)):
|
||||
if isinstance(node.parent, Block) and node.parent.name != "ZP":
|
||||
self._check_last_statement_is_return(node.nodes[-1])
|
||||
elif isinstance(node, SubCall):
|
||||
if isinstance(node.target, SymbolName):
|
||||
subdef = node.my_scope().lookup(node.target.name)
|
||||
if isinstance(subdef, Subroutine):
|
||||
self.check_subroutine_arguments(node, subdef)
|
||||
elif isinstance(node, Subroutine):
|
||||
# the previous statement (if any) must be a Goto or Return
|
||||
if not isinstance(previous_stmt, (Scope, Goto, Return, VarDef, Subroutine)):
|
||||
raise ParseError("statement preceding subroutine must be a goto or return or another subroutine", node.sourceref)
|
||||
elif isinstance(node, IncrDecr):
|
||||
if isinstance(node.target, SymbolName):
|
||||
symdef = node.my_scope().lookup(node.target.name)
|
||||
if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST:
|
||||
raise ParseError("cannot modify a constant", node.sourceref)
|
||||
elif isinstance(node, Assignment):
|
||||
scope = node.my_scope()
|
||||
for target in node.left.nodes:
|
||||
if isinstance(target, SymbolName):
|
||||
symdef = scope.lookup(target.name)
|
||||
if isinstance(symdef, VarDef) and symdef.vartype == VarType.CONST:
|
||||
raise ParseError("cannot modify a constant", target.sourceref)
|
||||
elif isinstance(node, AugAssignment):
|
||||
# the assignment target must not be a constant
|
||||
if isinstance(node.left, SymbolName):
|
||||
symdef = node.my_scope().lookup(node.left.name)
|
||||
if isinstance(symdef, VarDef):
|
||||
if symdef.vartype == VarType.CONST:
|
||||
raise ParseError("cannot modify a constant", node.sourceref)
|
||||
elif symdef.datatype not in {DataType.BYTE, DataType.WORD, DataType.FLOAT}:
|
||||
raise ParseError("cannot modify that datatype ({:s}) in this way"
|
||||
.format(symdef.datatype.name.lower()), node.sourceref)
|
||||
# check for divide by (constant) zero
|
||||
if node.operator in ("/=", "//="):
|
||||
if isinstance(node.right, LiteralValue) and node.right.value == 0:
|
||||
raise ParseError("division by zero", node.right.sourceref)
|
||||
elif isinstance(node, VarDef):
|
||||
if node.value is not None and not node.value.is_compiletime_const():
|
||||
raise ParseError("variable initialization value should be a compile-time constant", node.value.sourceref)
|
||||
elif isinstance(node, Dereference):
|
||||
if isinstance(node.operand, Register) and node.operand.datatype == DataType.BYTE:
|
||||
raise ParseError("can't dereference a single register; use a register pair", node.operand.sourceref)
|
||||
previous_stmt = node
|
||||
|
||||
def check_subroutine_arguments(self, call: SubCall, subdef: Subroutine) -> None:
|
||||
if len(call.arguments.nodes) != len(subdef.param_spec):
|
||||
raise ParseError("invalid number of arguments ({:d}, required: {:d})"
|
||||
.format(len(call.arguments.nodes), len(subdef.param_spec)), call.sourceref)
|
||||
for arg, param in zip(call.arguments.nodes, subdef.param_spec):
|
||||
if arg.name:
|
||||
if not param[0]:
|
||||
raise ParseError("parameter is unnamed but name was used", arg.sourceref)
|
||||
if arg.name != param[0]:
|
||||
raise ParseError("parameter name mismatch", arg.sourceref)
|
||||
|
||||
@no_type_check
|
||||
def check_and_merge_zeropages(self, module: Module) -> None:
|
||||
# merge all ZP blocks into one
|
||||
for zeropage in module.all_nodes(Block):
|
||||
if zeropage.name == "ZP":
|
||||
break
|
||||
else:
|
||||
return
|
||||
for block in list(module.all_nodes(Block)):
|
||||
if block is not zeropage and block.name == "ZP":
|
||||
# merge other ZP block into first ZP block
|
||||
for node in block.scope.nodes:
|
||||
if isinstance(node, Directive):
|
||||
zeropage.scope.add_node(node, 0)
|
||||
elif isinstance(node, VarDef):
|
||||
zeropage.scope.add_node(node)
|
||||
else:
|
||||
raise ParseError("only variables and directives allowed in zeropage block", node.sourceref)
|
||||
block.parent.remove_node(block)
|
||||
block.parent._populate_symboltable(zeropage) # re-add the 'ZP' symbol
|
||||
|
||||
def allocate_zeropage_vars(self, module: Module) -> None:
|
||||
# allocate zeropage variables to the available free zp addresses
|
||||
if not module.scope.nodes:
|
||||
return
|
||||
zpnode = module.zeropage()
|
||||
if zpnode is None:
|
||||
return
|
||||
zeropage = Zeropage(module.zp_options, self.floats_enabled)
|
||||
for vardef in zpnode.all_nodes(VarDef):
|
||||
if vardef.datatype.isstring():
|
||||
raise ParseError("cannot put strings in the zeropage", vardef.sourceref)
|
||||
try:
|
||||
if vardef.vartype == VarType.VAR:
|
||||
vardef.zp_address = zeropage.allocate(vardef)
|
||||
except CompileError as x:
|
||||
raise ParseError(str(x), vardef.sourceref)
|
||||
|
||||
def check_all_symbolnames(self, module: Module) -> None:
|
||||
for node in module.all_nodes(SymbolName):
|
||||
check_symbol_definition(node.name, node.my_scope(), node.sourceref) # type: ignore
|
||||
|
||||
@no_type_check
|
||||
def apply_directive_options(self, module: Module) -> None:
|
||||
def set_save_registers(scope: Scope, save_dir: Directive) -> None:
|
||||
if not scope:
|
||||
return
|
||||
if len(save_dir.args) > 1:
|
||||
raise ParseError("expected zero or one directive argument", save_dir.sourceref)
|
||||
if save_dir.args:
|
||||
if save_dir.args[0] in ("yes", "true", True):
|
||||
scope.save_registers = True
|
||||
elif save_dir.args[0] in ("no", "false", False):
|
||||
scope.save_registers = False
|
||||
else:
|
||||
raise ParseError("invalid directive args", save_dir.sourceref)
|
||||
else:
|
||||
scope.save_registers = True
|
||||
|
||||
for directive in module.all_nodes(Directive):
|
||||
node = directive.my_scope().parent
|
||||
if isinstance(node, Module):
|
||||
# process the module's directives
|
||||
if directive.name == "output":
|
||||
if len(directive.args) != 1 or not isinstance(directive.args[0], str):
|
||||
raise ParseError("expected one str directive argument", directive.sourceref)
|
||||
if directive.args[0] == "raw":
|
||||
node.format = ProgramFormat.RAW
|
||||
node.address = 0xc000
|
||||
elif directive.args[0] == "prg":
|
||||
node.format = ProgramFormat.PRG
|
||||
node.address = 0xc000
|
||||
elif directive.args[0] == "basic":
|
||||
node.format = ProgramFormat.BASIC
|
||||
node.address = 0x0801
|
||||
elif directive.args[0] == "enable_floats":
|
||||
self.floats_enabled = module.floats_enabled = True
|
||||
else:
|
||||
raise ParseError("invalid directive args", directive.sourceref)
|
||||
elif directive.name == "address":
|
||||
if len(directive.args) != 1 or type(directive.args[0]) is not int:
|
||||
raise ParseError("expected one integer directive argument", directive.sourceref)
|
||||
if node.format == ProgramFormat.BASIC:
|
||||
raise ParseError("basic cannot have a custom load address", directive.sourceref)
|
||||
node.address = directive.args[0]
|
||||
attr.validate(node)
|
||||
elif directive.name in "import":
|
||||
pass # is processed earlier
|
||||
elif directive.name == "zp":
|
||||
if len(directive.args) not in (1, 2) or set(directive.args) - {"clobber", "restore"}:
|
||||
raise ParseError("invalid directive args", directive.sourceref)
|
||||
if "clobber" in directive.args and "restore" in directive.args:
|
||||
module.zp_options = ZpOptions.CLOBBER_RESTORE
|
||||
elif "clobber" in directive.args:
|
||||
module.zp_options = ZpOptions.CLOBBER
|
||||
elif "restore" in directive.args:
|
||||
raise ParseError("invalid directive args", directive.sourceref)
|
||||
elif directive.name == "saveregisters":
|
||||
set_save_registers(directive.my_scope(), directive)
|
||||
else:
|
||||
raise NotImplementedError(directive.name)
|
||||
elif isinstance(node, Block):
|
||||
# process the block's directives
|
||||
if directive.name == "saveregisters":
|
||||
set_save_registers(directive.my_scope(), directive)
|
||||
elif directive.name in ("breakpoint", "asmbinary", "asminclude", "noreturn"):
|
||||
continue
|
||||
else:
|
||||
raise NotImplementedError(directive.name)
|
||||
elif isinstance(node, Subroutine):
|
||||
# process the sub's directives
|
||||
if directive.name == "saveregisters":
|
||||
set_save_registers(directive.my_scope(), directive)
|
||||
elif directive.name in ("breakpoint", "asmbinary", "asminclude", "noreturn"):
|
||||
continue
|
||||
else:
|
||||
raise NotImplementedError(directive.name)
|
||||
|
||||
@no_type_check
|
||||
def determine_subroutine_usage(self, module: Module) -> None:
|
||||
module.subroutine_usage.clear()
|
||||
for node in module.all_nodes():
|
||||
if isinstance(node, InlineAssembly):
|
||||
self._get_subroutine_usages_from_asm(module.subroutine_usage, node, node.my_scope())
|
||||
elif isinstance(node, SubCall):
|
||||
self._get_subroutine_usages_from_subcall(module.subroutine_usage, node, node.my_scope())
|
||||
elif isinstance(node, Goto):
|
||||
self._get_subroutine_usages_from_goto(module.subroutine_usage, node, node.my_scope())
|
||||
elif isinstance(node, Return):
|
||||
self._get_subroutine_usages_from_return(module.subroutine_usage, node, node.my_scope())
|
||||
elif isinstance(node, Assignment):
|
||||
self._get_subroutine_usages_from_assignment(module.subroutine_usage, node, node.my_scope())
|
||||
# print("----------SUBROUTINES IN USE-------------")
|
||||
# import pprint
|
||||
# pprint.pprint(module.subroutine_usage)
|
||||
# print("----------/SUBROUTINES IN USE-------------")
|
||||
|
||||
def _get_subroutine_usages_from_subcall(self, usages: Dict[Tuple[str, str], Set[str]],
|
||||
subcall: SubCall, parent_scope: Scope) -> None:
|
||||
if isinstance(subcall.target, SymbolName):
|
||||
usages[(parent_scope.name, subcall.target.name)].add(str(subcall.sourceref))
|
||||
for arg in subcall.arguments.nodes:
|
||||
self._get_subroutine_usages_from_expression(usages, arg.value, parent_scope)
|
||||
|
||||
def _get_subroutine_usages_from_expression(self, usages: Dict[Tuple[str, str], Set[str]],
|
||||
expr: Any, parent_scope: Scope) -> None:
|
||||
if expr is None or isinstance(expr, (int, str, float, bool, Register)):
|
||||
return
|
||||
elif isinstance(expr, SubCall):
|
||||
self._get_subroutine_usages_from_subcall(usages, expr, parent_scope)
|
||||
elif isinstance(expr, ExpressionWithOperator):
|
||||
self._get_subroutine_usages_from_expression(usages, expr.left, parent_scope)
|
||||
self._get_subroutine_usages_from_expression(usages, expr.right, parent_scope)
|
||||
elif isinstance(expr, (LiteralValue, AddressOf)):
|
||||
return
|
||||
elif isinstance(expr, Dereference):
|
||||
return self._get_subroutine_usages_from_expression(usages, expr.operand, parent_scope)
|
||||
elif isinstance(expr, SymbolName):
|
||||
try:
|
||||
symbol = parent_scope.lookup(expr.name)
|
||||
if isinstance(symbol, Subroutine):
|
||||
usages[(parent_scope.name, expr.name)].add(str(expr.sourceref))
|
||||
except UndefinedSymbolError:
|
||||
pass
|
||||
else:
|
||||
raise TypeError("unknown expr type to scan for sub usages", expr, expr.sourceref)
|
||||
|
||||
@no_type_check
|
||||
def _get_subroutine_usages_from_goto(self, usages: Dict[Tuple[str, str], Set[str]],
|
||||
goto: Goto, parent_scope: Scope) -> None:
|
||||
if isinstance(goto.target, SymbolName):
|
||||
usages[(parent_scope.name, goto.target.name)].add(str(goto.sourceref))
|
||||
self._get_subroutine_usages_from_expression(usages, goto.condition, parent_scope)
|
||||
|
||||
def _get_subroutine_usages_from_return(self, usages: Dict[Tuple[str, str], Set[str]],
|
||||
returnnode: Return, parent_scope: Scope) -> None:
|
||||
# node.value_A (expression), value_X (expression), value_Y (expression)
|
||||
self._get_subroutine_usages_from_expression(usages, returnnode.value_A, parent_scope)
|
||||
self._get_subroutine_usages_from_expression(usages, returnnode.value_X, parent_scope)
|
||||
self._get_subroutine_usages_from_expression(usages, returnnode.value_Y, parent_scope)
|
||||
|
||||
def _get_subroutine_usages_from_assignment(self, usages: Dict[Tuple[str, str], Set[str]],
|
||||
assignment: Assignment, parent_scope: Scope) -> None:
|
||||
# node.right (expression, or another Assignment)
|
||||
if isinstance(assignment.right, Assignment):
|
||||
self._get_subroutine_usages_from_assignment(usages, assignment.right, parent_scope)
|
||||
else:
|
||||
self._get_subroutine_usages_from_expression(usages, assignment.right, parent_scope)
|
||||
|
||||
def _get_subroutine_usages_from_asm(self, usages: Dict[Tuple[str, str], Set[str]],
|
||||
asmnode: InlineAssembly, parent_scope: Scope) -> None:
|
||||
# asm can refer to other symbols as well, track subroutine usage
|
||||
for line in asmnode.assembly.splitlines():
|
||||
splits = line.split(maxsplit=1)
|
||||
if len(splits) == 2:
|
||||
for match in re.finditer(r"(?P<symbol>[a-zA-Z_$][a-zA-Z0-9_\.]+)", splits[1]):
|
||||
name = match.group("symbol")
|
||||
if name[0] == '$':
|
||||
continue
|
||||
try:
|
||||
symbol = parent_scope.lookup(name)
|
||||
except UndefinedSymbolError:
|
||||
pass
|
||||
else:
|
||||
if isinstance(symbol, Subroutine):
|
||||
if symbol.scope:
|
||||
namespace = symbol.scope.parent_scope.name
|
||||
else:
|
||||
namespace, name = name.rsplit(".", maxsplit=2)
|
||||
usages[(namespace, symbol.name)].add(str(asmnode.sourceref))
|
||||
|
||||
def check_directives_and_const_defs(self, module: Module) -> None:
|
||||
imports = set() # type: Set[str]
|
||||
for node in module.all_nodes():
|
||||
if isinstance(node, VarDef):
|
||||
if node.value is None and node.vartype == VarType.CONST:
|
||||
raise ParseError("const should be initialized with a compile-time constant value", node.sourceref)
|
||||
elif isinstance(node, Directive):
|
||||
assert isinstance(node.parent, Scope)
|
||||
if node.parent.level == "module":
|
||||
if node.name not in {"output", "zp", "address", "import", "saveregisters", "noreturn"}:
|
||||
raise ParseError("invalid directive in module", node.sourceref)
|
||||
if node.name == "import":
|
||||
if imports & set(node.args):
|
||||
raise ParseError("duplicate import", node.sourceref)
|
||||
imports |= set(node.args)
|
||||
else:
|
||||
if node.name not in {"asmbinary", "asminclude", "breakpoint", "saveregisters", "noreturn"}:
|
||||
raise ParseError("invalid directive in " + node.parent.__class__.__name__.lower(), node.sourceref)
|
||||
if node.name == "saveregisters":
|
||||
# it should be the first node in the scope
|
||||
if node.parent.nodes[0] is not node:
|
||||
raise ParseError("saveregisters directive must be first in this scope", node.sourceref)
|
||||
|
||||
def process_imports(self, module: Module) -> None:
|
||||
# (recursively) imports the modules
|
||||
imported = []
|
||||
for directive in module.all_nodes(Directive):
|
||||
if directive.name == "import": # type: ignore
|
||||
if len(directive.args) < 1: # type: ignore
|
||||
raise ParseError("missing argument(s) for import directive", directive.sourceref)
|
||||
for arg in directive.args: # type: ignore
|
||||
filename = self.find_import_file(arg, directive.sourceref.file)
|
||||
if not filename:
|
||||
raise ParseError("imported file not found", directive.sourceref)
|
||||
imported_module, import_parse_errors = self.import_file(filename)
|
||||
imported.append(imported_module)
|
||||
self.parse_errors += import_parse_errors
|
||||
if not self.imported_module:
|
||||
# compiler support library is always imported (in main parser)
|
||||
filename = self.find_import_file("il65lib", module.sourceref.file)
|
||||
if filename:
|
||||
imported_module, import_parse_errors = self.import_file(filename)
|
||||
imported.append(imported_module)
|
||||
self.parse_errors += import_parse_errors
|
||||
else:
|
||||
raise FileNotFoundError("missing il65lib")
|
||||
if sum(m.floats_enabled for m in imported):
|
||||
self.floats_enabled = module.floats_enabled = True
|
||||
# append the imported module's contents (blocks) at the end of the current module
|
||||
for block in (node for imported_module in imported
|
||||
for node in imported_module.scope.nodes
|
||||
if isinstance(node, Block)):
|
||||
module.scope.add_node(block)
|
||||
|
||||
def import_file(self, filename: str) -> Tuple[Module, int]:
|
||||
sub_parser = PlyParser(imported_module=True)
|
||||
return sub_parser.parse_file(filename), sub_parser.parse_errors
|
||||
|
||||
def find_import_file(self, modulename: str, sourcefile: str) -> Optional[str]:
|
||||
candidates = [modulename+".ill", modulename]
|
||||
filename_at_source_location = os.path.join(os.path.split(sourcefile)[0], modulename)
|
||||
if filename_at_source_location not in candidates:
|
||||
candidates.append(filename_at_source_location+".ill")
|
||||
candidates.append(filename_at_source_location)
|
||||
filename_at_libs_location = os.path.join(os.path.split(__file__)[0], "lib", modulename)
|
||||
if filename_at_libs_location not in candidates:
|
||||
candidates.append(filename_at_libs_location+".ill")
|
||||
candidates.append(filename_at_libs_location)
|
||||
for filename in candidates:
|
||||
if os.path.isfile(filename):
|
||||
return filename
|
||||
return None
|
||||
|
||||
def handle_parse_error(self, exc: ParseError) -> None:
|
||||
self.parse_errors += 1
|
||||
out = sys.stdout
|
||||
if out.isatty():
|
||||
print("\x1b[1m", file=out)
|
||||
if self.imported_module:
|
||||
print("Error (in imported file):", str(exc), file=out)
|
||||
else:
|
||||
print("Error:", str(exc), file=out)
|
||||
self.print_error_sourceline(exc.sourceref)
|
||||
if out.isatty():
|
||||
print("\x1b[0m", file=out, end="", flush=True)
|
||||
raise exc # XXX temporary to see where the error occurred
|
||||
|
||||
def print_error_sourceline(self, sref: SourceRef) -> None:
|
||||
if not sref:
|
||||
return
|
||||
sourcetext = linecache.getline(sref.file, sref.line).rstrip()
|
||||
if sourcetext:
|
||||
print(" " + sourcetext.expandtabs(8))
|
||||
if sref.column:
|
||||
print(' ' * (1+sref.column) + '^')
|
||||
|
||||
|
||||
class Zeropage:
|
||||
SCRATCH_B1 = 0x02
|
||||
SCRATCH_B2 = 0x03
|
||||
SCRATCH_W1 = 0xfb # $fb/$fc
|
||||
SCRATCH_W2 = 0xfd # $fd/$fe
|
||||
|
||||
def __init__(self, options: ZpOptions, enable_floats: bool) -> None:
|
||||
self.floats_enabled = enable_floats
|
||||
self.free = [] # type: List[int]
|
||||
self.allocations = {} # type: Dict[int, Tuple[str, DataType]]
|
||||
if options in (ZpOptions.CLOBBER_RESTORE, ZpOptions.CLOBBER):
|
||||
# clobber the zp, more free storage, yay!
|
||||
self.free = list(range(0x04, 0xfb)) + [0xff]
|
||||
for updated_by_irq in [0xa0, 0xa1, 0xa2, 0x91, 0xc0, 0xc5, 0xcb, 0xf5, 0xf6]:
|
||||
self.free.remove(updated_by_irq)
|
||||
else:
|
||||
# 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)
|
||||
self.free = [0x04, 0x05, 0x06, 0x2a, 0x52, 0xf7, 0xf8, 0xf9, 0xfa]
|
||||
assert self.SCRATCH_B1 not in self.free
|
||||
assert self.SCRATCH_B2 not in self.free
|
||||
assert self.SCRATCH_W1 not in self.free
|
||||
assert self.SCRATCH_W2 not in self.free
|
||||
|
||||
def allocate(self, vardef: VarDef) -> int:
|
||||
assert not vardef.name or vardef.name not in {a[0] for a in self.allocations.values()}, "var name is not unique"
|
||||
assert vardef.vartype == VarType.VAR, "can only allocate var"
|
||||
|
||||
def sequential_free(location: int) -> bool:
|
||||
return all(location + i in self.free for i in range(size))
|
||||
|
||||
def lone_byte(location: int) -> bool:
|
||||
return (location-1) not in self.free and (location+1) not in self.free and location in self.free
|
||||
|
||||
def make_allocation(location: int) -> int:
|
||||
for loc in range(location, location + size):
|
||||
self.free.remove(loc)
|
||||
self.allocations[location] = (vardef.name or "<unnamed>", vardef.datatype)
|
||||
return location
|
||||
|
||||
if vardef.datatype == DataType.BYTE:
|
||||
size = 1
|
||||
elif vardef.datatype == DataType.WORD:
|
||||
size = 2
|
||||
elif vardef.datatype == DataType.FLOAT:
|
||||
if not self.floats_enabled:
|
||||
raise TypeError("floating point numbers not enabled via option")
|
||||
print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref))
|
||||
size = 5
|
||||
elif vardef.datatype == DataType.BYTEARRAY:
|
||||
print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref))
|
||||
size = vardef.size[0]
|
||||
elif vardef.datatype == DataType.WORDARRAY:
|
||||
print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref))
|
||||
size = vardef.size[0] * 2
|
||||
elif vardef.datatype == DataType.MATRIX:
|
||||
print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref))
|
||||
size = vardef.size[0] * vardef.size[1]
|
||||
elif vardef.datatype.isstring():
|
||||
print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref))
|
||||
size = vardef.size[0]
|
||||
else:
|
||||
raise CompileError("cannot put datatype {:s} in ZP".format(vardef.datatype.name))
|
||||
if len(self.free) > 0:
|
||||
if size == 1:
|
||||
for candidate in range(min(self.free), max(self.free)+1):
|
||||
if lone_byte(candidate):
|
||||
return make_allocation(candidate)
|
||||
return make_allocation(self.free[0])
|
||||
for candidate in range(min(self.free), max(self.free)+1):
|
||||
if sequential_free(candidate):
|
||||
return make_allocation(candidate)
|
||||
raise CompileError("ERROR: no free space in ZP to allocate {:d} sequential bytes".format(size))
|
||||
|
||||
def available(self) -> int:
|
||||
return len(self.free)
|
@ -1,222 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
This is the part of the compiler/optimizer that simplifies expressions by doing
|
||||
'constant folding' - replacing expressions with constant, compile-time precomputed values.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
import sys
|
||||
from .plylex import SourceRef
|
||||
from .datatypes import VarType
|
||||
from .plyparse import *
|
||||
|
||||
|
||||
def handle_internal_error(exc: Exception, msg: str = "") -> None:
|
||||
out = sys.stdout
|
||||
if out.isatty():
|
||||
print("\x1b[1m", file=out)
|
||||
print("\nERROR: internal parser/optimizer error: ", exc, file=out)
|
||||
if msg:
|
||||
print(" Message:", msg, end="\n\n")
|
||||
if out.isatty():
|
||||
print("\x1b[0m", file=out, end="", flush=True)
|
||||
raise exc
|
||||
|
||||
|
||||
class ConstantFold:
|
||||
def __init__(self, mod: Module) -> None:
|
||||
self.num_warnings = 0
|
||||
self.module = mod
|
||||
self.optimizations_performed = False
|
||||
|
||||
def fold_constants(self, once: bool=False) -> None:
|
||||
self.num_warnings = 0
|
||||
if once:
|
||||
self._constant_folding()
|
||||
else:
|
||||
self.optimizations_performed = True
|
||||
# keep optimizing as long as there were changes made
|
||||
while self.optimizations_performed:
|
||||
self.optimizations_performed = False
|
||||
self._constant_folding()
|
||||
|
||||
def _constant_folding(self) -> None:
|
||||
for expression in self.module.all_nodes(Expression):
|
||||
if expression.parent is None or expression.parent.parent is None:
|
||||
# stale expression node (was part of an expression that was constant-folded away)
|
||||
continue
|
||||
if isinstance(expression, LiteralValue):
|
||||
continue
|
||||
try:
|
||||
evaluated = self._process_expression(expression) # type: ignore
|
||||
if evaluated is not expression:
|
||||
# replace the node with the newly evaluated result
|
||||
parent = expression.parent
|
||||
parent.replace_node(expression, evaluated)
|
||||
self.optimizations_performed = True
|
||||
except ParseError:
|
||||
raise
|
||||
except Exception as x:
|
||||
handle_internal_error(x, "process_expressions of node {}".format(expression))
|
||||
|
||||
def _process_expression(self, expr: Expression) -> Expression:
|
||||
# process/simplify all expressions (constant folding etc)
|
||||
if expr.is_lhs:
|
||||
if isinstance(expr, (Register, SymbolName, Dereference)):
|
||||
return expr
|
||||
raise ParseError("invalid lhs expression type", expr.sourceref)
|
||||
result = None # type: Expression
|
||||
if expr.is_compiletime_const():
|
||||
result = self._process_constant_expression(expr, expr.sourceref)
|
||||
else:
|
||||
result = self._process_dynamic_expression(expr, expr.sourceref)
|
||||
result.parent = expr.parent
|
||||
return result
|
||||
|
||||
def _process_constant_expression(self, expr: Expression, sourceref: SourceRef) -> LiteralValue:
|
||||
# the expression must result in a single (constant) value (int, float, whatever) wrapped as LiteralValue.
|
||||
if isinstance(expr, LiteralValue):
|
||||
return expr
|
||||
try:
|
||||
return LiteralValue(value=expr.const_value(), sourceref=sourceref) # type: ignore
|
||||
except NotCompiletimeConstantError:
|
||||
pass
|
||||
if isinstance(expr, SymbolName):
|
||||
value = check_symbol_definition(expr.name, expr.my_scope(), expr.sourceref)
|
||||
if isinstance(value, VarDef):
|
||||
if value.vartype == VarType.MEMORY:
|
||||
raise ExpressionEvaluationError("can't take a memory value, must be a constant", expr.sourceref)
|
||||
value = value.value
|
||||
if isinstance(value, ExpressionWithOperator):
|
||||
raise ExpressionEvaluationError("circular reference?", expr.sourceref)
|
||||
elif isinstance(value, LiteralValue):
|
||||
return value
|
||||
elif isinstance(value, (int, float, str, bool)):
|
||||
raise TypeError("symbol value node should not be a python primitive value", expr)
|
||||
else:
|
||||
raise ExpressionEvaluationError("constant symbol required, not {}".format(value.__class__.__name__), expr.sourceref)
|
||||
elif isinstance(expr, AddressOf):
|
||||
assert isinstance(expr.name, str)
|
||||
value = check_symbol_definition(expr.name, expr.my_scope(), expr.sourceref)
|
||||
if isinstance(value, VarDef):
|
||||
if value.vartype == VarType.MEMORY:
|
||||
if isinstance(value.value, LiteralValue):
|
||||
return value.value
|
||||
else:
|
||||
raise ExpressionEvaluationError("constant literal value required", value.sourceref)
|
||||
if value.vartype == VarType.CONST:
|
||||
raise ExpressionEvaluationError("can't take the address of a constant", expr.sourceref)
|
||||
raise ExpressionEvaluationError("address-of this {} isn't a compile-time constant"
|
||||
.format(value.__class__.__name__), expr.sourceref)
|
||||
else:
|
||||
raise ExpressionEvaluationError("constant address required, not {}"
|
||||
.format(value.__class__.__name__), expr.sourceref)
|
||||
elif isinstance(expr, SubCall):
|
||||
if isinstance(expr.target, SymbolName): # 'function(1,2,3)'
|
||||
funcname = expr.target.name
|
||||
if funcname in math_functions or funcname in builtin_functions:
|
||||
func_args = []
|
||||
for a in (self._process_constant_expression(callarg.value, sourceref) for callarg in list(expr.arguments.nodes)):
|
||||
if isinstance(a, LiteralValue):
|
||||
func_args.append(a.value)
|
||||
else:
|
||||
func_args.append(a)
|
||||
func = math_functions.get(funcname, builtin_functions.get(funcname))
|
||||
try:
|
||||
return LiteralValue(value=func(*func_args), sourceref=expr.arguments.sourceref) # type: ignore
|
||||
except Exception as x:
|
||||
raise ExpressionEvaluationError(str(x), expr.sourceref)
|
||||
else:
|
||||
raise ExpressionEvaluationError("can only use math- or builtin function", expr.sourceref)
|
||||
elif isinstance(expr.target, Dereference): # '[...](1,2,3)'
|
||||
raise ExpressionEvaluationError("dereferenced value call is not a constant value", expr.sourceref)
|
||||
elif isinstance(expr.target, LiteralValue) and type(expr.target.value) is int: # '64738()'
|
||||
raise ExpressionEvaluationError("immediate address call is not a constant value", expr.sourceref)
|
||||
else:
|
||||
raise NotImplementedError("weird call target", expr.target)
|
||||
elif isinstance(expr, ExpressionWithOperator):
|
||||
if expr.unary:
|
||||
left_sourceref = expr.left.sourceref if isinstance(expr.left, AstNode) else sourceref
|
||||
expr.left = self._process_constant_expression(expr.left, left_sourceref)
|
||||
expr.left.parent = expr
|
||||
if isinstance(expr.left, LiteralValue) and type(expr.left.value) in (int, float):
|
||||
try:
|
||||
if expr.operator == '-':
|
||||
return LiteralValue(value=-expr.left.value, sourceref=expr.left.sourceref) # type: ignore
|
||||
elif expr.operator == '~':
|
||||
return LiteralValue(value=~expr.left.value, sourceref=expr.left.sourceref) # type: ignore
|
||||
elif expr.operator in ("++", "--"):
|
||||
raise ValueError("incr/decr should not be an expression")
|
||||
raise ValueError("invalid unary operator", expr.operator)
|
||||
except TypeError as x:
|
||||
raise ParseError(str(x), expr.sourceref) from None
|
||||
raise ValueError("invalid operand type for unary operator", expr.left, expr.operator)
|
||||
else:
|
||||
left_sourceref = expr.left.sourceref if isinstance(expr.left, AstNode) else sourceref
|
||||
expr.left = self._process_constant_expression(expr.left, left_sourceref)
|
||||
expr.left.parent = expr
|
||||
right_sourceref = expr.right.sourceref if isinstance(expr.right, AstNode) else sourceref
|
||||
expr.right = self._process_constant_expression(expr.right, right_sourceref)
|
||||
expr.right.parent = expr
|
||||
if isinstance(expr.left, LiteralValue):
|
||||
if isinstance(expr.right, LiteralValue):
|
||||
return expr.evaluate_primitive_constants(expr.right.sourceref)
|
||||
else:
|
||||
raise ExpressionEvaluationError("constant literal value required on right, not {}"
|
||||
.format(expr.right.__class__.__name__), right_sourceref)
|
||||
else:
|
||||
raise ExpressionEvaluationError("constant literal value required on left, not {}"
|
||||
.format(expr.left.__class__.__name__), left_sourceref)
|
||||
else:
|
||||
raise ExpressionEvaluationError("constant value required, not {}".format(expr.__class__.__name__), expr.sourceref)
|
||||
|
||||
def _process_dynamic_expression(self, expr: Expression, sourceref: SourceRef) -> Expression:
|
||||
# constant-fold a dynamic expression
|
||||
if isinstance(expr, LiteralValue):
|
||||
return expr
|
||||
try:
|
||||
return LiteralValue(value=expr.const_value(), sourceref=sourceref) # type: ignore
|
||||
except NotCompiletimeConstantError:
|
||||
pass
|
||||
if isinstance(expr, SymbolName):
|
||||
try:
|
||||
return self._process_constant_expression(expr, sourceref)
|
||||
except (ExpressionEvaluationError, NotCompiletimeConstantError):
|
||||
return expr
|
||||
elif isinstance(expr, AddressOf):
|
||||
try:
|
||||
return self._process_constant_expression(expr, sourceref)
|
||||
except (ExpressionEvaluationError, NotCompiletimeConstantError):
|
||||
return expr
|
||||
elif isinstance(expr, SubCall):
|
||||
try:
|
||||
return self._process_constant_expression(expr, sourceref)
|
||||
except (ExpressionEvaluationError, NotCompiletimeConstantError):
|
||||
if isinstance(expr.target, SymbolName):
|
||||
check_symbol_definition(expr.target.name, expr.my_scope(), expr.target.sourceref)
|
||||
return expr
|
||||
elif isinstance(expr, (Register, Dereference)):
|
||||
return expr
|
||||
elif isinstance(expr, ExpressionWithOperator):
|
||||
if expr.unary:
|
||||
left_sourceref = expr.left.sourceref if isinstance(expr.left, AstNode) else sourceref
|
||||
expr.left = self._process_dynamic_expression(expr.left, left_sourceref)
|
||||
expr.left.parent = expr
|
||||
try:
|
||||
return self._process_constant_expression(expr, sourceref)
|
||||
except (ExpressionEvaluationError, NotCompiletimeConstantError):
|
||||
return expr
|
||||
else:
|
||||
left_sourceref = expr.left.sourceref if isinstance(expr.left, AstNode) else sourceref
|
||||
expr.left = self._process_dynamic_expression(expr.left, left_sourceref)
|
||||
expr.left.parent = expr
|
||||
right_sourceref = expr.right.sourceref if isinstance(expr.right, AstNode) else sourceref
|
||||
expr.right = self._process_dynamic_expression(expr.right, right_sourceref)
|
||||
expr.right.parent = expr
|
||||
try:
|
||||
return self._process_constant_expression(expr, sourceref)
|
||||
except (ExpressionEvaluationError, NotCompiletimeConstantError):
|
||||
return expr
|
||||
else:
|
||||
raise ParseError("expression required, not {}".format(expr.__class__.__name__), expr.sourceref)
|
@ -1,183 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
Here are the data type definitions and -conversions.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
import enum
|
||||
from functools import total_ordering
|
||||
|
||||
|
||||
@total_ordering
|
||||
class VarType(enum.Enum):
|
||||
CONST = 1
|
||||
MEMORY = 2
|
||||
VAR = 3
|
||||
|
||||
def __lt__(self, other):
|
||||
if self.__class__ == other.__class__:
|
||||
return self.value < other.value
|
||||
return NotImplemented
|
||||
|
||||
|
||||
@total_ordering
|
||||
class DataType(enum.Enum):
|
||||
"""The possible data types of values"""
|
||||
BYTE = 1
|
||||
WORD = 2
|
||||
FLOAT = 3
|
||||
BYTEARRAY = 4
|
||||
WORDARRAY = 5
|
||||
MATRIX = 6
|
||||
STRING = 7
|
||||
STRING_P = 8
|
||||
STRING_S = 9
|
||||
STRING_PS = 10
|
||||
|
||||
def __lt__(self, other):
|
||||
if self.__class__ == other.__class__:
|
||||
return self.value < other.value
|
||||
return NotImplemented
|
||||
|
||||
def isnumeric(self) -> bool:
|
||||
return self.value in (1, 2, 3)
|
||||
|
||||
def isinteger(self) -> bool:
|
||||
return self.value in (1, 2)
|
||||
|
||||
def isarray(self) -> bool:
|
||||
return self.value in (4, 5, 6)
|
||||
|
||||
def isstring(self) -> bool:
|
||||
return self.value in (7, 8, 9, 10)
|
||||
|
||||
|
||||
STRING_DATATYPES = {DataType.STRING, DataType.STRING_P, DataType.STRING_S, DataType.STRING_PS}
|
||||
|
||||
|
||||
REGISTER_SYMBOLS = {"A", "X", "Y", "AX", "AY", "XY", "SC", "SI"}
|
||||
REGISTER_SYMBOLS_RETURNVALUES = REGISTER_SYMBOLS | {"SZ"}
|
||||
REGISTER_BYTES = {"A", "X", "Y"}
|
||||
REGISTER_SBITS = {"SC", "SI", "SZ"}
|
||||
REGISTER_WORDS = {"AX", "AY", "XY"}
|
||||
|
||||
# 5-byte cbm MFLPT format limitations:
|
||||
FLOAT_MAX_POSITIVE = 1.7014118345e+38
|
||||
FLOAT_MAX_NEGATIVE = -1.7014118345e+38
|
||||
|
||||
|
||||
def char_to_bytevalue(character: str, petscii: bool=True) -> int:
|
||||
assert len(character) == 1
|
||||
if petscii:
|
||||
return ord(character.translate(ascii_to_petscii_trans))
|
||||
else:
|
||||
raise NotImplementedError("screencode conversion not yet implemented for chars")
|
||||
|
||||
|
||||
# ASCII/UNICODE-to-PETSCII translation table
|
||||
# Unicode symbols supported that map to a PETSCII character: £ ↑ ← ♠ ♥ ♦ ♣ π ● ○ and various others
|
||||
|
||||
# @todo cbmcodecs pypi package?
|
||||
ascii_to_petscii_trans = str.maketrans({
|
||||
'\f': 147, # form feed becomes ClearScreen "{clear}"
|
||||
'\n': 13, # line feed becomes a RETURN "{cr}" (not a line feed)
|
||||
'\r': 17, # CR becomes CursorDown "{down}"
|
||||
'a': 65,
|
||||
'b': 66,
|
||||
'c': 67,
|
||||
'd': 68,
|
||||
'e': 69,
|
||||
'f': 70,
|
||||
'g': 71,
|
||||
'h': 72,
|
||||
'i': 73,
|
||||
'j': 74,
|
||||
'k': 75,
|
||||
'l': 76,
|
||||
'm': 77,
|
||||
'n': 78,
|
||||
'o': 79,
|
||||
'p': 80,
|
||||
'q': 81,
|
||||
'r': 82,
|
||||
's': 83,
|
||||
't': 84,
|
||||
'u': 85,
|
||||
'v': 86,
|
||||
'w': 87,
|
||||
'x': 88,
|
||||
'y': 89,
|
||||
'z': 90,
|
||||
'A': 97,
|
||||
'B': 98,
|
||||
'C': 99,
|
||||
'D': 100,
|
||||
'E': 101,
|
||||
'F': 102,
|
||||
'G': 103,
|
||||
'H': 104,
|
||||
'I': 105,
|
||||
'J': 106,
|
||||
'K': 107,
|
||||
'L': 108,
|
||||
'M': 109,
|
||||
'N': 110,
|
||||
'O': 111,
|
||||
'P': 112,
|
||||
'Q': 113,
|
||||
'R': 114,
|
||||
'S': 115,
|
||||
'T': 116,
|
||||
'U': 117,
|
||||
'V': 118,
|
||||
'W': 119,
|
||||
'X': 120,
|
||||
'Y': 121,
|
||||
'Z': 122,
|
||||
'{': 179, # left squiggle
|
||||
'}': 235, # right squiggle
|
||||
'£': 92, # pound currency sign
|
||||
'^': 94, # up arrow
|
||||
'~': 126, # pi math symbol
|
||||
'π': 126, # pi symbol
|
||||
'`': 39, # single quote
|
||||
'✓': 250, # check mark
|
||||
|
||||
'|': 221, # vertical bar
|
||||
'│': 221, # vertical bar
|
||||
'─': 96, # horizontal bar
|
||||
'┼': 123, # vertical and horizontal bar
|
||||
|
||||
'↑': 94, # up arrow
|
||||
'←': 95, # left arrow
|
||||
|
||||
'▔': 163, # upper bar
|
||||
'_': 164, # lower bar (underscore)
|
||||
'▁': 164, # lower bar
|
||||
'▎': 165, # left bar
|
||||
|
||||
'♠': 97, # spades
|
||||
'●': 113, # circle
|
||||
'♥': 115, # hearts
|
||||
'○': 119, # open circle
|
||||
'♣': 120, # clubs
|
||||
'♦': 122, # diamonds
|
||||
|
||||
'├': 171, # vertical and right
|
||||
'┤': 179, # vertical and left
|
||||
'┴': 177, # horiz and up
|
||||
'┬': 178, # horiz and down
|
||||
'└': 173, # up right
|
||||
'┐': 174, # down left
|
||||
'┌': 175, # down right
|
||||
'┘': 189, # up left
|
||||
'▗': 172, # block lr
|
||||
'▖': 187, # block ll
|
||||
'▝': 188, # block ur
|
||||
'▘': 190, # block ul
|
||||
'▚': 191, # block ul and lr
|
||||
'▌': 161, # left half
|
||||
'▄': 162, # lower half
|
||||
'▒': 230, # raster
|
||||
})
|
@ -1,128 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
This is the main program that drives the rest.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
import time
|
||||
import os
|
||||
import re
|
||||
import argparse
|
||||
import subprocess
|
||||
from .compile import PlyParser
|
||||
from .optimize import optimize
|
||||
from .plylex import print_bold
|
||||
from .plyparse import ProgramFormat, Module
|
||||
|
||||
|
||||
class Assembler64Tass:
|
||||
def __init__(self, format: ProgramFormat) -> None:
|
||||
self.format = format
|
||||
|
||||
def assemble(self, inputfilename: str, outputfilename: str) -> None:
|
||||
args = ["64tass", "--ascii", "--case-sensitive", "-Wall", "-Wno-strict-bool",
|
||||
"--dump-labels", "--vice-labels", "-l", outputfilename+".vice-mon-list",
|
||||
"--no-monitor", "--output", outputfilename, inputfilename]
|
||||
if self.format in (ProgramFormat.PRG, ProgramFormat.BASIC):
|
||||
args.append("--cbm-prg")
|
||||
elif self.format == ProgramFormat.RAW:
|
||||
args.append("--nostart")
|
||||
else:
|
||||
raise ValueError("don't know how to create code format "+str(self.format))
|
||||
try:
|
||||
if self.format == ProgramFormat.PRG:
|
||||
print("\nCreating C-64 prg.")
|
||||
elif self.format == ProgramFormat.RAW:
|
||||
print("\nCreating raw binary.")
|
||||
try:
|
||||
subprocess.check_call(args)
|
||||
except FileNotFoundError as x:
|
||||
raise SystemExit("ERROR: cannot run assembler program: "+str(x))
|
||||
except subprocess.CalledProcessError as x:
|
||||
raise SystemExit("assembler failed with returncode " + str(x.returncode))
|
||||
|
||||
def generate_breakpoint_list(self, program_filename: str) -> str:
|
||||
breakpoints = []
|
||||
vice_mon_file = program_filename + ".vice-mon-list"
|
||||
with open(vice_mon_file, "rU") as f:
|
||||
for line in f:
|
||||
match = re.fullmatch(r"al (?P<address>\w+) \S+_il65_breakpoint_\d+.?", line, re.DOTALL)
|
||||
if match:
|
||||
breakpoints.append("$" + match.group("address"))
|
||||
with open(vice_mon_file, "at") as f:
|
||||
print("; vice monitor breakpoint list now follows", file=f)
|
||||
print("; {:d} breakpoints have been defined here".format(len(breakpoints)), file=f)
|
||||
print("del", file=f)
|
||||
for b in breakpoints:
|
||||
print("break", b, file=f)
|
||||
return vice_mon_file
|
||||
|
||||
|
||||
def main() -> None:
|
||||
description = "Compiler for IL65 language, code name 'Sick'"
|
||||
ap = argparse.ArgumentParser(description=description)
|
||||
ap.add_argument("-o", "--output", help="output directory")
|
||||
ap.add_argument("-c", "--codegenerator", choices=["6502", "tinyvm"], default="tinyvm", help="what code generator to use")
|
||||
ap.add_argument("-f", "--enablefloat", action="store_true", help="enable C64 (mflpt5) floating point operations")
|
||||
ap.add_argument("-no", "--nooptimize", action="store_true", help="do not optimize the parse tree")
|
||||
ap.add_argument("-sv", "--startvice", action="store_true", help="autostart vice x64 emulator after compilation")
|
||||
ap.add_argument("sourcefile", help="the source .ill/.il65 file to compile")
|
||||
args = ap.parse_args()
|
||||
assembly_filename = os.path.splitext(args.sourcefile)[0] + ".asm"
|
||||
program_filename = os.path.splitext(args.sourcefile)[0] + ".prg"
|
||||
if args.output:
|
||||
os.makedirs(args.output, mode=0o700, exist_ok=True)
|
||||
assembly_filename = os.path.join(args.output, os.path.split(assembly_filename)[1])
|
||||
program_filename = os.path.join(args.output, os.path.split(program_filename)[1])
|
||||
|
||||
print("\n" + description)
|
||||
|
||||
start = time.perf_counter()
|
||||
print("\nParsing program source code.")
|
||||
parser = PlyParser(enable_floats=args.enablefloat)
|
||||
parsed_module = parser.parse_file(args.sourcefile)
|
||||
if parsed_module:
|
||||
if args.nooptimize:
|
||||
print_bold("Optimizations disabled!")
|
||||
else:
|
||||
print("\nOptimizing code.")
|
||||
optimize(parsed_module)
|
||||
if args.codegenerator == "tinyvm":
|
||||
generate_tinyvm_code(parsed_module, args.enablefloat, assembly_filename, start)
|
||||
else:
|
||||
generate_6502_code(parsed_module, args.enablefloat, args.startvice, program_filename, assembly_filename, start)
|
||||
|
||||
|
||||
def generate_tinyvm_code(module: Module, float_enabled: bool, assembly_filename: str, compilationstart: float) -> None:
|
||||
print("\nGenerating tinyvm code.")
|
||||
from il65.codegen.tinyvm.generate import AssemblyGenerator
|
||||
cg = AssemblyGenerator(module, float_enabled)
|
||||
cg.generate(assembly_filename)
|
||||
duration_total = time.perf_counter() - compilationstart
|
||||
print("Compile duration: {:.2f} seconds".format(duration_total))
|
||||
print()
|
||||
|
||||
|
||||
def generate_6502_code(module: Module, float_enabled: bool, startvice: bool,
|
||||
program_filename: str, assembly_filename: str, compilationstart: float) -> None:
|
||||
print("\nGenerating 6502 assembly code.")
|
||||
from il65.codegen.mos6502.generate import AssemblyGenerator
|
||||
cg = AssemblyGenerator(module, float_enabled)
|
||||
cg.generate(assembly_filename)
|
||||
assembler = Assembler64Tass(module.format)
|
||||
assembler.assemble(assembly_filename, program_filename)
|
||||
mon_command_file = assembler.generate_breakpoint_list(program_filename)
|
||||
duration_total = time.perf_counter() - compilationstart
|
||||
print("Compile duration: {:.2f} seconds".format(duration_total))
|
||||
size = os.path.getsize(program_filename)
|
||||
print("Output size: {:d} bytes".format(size))
|
||||
print_bold("Output file: " + program_filename)
|
||||
print()
|
||||
if startvice:
|
||||
print("Autostart vice emulator...")
|
||||
# "-remotemonitor"
|
||||
cmdline = ["x64", "-moncommands", mon_command_file,
|
||||
"-autostartprgmode", "1", "-autostart-warp", "-autostart", program_filename]
|
||||
with open(os.devnull, "wb") as shutup:
|
||||
subprocess.call(cmdline, stdout=shutup)
|
@ -1,724 +0,0 @@
|
||||
# old deprecated code, in the process of moving this to the new emit/... modules
|
||||
|
||||
|
||||
class CodeGenerator:
|
||||
def _generate_goto_conditional_truthvalue(self, stmt: CallStmt) -> None:
|
||||
# the condition is just the 'truth value' of the single value,
|
||||
# this is translated into assembly by comparing the argument to zero.
|
||||
def branch_emitter_mmap(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
|
||||
assert is_goto and not stmt.condition.comparison_op
|
||||
assert stmt.condition.lvalue and not stmt.condition.rvalue
|
||||
assert not target_indirect
|
||||
assert stmt.condition.ifstatus in ("true", "not", "zero")
|
||||
branch, inverse_branch = ("bne", "beq") if stmt.condition.ifstatus == "true" else ("beq", "bne")
|
||||
cv = stmt.condition.lvalue
|
||||
assert isinstance(cv, MemMappedValue)
|
||||
cv_str = cv.name or Parser.to_hex(cv.address)
|
||||
if cv.datatype == DataType.BYTE:
|
||||
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
|
||||
self.p("\t\tlda " + cv_str)
|
||||
self.p("\t\t{:s} {:s}".format(branch, targetstr))
|
||||
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
|
||||
elif cv.datatype == DataType.WORD:
|
||||
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
|
||||
self.p("\t\tlda " + cv_str)
|
||||
if stmt.condition.ifstatus == "true":
|
||||
self.p("\t\t{:s} {:s}".format(branch, targetstr))
|
||||
self.p("\t\tlda {:s}+1".format(cv_str))
|
||||
self.p("\t\t{:s} {:s}".format(branch, targetstr))
|
||||
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
|
||||
else:
|
||||
self.p("\t\t{:s} +".format(inverse_branch, targetstr))
|
||||
self.p("\t\tlda {:s}+1".format(cv_str))
|
||||
self.p("\t\t{:s} {:s}".format(branch, targetstr))
|
||||
self.p("+\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
|
||||
else:
|
||||
raise CodeError("conditions cannot yet use other types than byte or word", # @todo comparisons of other types
|
||||
cv.datatype, str(cv), stmt.sourceref)
|
||||
|
||||
def branch_emitter_reg(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
|
||||
assert is_goto and not stmt.condition.comparison_op
|
||||
assert stmt.condition.lvalue and not stmt.condition.rvalue
|
||||
assert not target_indirect
|
||||
assert stmt.condition.ifstatus in ("true", "not", "zero")
|
||||
branch, inverse_branch = ("bne", "beq") if stmt.condition.ifstatus == "true" else ("beq", "bne")
|
||||
line_after_branch = ""
|
||||
cv = stmt.condition.lvalue
|
||||
assert isinstance(cv, RegisterValue)
|
||||
if cv.register == 'A':
|
||||
self.p("\t\tcmp #0")
|
||||
elif cv.register == 'X':
|
||||
self.p("\t\tcpx #0")
|
||||
elif cv.register == 'Y':
|
||||
self.p("\t\tcpy #0")
|
||||
else:
|
||||
if cv.register == 'AX':
|
||||
line_after_branch = "+"
|
||||
self.p("\t\tcmp #0")
|
||||
self.p("\t\t{:s} {:s}".format(inverse_branch, line_after_branch))
|
||||
self.p("\t\tcpx #0")
|
||||
elif cv.register == 'AY':
|
||||
line_after_branch = "+"
|
||||
self.p("\t\tcmp #0")
|
||||
self.p("\t\t{:s} {:s}".format(inverse_branch, line_after_branch))
|
||||
self.p("\t\tcpy #0")
|
||||
elif cv.register == 'XY':
|
||||
line_after_branch = "+"
|
||||
self.p("\t\tcpx #0")
|
||||
self.p("\t\t{:s} {:s}".format(inverse_branch, line_after_branch))
|
||||
self.p("\t\tcpy #0")
|
||||
else:
|
||||
raise CodeError("invalid register", cv.register)
|
||||
self.p("\t\t{:s} {:s}".format(branch, targetstr))
|
||||
if line_after_branch:
|
||||
self.p(line_after_branch)
|
||||
|
||||
def branch_emitter_indirect_cond(targetstr: str, is_goto: bool, target_indirect: bool) -> None:
|
||||
assert is_goto and not stmt.condition.comparison_op
|
||||
assert stmt.condition.lvalue and not stmt.condition.rvalue
|
||||
assert stmt.condition.ifstatus in ("true", "not", "zero")
|
||||
assert not target_indirect
|
||||
cv = stmt.condition.lvalue.value # type: ignore
|
||||
if isinstance(cv, RegisterValue):
|
||||
branch = "bne" if stmt.condition.ifstatus == "true" else "beq"
|
||||
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
|
||||
if cv.register == 'Y':
|
||||
self.p("\t\tlda ($00),y")
|
||||
elif cv.register == 'X':
|
||||
self.p("\t\tstx *+2\t; self-modify")
|
||||
self.p("\t\tlda $ff")
|
||||
elif cv.register == 'A':
|
||||
self.p("\t\tsta *+2\t; self-modify")
|
||||
self.p("\t\tlda $ff")
|
||||
else:
|
||||
self.p("\t\tst{:s} (+)+1\t; self-modify".format(cv.register[0].lower()))
|
||||
self.p("\t\tst{:s} (+)+2\t; self-modify".format(cv.register[1].lower()))
|
||||
self.p("+\t\tlda $ffff")
|
||||
self.p("\t\t{:s} {:s}".format(branch, targetstr))
|
||||
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
|
||||
elif isinstance(cv, MemMappedValue):
|
||||
raise CodeError("memmapped indirect should not occur, use the variable without indirection")
|
||||
elif isinstance(cv, IntegerValue) and cv.constant:
|
||||
branch, inverse_branch = ("bne", "beq") if stmt.condition.ifstatus == "true" else ("beq", "bne")
|
||||
cv_str = cv.name or Parser.to_hex(cv.value)
|
||||
if cv.datatype == DataType.BYTE:
|
||||
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
|
||||
self.p("\t\tlda " + cv_str)
|
||||
self.p("\t\t{:s} {:s}".format(branch, targetstr))
|
||||
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
|
||||
elif cv.datatype == DataType.WORD:
|
||||
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
|
||||
self.p("\t\tlda " + cv_str)
|
||||
if stmt.condition.ifstatus == "true":
|
||||
self.p("\t\t{:s} {:s}".format(branch, targetstr))
|
||||
self.p("\t\tlda {:s}+1".format(cv_str))
|
||||
self.p("\t\t{:s} {:s}".format(branch, targetstr))
|
||||
self.p("\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
|
||||
else:
|
||||
self.p("\t\t{:s} +".format(inverse_branch, targetstr))
|
||||
self.p("\t\tlda {:s}+1".format(cv_str))
|
||||
self.p("\t\t{:s} {:s}".format(branch, targetstr))
|
||||
self.p("+\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1)) # restore A
|
||||
else:
|
||||
raise CodeError("conditions cannot yet use other types than byte or word", # @todo comparisons of other types
|
||||
cv.datatype, str(cv), stmt.sourceref)
|
||||
else:
|
||||
raise CodeError("weird indirect type", str(cv))
|
||||
|
||||
cv = stmt.condition.lvalue
|
||||
if isinstance(cv, RegisterValue):
|
||||
self._generate_call_or_goto(stmt, branch_emitter_reg)
|
||||
elif isinstance(cv, MemMappedValue):
|
||||
self._generate_call_or_goto(stmt, branch_emitter_mmap)
|
||||
elif isinstance(cv, IndirectValue):
|
||||
if isinstance(cv.value, RegisterValue):
|
||||
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
|
||||
elif isinstance(cv.value, MemMappedValue):
|
||||
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
|
||||
elif isinstance(cv.value, IntegerValue) and cv.value.constant:
|
||||
self._generate_call_or_goto(stmt, branch_emitter_indirect_cond)
|
||||
else:
|
||||
raise CodeError("weird indirect type", str(cv))
|
||||
else:
|
||||
raise CodeError("need register, memmapped or indirect value", str(cv))
|
||||
|
||||
def _generate_goto_conditional_comparison(self, stmt: CallStmt) -> None:
|
||||
# the condition is lvalue operator rvalue
|
||||
raise NotImplementedError("no comparisons yet") # XXX comparisons
|
||||
assert stmt.condition.ifstatus in ("true", "not", "zero")
|
||||
assert stmt.condition.lvalue != stmt.condition.rvalue # so we know we actually have to compare different things
|
||||
lv, compare_operator, rv = stmt.condition.lvalue, stmt.condition.comparison_op, stmt.condition.rvalue
|
||||
if lv.constant and not rv.constant:
|
||||
# if lv is a constant, swap the whole thing around so the constant is on the right
|
||||
lv, compare_operator, rv = stmt.condition.swap()
|
||||
if isinstance(rv, RegisterValue):
|
||||
# if rv is a register, make sure it comes first instead
|
||||
lv, compare_operator, rv = stmt.condition.swap()
|
||||
if lv.datatype != DataType.BYTE or rv.datatype != DataType.BYTE:
|
||||
raise CodeError("can only generate comparison code for byte values for now") # @todo compare non-bytes
|
||||
if isinstance(lv, RegisterValue):
|
||||
if isinstance(rv, RegisterValue):
|
||||
self.p("\t\tst{:s} {:s}".format(rv.register.lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
|
||||
if lv.register == "A":
|
||||
self.p("\t\tcmp " + Parser.to_hex(Zeropage.SCRATCH_B1))
|
||||
elif lv.register == "X":
|
||||
self.p("\t\tcpx " + Parser.to_hex(Zeropage.SCRATCH_B1))
|
||||
elif lv.register == "Y":
|
||||
self.p("\t\tcpy " + Parser.to_hex(Zeropage.SCRATCH_B1))
|
||||
else:
|
||||
raise CodeError("wrong lvalue register")
|
||||
elif isinstance(rv, IntegerValue):
|
||||
rvstr = rv.name or Parser.to_hex(rv.value)
|
||||
if lv.register == "A":
|
||||
self.p("\t\tcmp #" + rvstr)
|
||||
elif lv.register == "X":
|
||||
self.p("\t\tcpx #" + rvstr)
|
||||
elif lv.register == "Y":
|
||||
self.p("\t\tcpy #" + rvstr)
|
||||
else:
|
||||
raise CodeError("wrong lvalue register")
|
||||
elif isinstance(rv, MemMappedValue):
|
||||
rvstr = rv.name or Parser.to_hex(rv.address)
|
||||
if lv.register == "A":
|
||||
self.p("\t\tcmp " + rvstr)
|
||||
elif lv.register == "X":
|
||||
self.p("\t\tcpx #" + rvstr)
|
||||
elif lv.register == "Y":
|
||||
self.p("\t\tcpy #" + rvstr)
|
||||
else:
|
||||
raise CodeError("wrong lvalue register")
|
||||
else:
|
||||
raise CodeError("invalid rvalue type in comparison", rv)
|
||||
elif isinstance(lv, MemMappedValue):
|
||||
assert not isinstance(rv, RegisterValue), "registers as rvalue should have been swapped with lvalue"
|
||||
if isinstance(rv, IntegerValue):
|
||||
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
|
||||
self.p("\t\tlda " + (lv.name or Parser.to_hex(lv.address)))
|
||||
self.p("\t\tcmp #" + (rv.name or Parser.to_hex(rv.value)))
|
||||
line_after_goto = "\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
|
||||
elif isinstance(rv, MemMappedValue):
|
||||
rvstr = rv.name or Parser.to_hex(rv.address)
|
||||
self.p("\t\tsta " + Parser.to_hex(Zeropage.SCRATCH_B1)) # need to save A, because the goto may not be taken
|
||||
self.p("\t\tlda " + (lv.name or Parser.to_hex(lv.address)))
|
||||
self.p("\t\tcmp " + rvstr)
|
||||
line_after_goto = "\t\tlda " + Parser.to_hex(Zeropage.SCRATCH_B1) # restore A
|
||||
else:
|
||||
raise CodeError("invalid rvalue type in comparison", rv)
|
||||
else:
|
||||
raise CodeError("invalid lvalue type in comparison", lv)
|
||||
|
||||
def _generate_call_or_goto(self, stmt: CallStmt, branch_emitter: Callable[[str, bool, bool], None]) -> None:
|
||||
def generate_param_assignments() -> None:
|
||||
for assign_stmt in stmt.desugared_call_arguments:
|
||||
self.generate_assignment(assign_stmt)
|
||||
|
||||
def generate_result_assignments() -> None:
|
||||
for assign_stmt in stmt.desugared_output_assignments:
|
||||
self.generate_assignment(assign_stmt)
|
||||
|
||||
def params_load_a() -> bool:
|
||||
for assign_stmt in stmt.desugared_call_arguments:
|
||||
for lv in assign_stmt.leftvalues:
|
||||
if isinstance(lv, RegisterValue):
|
||||
if lv.register == 'A':
|
||||
return True
|
||||
return False
|
||||
|
||||
def unclobber_result_registers(registers: Set[str], output_assignments: List[AssignmentStmt]) -> Set[str]:
|
||||
result = registers.copy()
|
||||
for a in output_assignments:
|
||||
for lv in a.leftvalues:
|
||||
if isinstance(lv, RegisterValue):
|
||||
if len(lv.register) == 1:
|
||||
result.discard(lv.register)
|
||||
else:
|
||||
for r in lv.register:
|
||||
result.discard(r)
|
||||
return result
|
||||
|
||||
if stmt.target.name:
|
||||
symblock, targetdef = self.cur_block.lookup(stmt.target.name)
|
||||
else:
|
||||
symblock = None
|
||||
targetdef = None
|
||||
if isinstance(targetdef, SubroutineDef):
|
||||
if isinstance(stmt.target, MemMappedValue):
|
||||
targetstr = stmt.target.name or Parser.to_hex(stmt.address)
|
||||
else:
|
||||
raise CodeError("call sub target must be mmapped")
|
||||
if stmt.is_goto:
|
||||
generate_param_assignments()
|
||||
branch_emitter(targetstr, True, False)
|
||||
# no result assignments because it's a goto
|
||||
return
|
||||
clobbered = set() # type: Set[str]
|
||||
if targetdef.clobbered_registers:
|
||||
if stmt.preserve_regs is not None:
|
||||
clobbered = targetdef.clobbered_registers & stmt.preserve_regs
|
||||
clobbered = unclobber_result_registers(clobbered, stmt.desugared_output_assignments)
|
||||
with self.preserving_registers(clobbered, loads_a_within=params_load_a(), always_preserve=stmt.preserve_regs is not None):
|
||||
generate_param_assignments()
|
||||
branch_emitter(targetstr, False, False)
|
||||
generate_result_assignments()
|
||||
return
|
||||
if isinstance(stmt.target, IndirectValue):
|
||||
if stmt.target.name:
|
||||
targetstr = stmt.target.name
|
||||
elif stmt.address is not None:
|
||||
targetstr = Parser.to_hex(stmt.address)
|
||||
elif stmt.target.value.name:
|
||||
targetstr = stmt.target.value.name
|
||||
elif isinstance(stmt.target.value, RegisterValue):
|
||||
targetstr = stmt.target.value.register
|
||||
elif isinstance(stmt.target.value, IntegerValue):
|
||||
targetstr = stmt.target.value.name or Parser.to_hex(stmt.target.value.value)
|
||||
else:
|
||||
raise CodeError("missing name", stmt.target.value)
|
||||
if stmt.is_goto:
|
||||
# no need to preserve registers for a goto
|
||||
generate_param_assignments()
|
||||
if targetstr in REGISTER_WORDS:
|
||||
self.p("\t\tst{:s} {:s}".format(targetstr[0].lower(), Parser.to_hex(Zeropage.SCRATCH_B1)))
|
||||
self.p("\t\tst{:s} {:s}".format(targetstr[1].lower(), Parser.to_hex(Zeropage.SCRATCH_B2)))
|
||||
branch_emitter(Parser.to_hex(Zeropage.SCRATCH_B1), True, True)
|
||||
else:
|
||||
branch_emitter(targetstr, True, True)
|
||||
# no result assignments because it's a goto
|
||||
else:
|
||||
# indirect call to subroutine
|
||||
preserve_regs = unclobber_result_registers(stmt.preserve_regs or set(), stmt.desugared_output_assignments)
|
||||
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a(),
|
||||
always_preserve=stmt.preserve_regs is not None):
|
||||
generate_param_assignments()
|
||||
if targetstr in REGISTER_WORDS:
|
||||
print("warning: {}: indirect register pair call is quite inefficient, use a jump table in memory instead?"
|
||||
.format(stmt.sourceref))
|
||||
if stmt.preserve_regs is not None:
|
||||
# cannot use zp scratch because it may be used by the register backup. This is very inefficient code!
|
||||
self.p("\t\tjsr il65_lib.jsr_indirect_nozpuse_"+targetstr)
|
||||
|
||||
else:
|
||||
self.p("\t\tjsr il65_lib.jsr_indirect_"+targetstr)
|
||||
else:
|
||||
self.p("\t\tjsr +")
|
||||
self.p("\t\tjmp ++")
|
||||
self.p("+\t\tjmp ({:s})".format(targetstr))
|
||||
self.p("+")
|
||||
generate_result_assignments()
|
||||
else:
|
||||
# call to a label or immediate address
|
||||
if stmt.target.name:
|
||||
targetstr = stmt.target.name
|
||||
elif stmt.address is not None:
|
||||
targetstr = Parser.to_hex(stmt.address)
|
||||
elif isinstance(stmt.target, IntegerValue):
|
||||
targetstr = stmt.target.name or Parser.to_hex(stmt.target.value)
|
||||
else:
|
||||
raise CodeError("missing name", stmt.target)
|
||||
if stmt.is_goto:
|
||||
# no need to preserve registers for a goto
|
||||
generate_param_assignments()
|
||||
branch_emitter(targetstr, True, False)
|
||||
# no result assignments because it's a goto
|
||||
else:
|
||||
preserve_regs = unclobber_result_registers(stmt.preserve_regs or set(), stmt.desugared_output_assignments)
|
||||
with self.preserving_registers(preserve_regs, loads_a_within=params_load_a(),
|
||||
always_preserve=stmt.preserve_regs is not None):
|
||||
generate_param_assignments()
|
||||
branch_emitter(targetstr, False, False)
|
||||
generate_result_assignments()
|
||||
|
||||
def generate_assignment(self, stmt: AssignmentStmt) -> None:
|
||||
def unwrap_indirect(iv: IndirectValue) -> MemMappedValue:
|
||||
if isinstance(iv.value, MemMappedValue):
|
||||
return iv.value
|
||||
elif iv.value.constant and isinstance(iv.value, IntegerValue):
|
||||
return MemMappedValue(iv.value.value, iv.datatype, 1, stmt.sourceref, iv.name)
|
||||
else:
|
||||
raise CodeError("cannot yet generate code for assignment: non-constant and non-memmapped indirect") # XXX
|
||||
|
||||
rvalue = stmt.right
|
||||
if isinstance(rvalue, IndirectValue):
|
||||
rvalue = unwrap_indirect(rvalue)
|
||||
self.p("\t\t\t\t\t; " + stmt.lineref)
|
||||
if isinstance(rvalue, IntegerValue):
|
||||
for lv in stmt.leftvalues:
|
||||
if isinstance(lv, RegisterValue):
|
||||
self.generate_assign_integer_to_reg(lv.register, rvalue)
|
||||
elif isinstance(lv, MemMappedValue):
|
||||
self.generate_assign_integer_to_mem(lv, rvalue)
|
||||
elif isinstance(lv, IndirectValue):
|
||||
lv = unwrap_indirect(lv)
|
||||
self.generate_assign_integer_to_mem(lv, rvalue)
|
||||
else:
|
||||
raise CodeError("invalid assignment target (1)", str(stmt))
|
||||
elif isinstance(rvalue, RegisterValue):
|
||||
for lv in stmt.leftvalues:
|
||||
if isinstance(lv, RegisterValue):
|
||||
self.generate_assign_reg_to_reg(lv, rvalue.register)
|
||||
elif isinstance(lv, MemMappedValue):
|
||||
self.generate_assign_reg_to_memory(lv, rvalue.register)
|
||||
elif isinstance(lv, IndirectValue):
|
||||
lv = unwrap_indirect(lv)
|
||||
self.generate_assign_reg_to_memory(lv, rvalue.register)
|
||||
else:
|
||||
raise CodeError("invalid assignment target (2)", str(stmt))
|
||||
elif isinstance(rvalue, StringValue):
|
||||
r_str = self.output_string(rvalue.value, True)
|
||||
for lv in stmt.leftvalues:
|
||||
if isinstance(lv, RegisterValue):
|
||||
if len(rvalue.value) == 1:
|
||||
self.generate_assign_char_to_reg(lv, r_str)
|
||||
else:
|
||||
self.generate_assign_string_to_reg(lv, rvalue)
|
||||
elif isinstance(lv, MemMappedValue):
|
||||
if len(rvalue.value) == 1:
|
||||
self.generate_assign_char_to_memory(lv, r_str)
|
||||
else:
|
||||
self.generate_assign_string_to_memory(lv, rvalue)
|
||||
elif isinstance(lv, IndirectValue):
|
||||
lv = unwrap_indirect(lv)
|
||||
if len(rvalue.value) == 1:
|
||||
self.generate_assign_char_to_memory(lv, r_str)
|
||||
else:
|
||||
self.generate_assign_string_to_memory(lv, rvalue)
|
||||
else:
|
||||
raise CodeError("invalid assignment target (2)", str(stmt))
|
||||
elif isinstance(rvalue, MemMappedValue):
|
||||
for lv in stmt.leftvalues:
|
||||
if isinstance(lv, RegisterValue):
|
||||
self.generate_assign_mem_to_reg(lv.register, rvalue)
|
||||
elif isinstance(lv, MemMappedValue):
|
||||
self.generate_assign_mem_to_mem(lv, rvalue)
|
||||
elif isinstance(lv, IndirectValue):
|
||||
lv = unwrap_indirect(lv)
|
||||
self.generate_assign_mem_to_mem(lv, rvalue)
|
||||
else:
|
||||
raise CodeError("invalid assignment target (4)", str(stmt))
|
||||
elif isinstance(rvalue, FloatValue):
|
||||
for lv in stmt.leftvalues:
|
||||
if isinstance(lv, MemMappedValue) and lv.datatype == DataType.FLOAT:
|
||||
self.generate_assign_float_to_mem(lv, rvalue)
|
||||
elif isinstance(lv, IndirectValue):
|
||||
lv = unwrap_indirect(lv)
|
||||
assert lv.datatype == DataType.FLOAT
|
||||
self.generate_assign_float_to_mem(lv, rvalue)
|
||||
else:
|
||||
raise CodeError("cannot assign float to ", str(lv))
|
||||
else:
|
||||
raise CodeError("invalid assignment value type", str(stmt))
|
||||
|
||||
def generate_assign_float_to_mem(self, mmv: MemMappedValue,
|
||||
rvalue: Union[FloatValue, IntegerValue]) -> None:
|
||||
floatvalue = float(rvalue.value)
|
||||
mflpt = self.to_mflpt5(floatvalue)
|
||||
target = mmv.name or Parser.to_hex(mmv.address)
|
||||
with self.preserving_registers({'A'}):
|
||||
self.p("\t\t\t\t\t; {:s} = {}".format(target, rvalue.name or floatvalue))
|
||||
a_reg_value = None
|
||||
for i, byte in enumerate(mflpt):
|
||||
if byte != a_reg_value:
|
||||
self.p("\t\tlda #${:02x}".format(byte))
|
||||
a_reg_value = byte
|
||||
self.p("\t\tsta {:s}+{:d}".format(target, i))
|
||||
|
||||
def generate_assign_reg_to_memory(self, lv: MemMappedValue, r_register: str) -> None:
|
||||
# Memory = Register
|
||||
lv_string = lv.name or Parser.to_hex(lv.address)
|
||||
if lv.datatype == DataType.BYTE:
|
||||
if len(r_register) > 1:
|
||||
raise CodeError("cannot assign register pair to single byte memory")
|
||||
self.p("\t\tst{:s} {}".format(r_register.lower(), lv_string))
|
||||
elif lv.datatype == DataType.WORD:
|
||||
if len(r_register) == 1:
|
||||
self.p("\t\tst{:s} {}".format(r_register.lower(), lv_string)) # lsb
|
||||
with self.preserving_registers({'A'}, loads_a_within=True):
|
||||
self.p("\t\tlda #0")
|
||||
self.p("\t\tsta {:s}+1".format(lv_string)) # msb
|
||||
else:
|
||||
self.p("\t\tst{:s} {}".format(r_register[0].lower(), lv_string))
|
||||
self.p("\t\tst{:s} {}+1".format(r_register[1].lower(), lv_string))
|
||||
elif lv.datatype == DataType.FLOAT:
|
||||
# assigning a register to a float requires c64 ROM routines
|
||||
if r_register in REGISTER_WORDS:
|
||||
def do_rom_calls():
|
||||
self.p("\t\tjsr c64flt.GIVUAYF") # uword AY -> fac1
|
||||
self.p("\t\tldx #<" + lv_string)
|
||||
self.p("\t\tldy #>" + lv_string)
|
||||
self.p("\t\tjsr c64.FTOMEMXY") # fac1 -> memory XY
|
||||
if r_register == "AY":
|
||||
with self.preserving_registers({'A', 'X', 'Y'}):
|
||||
do_rom_calls()
|
||||
elif r_register == "AX":
|
||||
with self.preserving_registers({'A', 'X', 'Y'}):
|
||||
self.p("\t\tpha\n\t\ttxa\n\t\ttay\n\t\tpla") # X->Y (so we have AY now)
|
||||
do_rom_calls()
|
||||
else: # XY
|
||||
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
|
||||
self.p("\t\ttxa") # X->A (so we have AY now)
|
||||
do_rom_calls()
|
||||
elif r_register in "AXY":
|
||||
|
||||
def do_rom_calls():
|
||||
self.p("\t\tjsr c64.FREADUY") # ubyte Y -> fac1
|
||||
self.p("\t\tldx #<" + lv_string)
|
||||
self.p("\t\tldy #>" + lv_string)
|
||||
self.p("\t\tjsr c64.FTOMEMXY") # fac1 -> memory XY
|
||||
|
||||
if r_register == "A":
|
||||
with self.preserving_registers({'A', 'X', 'Y'}):
|
||||
self.p("\t\ttay")
|
||||
do_rom_calls()
|
||||
elif r_register == "X":
|
||||
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
|
||||
self.p("\t\ttxa")
|
||||
self.p("\t\ttay")
|
||||
do_rom_calls()
|
||||
elif r_register == "Y":
|
||||
with self.preserving_registers({'A', 'X', 'Y'}):
|
||||
do_rom_calls()
|
||||
else:
|
||||
raise CodeError("invalid register to assign to float", r_register)
|
||||
else:
|
||||
raise CodeError("invalid lvalue type", lv.datatype)
|
||||
|
||||
def generate_assign_reg_to_reg(self, lv: RegisterValue, r_register: str) -> None:
|
||||
if lv.register != r_register:
|
||||
if lv.register == 'A': # x/y -> a
|
||||
self.p("\t\tt{:s}a".format(r_register.lower()))
|
||||
elif lv.register == 'Y':
|
||||
if r_register == 'A':
|
||||
# a -> y
|
||||
self.p("\t\ttay")
|
||||
else:
|
||||
# x -> y, 6502 doesn't have txy
|
||||
self.p("\t\tstx ${0:02x}\n\t\tldy ${0:02x}".format(Zeropage.SCRATCH_B1))
|
||||
elif lv.register == 'X':
|
||||
if r_register == 'A':
|
||||
# a -> x
|
||||
self.p("\t\ttax")
|
||||
else:
|
||||
# y -> x, 6502 doesn't have tyx
|
||||
self.p("\t\tsty ${0:02x}\n\t\tldx ${0:02x}".format(Zeropage.SCRATCH_B1))
|
||||
elif lv.register in REGISTER_WORDS:
|
||||
if len(r_register) == 1:
|
||||
# assign one register to a pair, so the hi byte is zero.
|
||||
if lv.register == "AX" and r_register == "A":
|
||||
self.p("\t\tldx #0")
|
||||
elif lv.register == "AX" and r_register == "X":
|
||||
self.p("\t\ttxa\n\t\tldx #0")
|
||||
elif lv.register == "AX" and r_register == "Y":
|
||||
self.p("\t\ttya\n\t\tldx #0")
|
||||
elif lv.register == "AY" and r_register == "A":
|
||||
self.p("\t\tldy #0")
|
||||
elif lv.register == "AY" and r_register == "X":
|
||||
self.p("\t\ttxa\n\t\tldy #0")
|
||||
elif lv.register == "AY" and r_register == "Y":
|
||||
self.p("\t\ttya\n\t\tldy #0")
|
||||
elif lv.register == "XY" and r_register == "A":
|
||||
self.p("\t\ttax\n\t\tldy #0")
|
||||
elif lv.register == "XY" and r_register == "X":
|
||||
self.p("\t\tldy #0")
|
||||
elif lv.register == "XY" and r_register == "Y":
|
||||
self.p("\t\ttyx\n\t\tldy #0")
|
||||
else:
|
||||
raise CodeError("invalid register combination", lv.register, r_register)
|
||||
elif lv.register == "AX" and r_register == "AY":
|
||||
# y -> x, 6502 doesn't have tyx
|
||||
self.p("\t\tsty ${0:02x}\n\t\tldx ${0:02x}".format(Zeropage.SCRATCH_B1))
|
||||
elif lv.register == "AX" and r_register == "XY":
|
||||
# x -> a, y -> x, 6502 doesn't have tyx
|
||||
self.p("\t\ttxa")
|
||||
self.p("\t\tsty ${0:02x}\n\t\tldx ${0:02x}".format(Zeropage.SCRATCH_B1))
|
||||
elif lv.register == "AY" and r_register == "AX":
|
||||
# x -> y, 6502 doesn't have txy
|
||||
self.p("\t\tstx ${0:02x}\n\t\tldy ${0:02x}".format(Zeropage.SCRATCH_B1))
|
||||
elif lv.register == "AY" and r_register == "XY":
|
||||
# x -> a
|
||||
self.p("\t\ttxa")
|
||||
elif lv.register == "XY" and r_register == "AX":
|
||||
# x -> y, a -> x, 6502 doesn't have txy
|
||||
self.p("\t\tstx ${0:02x}\n\t\tldy ${0:02x}".format(Zeropage.SCRATCH_B1))
|
||||
self.p("\t\ttax")
|
||||
elif lv.register == "XY" and r_register == "AY":
|
||||
# a -> x
|
||||
self.p("\t\ttax")
|
||||
else:
|
||||
raise CodeError("invalid register combination", lv.register, r_register)
|
||||
else:
|
||||
raise CodeError("invalid register " + lv.register)
|
||||
|
||||
def generate_assign_integer_to_mem(self, lv: MemMappedValue, rvalue: IntegerValue) -> None:
|
||||
if lv.name:
|
||||
symblock, sym = self.cur_block.lookup(lv.name)
|
||||
if not isinstance(sym, VariableDef):
|
||||
raise CodeError("invalid lvalue type " + str(sym))
|
||||
assign_target = symblock.label + '.' + sym.name if symblock is not self.cur_block else lv.name
|
||||
lvdatatype = sym.type
|
||||
else:
|
||||
assign_target = Parser.to_hex(lv.address)
|
||||
lvdatatype = lv.datatype
|
||||
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.value)
|
||||
if lvdatatype == DataType.BYTE:
|
||||
if rvalue.value is not None and not lv.assignable_from(rvalue) or rvalue.datatype != DataType.BYTE:
|
||||
raise OverflowError("value doesn't fit in a byte")
|
||||
with self.preserving_registers({'A'}, loads_a_within=True):
|
||||
self.p("\t\tlda #" + r_str)
|
||||
self.p("\t\tsta " + assign_target)
|
||||
elif lvdatatype == DataType.WORD:
|
||||
if rvalue.value is not None and not lv.assignable_from(rvalue):
|
||||
raise OverflowError("value doesn't fit in a word")
|
||||
with self.preserving_registers({'A'}, loads_a_within=True):
|
||||
self.p("\t\tlda #<" + r_str)
|
||||
self.p("\t\tsta " + assign_target)
|
||||
self.p("\t\tlda #>" + r_str)
|
||||
self.p("\t\tsta {}+1".format(assign_target))
|
||||
elif lvdatatype == DataType.FLOAT:
|
||||
if rvalue.value is not None and not DataType.FLOAT.assignable_from_value(rvalue.value):
|
||||
raise CodeError("value cannot be assigned to a float")
|
||||
self.generate_assign_float_to_mem(lv, rvalue)
|
||||
else:
|
||||
raise CodeError("invalid lvalue type " + str(lvdatatype))
|
||||
|
||||
def generate_assign_mem_to_reg(self, l_register: str, rvalue: MemMappedValue) -> None:
|
||||
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.address)
|
||||
if len(l_register) == 1:
|
||||
if rvalue.datatype != DataType.BYTE:
|
||||
raise CodeError("can only assign a byte to a register")
|
||||
self.p("\t\tld{:s} {:s}".format(l_register.lower(), r_str))
|
||||
else:
|
||||
if rvalue.datatype == DataType.BYTE:
|
||||
self.p("\t\tld{:s} {:s}".format(l_register[0].lower(), r_str))
|
||||
self.p("\t\tld{:s} #0".format(l_register[1].lower()))
|
||||
elif rvalue.datatype == DataType.WORD:
|
||||
self.p("\t\tld{:s} {:s}".format(l_register[0].lower(), r_str))
|
||||
self.p("\t\tld{:s} {:s}+1".format(l_register[1].lower(), r_str))
|
||||
else:
|
||||
raise CodeError("can only assign a byte or word to a register pair")
|
||||
|
||||
def generate_assign_mem_to_mem(self, lv: MemMappedValue, rvalue: MemMappedValue) -> None:
|
||||
r_str = rvalue.name or Parser.to_hex(rvalue.address)
|
||||
l_str = lv.name or Parser.to_hex(lv.address)
|
||||
if lv.datatype == DataType.BYTE:
|
||||
if rvalue.datatype != DataType.BYTE:
|
||||
raise CodeError("can only assign a byte to a byte", str(rvalue))
|
||||
with self.preserving_registers({'A'}, loads_a_within=True):
|
||||
self.p("\t\tlda " + r_str)
|
||||
self.p("\t\tsta " + l_str)
|
||||
elif lv.datatype == DataType.WORD:
|
||||
if rvalue.datatype == DataType.BYTE:
|
||||
with self.preserving_registers({'A'}, loads_a_within=True):
|
||||
self.p("\t\tlda " + r_str)
|
||||
self.p("\t\tsta " + l_str)
|
||||
self.p("\t\tlda #0")
|
||||
self.p("\t\tsta {:s}+1".format(l_str))
|
||||
elif rvalue.datatype == DataType.WORD:
|
||||
with self.preserving_registers({'A'}, loads_a_within=True):
|
||||
self.p("\t\tlda {:s}".format(r_str))
|
||||
self.p("\t\tsta {:s}".format(l_str))
|
||||
self.p("\t\tlda {:s}+1".format(r_str))
|
||||
self.p("\t\tsta {:s}+1".format(l_str))
|
||||
else:
|
||||
raise CodeError("can only assign a byte or word to a word", str(rvalue))
|
||||
elif lv.datatype == DataType.FLOAT:
|
||||
if rvalue.datatype == DataType.FLOAT:
|
||||
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
|
||||
self.p("\t\tlda #<" + r_str)
|
||||
self.p("\t\tsta c64.SCRATCH_ZPWORD1")
|
||||
self.p("\t\tlda #>" + r_str)
|
||||
self.p("\t\tsta c64.SCRATCH_ZPWORD1+1")
|
||||
self.p("\t\tldx #<" + l_str)
|
||||
self.p("\t\tldy #>" + l_str)
|
||||
self.p("\t\tjsr c64flt.copy_mflt")
|
||||
elif rvalue.datatype == DataType.BYTE:
|
||||
with self.preserving_registers({'A', 'X', 'Y'}):
|
||||
self.p("\t\tldy " + r_str)
|
||||
self.p("\t\tjsr c64.FREADUY") # ubyte Y -> fac1
|
||||
self.p("\t\tldx #<" + l_str)
|
||||
self.p("\t\tldy #>" + l_str)
|
||||
self.p("\t\tjsr c64.FTOMEMXY") # fac1 -> memory XY
|
||||
elif rvalue.datatype == DataType.WORD:
|
||||
with self.preserving_registers({'A', 'X', 'Y'}, loads_a_within=True):
|
||||
self.p("\t\tlda " + r_str)
|
||||
self.p("\t\tldy {:s}+1".format(r_str))
|
||||
self.p("\t\tjsr c64flt.GIVUAYF") # uword AY -> fac1
|
||||
self.p("\t\tldx #<" + l_str)
|
||||
self.p("\t\tldy #>" + l_str)
|
||||
self.p("\t\tjsr c64.FTOMEMXY") # fac1 -> memory XY
|
||||
else:
|
||||
raise CodeError("unsupported rvalue to memfloat", str(rvalue))
|
||||
else:
|
||||
raise CodeError("invalid lvalue memmapped datatype", str(lv))
|
||||
|
||||
def generate_assign_char_to_memory(self, lv: MemMappedValue, char_str: str) -> None:
|
||||
# Memory = Character
|
||||
with self.preserving_registers({'A'}, loads_a_within=True):
|
||||
self.p("\t\tlda #" + char_str)
|
||||
if not lv.name:
|
||||
self.p("\t\tsta " + Parser.to_hex(lv.address))
|
||||
return
|
||||
# assign char value to a memory location by symbol name
|
||||
symblock, sym = self.cur_block.lookup(lv.name)
|
||||
if isinstance(sym, VariableDef):
|
||||
assign_target = lv.name
|
||||
if symblock is not self.cur_block:
|
||||
assign_target = symblock.label + '.' + sym.name
|
||||
if sym.type == DataType.BYTE:
|
||||
self.p("\t\tsta " + assign_target)
|
||||
elif sym.type == DataType.WORD:
|
||||
self.p("\t\tsta " + assign_target)
|
||||
self.p("\t\tlda #0")
|
||||
self.p("\t\tsta {}+1".format(assign_target))
|
||||
else:
|
||||
raise CodeError("invalid lvalue type " + str(sym))
|
||||
else:
|
||||
raise CodeError("invalid lvalue type " + str(sym))
|
||||
|
||||
def generate_assign_integer_to_reg(self, l_register: str, rvalue: IntegerValue) -> None:
|
||||
r_str = rvalue.name if rvalue.name else "${:x}".format(rvalue.value)
|
||||
if l_register in ('A', 'X', 'Y'):
|
||||
self.p("\t\tld{:s} #{:s}".format(l_register.lower(), r_str))
|
||||
elif l_register in REGISTER_WORDS:
|
||||
self.p("\t\tld{:s} #<{:s}".format(l_register[0].lower(), r_str))
|
||||
self.p("\t\tld{:s} #>{:s}".format(l_register[1].lower(), r_str))
|
||||
elif l_register == "SC":
|
||||
# set/clear S carry bit
|
||||
if rvalue.value:
|
||||
self.p("\t\tsec")
|
||||
else:
|
||||
self.p("\t\tclc")
|
||||
elif l_register == "SI":
|
||||
# interrupt disable bit
|
||||
if rvalue.value:
|
||||
self.p("\t\tsei")
|
||||
else:
|
||||
self.p("\t\tcli")
|
||||
else:
|
||||
raise CodeError("invalid register in immediate integer assignment", l_register, rvalue.value)
|
||||
|
||||
def generate_assign_char_to_reg(self, lv: RegisterValue, char_str: str) -> None:
|
||||
# Register = Char (string of length 1)
|
||||
if lv.register not in ('A', 'X', 'Y'):
|
||||
raise CodeError("invalid register for char assignment", lv.register)
|
||||
self.p("\t\tld{:s} #{:s}".format(lv.register.lower(), char_str))
|
||||
|
||||
def generate_assign_string_to_reg(self, lv: RegisterValue, rvalue: StringValue) -> None:
|
||||
if lv.register not in ("AX", "AY", "XY"):
|
||||
raise CodeError("need register pair AX, AY or XY for string address assignment", lv.register)
|
||||
if rvalue.name:
|
||||
self.p("\t\tld{:s} #<{:s}".format(lv.register[0].lower(), rvalue.name))
|
||||
self.p("\t\tld{:s} #>{:s}".format(lv.register[1].lower(), rvalue.name))
|
||||
else:
|
||||
raise CodeError("cannot assign immediate string, it must be a string variable")
|
||||
|
||||
def generate_assign_string_to_memory(self, lv: MemMappedValue, rvalue: StringValue) -> None:
|
||||
if lv.datatype != DataType.WORD:
|
||||
raise CodeError("need word memory type for string address assignment")
|
||||
if rvalue.name:
|
||||
assign_target = lv.name if lv.name else Parser.to_hex(lv.address)
|
||||
self.p("\t\tlda #<{:s}".format(rvalue.name))
|
||||
self.p("\t\tsta " + assign_target)
|
||||
self.p("\t\tlda #>{:s}".format(rvalue.name))
|
||||
self.p("\t\tsta {}+1".format(assign_target))
|
||||
else:
|
||||
raise CodeError("cannot assign immediate string, it must be a string variable")
|
@ -1,326 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
This is the optimizer that applies various optimizations to the parse tree,
|
||||
eliminates statements that have no effect, optimizes calculations etc.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
|
||||
from typing import List, no_type_check, Union
|
||||
from .datatypes import DataType, VarType
|
||||
from .plyparse import *
|
||||
from .plylex import print_warning, print_bold
|
||||
from .constantfold import ConstantFold
|
||||
|
||||
|
||||
class Optimizer:
|
||||
def __init__(self, mod: Module) -> None:
|
||||
self.num_warnings = 0
|
||||
self.module = mod
|
||||
self.optimizations_performed = False
|
||||
self.constant_folder = ConstantFold(self.module)
|
||||
|
||||
def optimize(self) -> None:
|
||||
self.num_warnings = 0
|
||||
self.optimizations_performed = True
|
||||
# keep optimizing as long as there were changes made
|
||||
while self.optimizations_performed:
|
||||
self.optimizations_performed = False
|
||||
self._optimize()
|
||||
# remaining optimizations that have to be done just once:
|
||||
self.remove_unused_subroutines()
|
||||
self.remove_empty_blocks()
|
||||
|
||||
def _optimize(self) -> None:
|
||||
self.constant_folder.fold_constants(True) # perform constant folding and simple expression optimization
|
||||
# @todo expression optimization: reduce expression nesting / flattening of parenthesis
|
||||
# @todo expression optimization: simplify logical expression when a term makes it always true or false
|
||||
# @todo expression optimization: optimize some simple multiplications into shifts (A*=8 -> A<<3)
|
||||
# @todo expression optimization: create augmented assignment from assignment that only refers to its lvalue (A=A+10, A=4*A, ...)
|
||||
self.optimize_assignments()
|
||||
self.remove_superfluous_assignments()
|
||||
# @todo optimize addition with self into shift 1 (A+=A -> A<<=1)
|
||||
self.optimize_goto_compare_with_zero()
|
||||
self.join_incrdecrs()
|
||||
# @todo remove gotos with conditions that are always false
|
||||
# @todo remove loops with conditions that are always empty/false
|
||||
# @todo analyse for unreachable code and remove that (f.i. code after goto or return that has no label so can never be jumped to)
|
||||
|
||||
def join_incrdecrs(self) -> None:
|
||||
def combine(incrdecrs: List[IncrDecr], scope: Scope) -> None:
|
||||
# combine the separate incrdecrs
|
||||
replaced = False
|
||||
total = 0
|
||||
for i in incrdecrs:
|
||||
if i.operator == "++":
|
||||
total += i.howmuch
|
||||
else:
|
||||
total -= i.howmuch
|
||||
if total == 0:
|
||||
replaced = True
|
||||
for x in incrdecrs:
|
||||
scope.remove_node(x)
|
||||
else:
|
||||
is_float = False
|
||||
if isinstance(target, SymbolName):
|
||||
symdef = target.my_scope().lookup(target.name)
|
||||
is_float = isinstance(symdef, VarDef) and symdef.datatype == DataType.FLOAT
|
||||
elif isinstance(target, Dereference):
|
||||
is_float = target.datatype == DataType.FLOAT
|
||||
if is_float or -255 <= total <= 255:
|
||||
replaced = True
|
||||
for x in incrdecrs[1:]:
|
||||
scope.remove_node(x)
|
||||
incrdecr = self._make_incrdecr(incrdecrs[0], target, abs(total), "++" if total >= 0 else "--")
|
||||
scope.replace_node(incrdecrs[0], incrdecr)
|
||||
else:
|
||||
# total is > 255 or < -255, make an augmented assignment out of it instead of an incrdecr
|
||||
aug_assign = AugAssignment(operator="-=" if total < 0 else "+=", sourceref=incrdecrs[0].sourceref) # type: ignore
|
||||
left = incrdecrs[0].target
|
||||
right = LiteralValue(value=abs(total), sourceref=incrdecrs[0].sourceref) # type: ignore
|
||||
left.parent = aug_assign
|
||||
right.parent = aug_assign
|
||||
aug_assign.nodes.append(left)
|
||||
aug_assign.nodes.append(right)
|
||||
aug_assign.mark_lhs()
|
||||
replaced = True
|
||||
for x in incrdecrs[1:]:
|
||||
scope.remove_node(x)
|
||||
scope.replace_node(incrdecrs[0], aug_assign)
|
||||
if replaced:
|
||||
self.optimizations_performed = True
|
||||
self.num_warnings += 1
|
||||
print_warning("{}: merged a sequence of incr/decrs or augmented assignments".format(incrdecrs[0].sourceref))
|
||||
|
||||
for scope in self.module.all_nodes(Scope):
|
||||
target = None
|
||||
incrdecrs = [] # type: List[IncrDecr]
|
||||
for node in list(scope.nodes):
|
||||
if isinstance(node, IncrDecr):
|
||||
if target is None:
|
||||
target = node.target
|
||||
incrdecrs.append(node)
|
||||
continue
|
||||
if self._same_target(target, node.target):
|
||||
incrdecrs.append(node)
|
||||
continue
|
||||
if len(incrdecrs) > 1:
|
||||
combine(incrdecrs, scope) # type: ignore
|
||||
incrdecrs.clear()
|
||||
target = None
|
||||
if isinstance(node, IncrDecr):
|
||||
# it was an incrdecr with a different target than what we had gathered so far.
|
||||
if target is None:
|
||||
target = node.target
|
||||
incrdecrs.append(node)
|
||||
if len(incrdecrs) > 1:
|
||||
# combine remaining incrdecrs at the bottom of the block
|
||||
combine(incrdecrs, scope) # type: ignore
|
||||
|
||||
def _same_target(self, node1: Union[Register, SymbolName, Dereference],
|
||||
node2: Union[Register, SymbolName, Dereference]) -> bool:
|
||||
if isinstance(node1, Register) and isinstance(node2, Register) and node1.name == node2.name:
|
||||
return True
|
||||
if isinstance(node1, SymbolName) and isinstance(node2, SymbolName) and node1.name == node2.name:
|
||||
return True
|
||||
if isinstance(node1, Dereference) and isinstance(node2, Dereference):
|
||||
if type(node1.operand) is not type(node2.operand):
|
||||
return False
|
||||
if isinstance(node1.operand, (SymbolName, LiteralValue, Register)):
|
||||
return node1.operand == node2.operand
|
||||
if not isinstance(node1, AstNode) or not isinstance(node2, AstNode):
|
||||
raise TypeError("same_target called with invalid type(s)", node1, node2)
|
||||
return False
|
||||
|
||||
def remove_superfluous_assignments(self) -> None:
|
||||
# remove consecutive assignment statements to the same target, only keep the last value (only if its a constant!)
|
||||
# this is NOT done for memory mapped variables because these often represent a volatile register of some sort!
|
||||
for scope in self.module.all_nodes(Scope):
|
||||
prev_node = None # type: AstNode
|
||||
for node in list(scope.nodes):
|
||||
if isinstance(node, Assignment) and isinstance(prev_node, Assignment):
|
||||
if isinstance(node.right, (LiteralValue, Register)) and self._same_target(node.left, prev_node.left):
|
||||
if isinstance(node.left, SymbolName):
|
||||
# only optimize if the symbol is not a memory mapped address (volatile memory!)
|
||||
symdef = node.left.my_scope().lookup(node.left.name)
|
||||
if isinstance(symdef, VarDef) and symdef.vartype == VarType.MEMORY:
|
||||
continue
|
||||
scope.remove_node(prev_node)
|
||||
self.optimizations_performed = True
|
||||
self.num_warnings += 1
|
||||
print_warning("{}: removed superfluous assignment".format(prev_node.sourceref))
|
||||
prev_node = node
|
||||
|
||||
@no_type_check
|
||||
def optimize_assignments(self) -> None:
|
||||
# remove assignment statements that do nothing (A=A)
|
||||
# remove augmented assignments that have no effect (x+=0, x-=0, x/=1, x//=1, x*=1)
|
||||
# convert augmented assignments to simple incr/decr if value allows it (A+=10 => incr A by 10)
|
||||
# simplify some calculations (x*=0, x**=0) to simple constant value assignment
|
||||
# @todo remove or simplify logical aug assigns like A |= 0, A |= true, A |= false (or perhaps turn them into byte values first?)
|
||||
for assignment in self.module.all_nodes():
|
||||
if isinstance(assignment, Assignment):
|
||||
if self._same_target(assignment.left, assignment.right):
|
||||
assignment.my_scope().remove_node(assignment)
|
||||
self.optimizations_performed = True
|
||||
self.num_warnings += 1
|
||||
print_warning("{}: removed statement that has no effect (left=right)".format(assignment.sourceref))
|
||||
elif isinstance(assignment, AugAssignment):
|
||||
if isinstance(assignment.right, LiteralValue) and isinstance(assignment.right.value, (int, float)):
|
||||
if assignment.right.value == 0:
|
||||
if assignment.operator in ("+=", "-=", "|=", "<<=", ">>=", "^="):
|
||||
self.num_warnings += 1
|
||||
print_warning("{}: removed statement that has no effect (aug.assign zero)".format(assignment.sourceref))
|
||||
assignment.my_scope().remove_node(assignment)
|
||||
self.optimizations_performed = True
|
||||
elif assignment.operator == "*=":
|
||||
self.num_warnings += 1
|
||||
print_warning("{}: statement replaced by = 0".format(assignment.sourceref))
|
||||
new_assignment = self._make_new_assignment(assignment, 0)
|
||||
assignment.my_scope().replace_node(assignment, new_assignment)
|
||||
self.optimizations_performed = True
|
||||
elif assignment.operator == "**=":
|
||||
self.num_warnings += 1
|
||||
print_warning("{}: statement replaced by = 1".format(assignment.sourceref))
|
||||
new_assignment = self._make_new_assignment(assignment, 1)
|
||||
assignment.my_scope().replace_node(assignment, new_assignment)
|
||||
self.optimizations_performed = True
|
||||
elif assignment.right.value >= 8 and assignment.operator in ("<<=", ">>="):
|
||||
print("{}: shifting result is always zero".format(assignment.sourceref))
|
||||
new_stmt = Assignment(sourceref=assignment.sourceref)
|
||||
new_stmt.nodes.append(assignment.left)
|
||||
new_stmt.nodes.append(LiteralValue(value=0, sourceref=assignment.sourceref))
|
||||
assignment.my_scope().replace_node(assignment, new_stmt)
|
||||
assignment.mark_lhs()
|
||||
self.optimizations_performed = True
|
||||
elif assignment.operator in ("+=", "-=") and 0 < assignment.right.value < 256:
|
||||
howmuch = assignment.right
|
||||
if howmuch.value not in (0, 1):
|
||||
_, howmuch = coerce_constant_value(datatype_of(assignment.left, assignment.my_scope()),
|
||||
howmuch, assignment.sourceref)
|
||||
new_stmt = IncrDecr(operator="++" if assignment.operator == "+=" else "--",
|
||||
howmuch=howmuch.value, sourceref=assignment.sourceref)
|
||||
new_stmt.target = assignment.left
|
||||
new_stmt.target.parent = new_stmt
|
||||
assignment.my_scope().replace_node(assignment, new_stmt)
|
||||
self.optimizations_performed = True
|
||||
elif assignment.right.value == 1 and assignment.operator in ("/=", "//=", "*="):
|
||||
self.num_warnings += 1
|
||||
print_warning("{}: removed statement that has no effect (aug.assign identity)".format(assignment.sourceref))
|
||||
assignment.my_scope().remove_node(assignment)
|
||||
self.optimizations_performed = True
|
||||
|
||||
@no_type_check
|
||||
def _make_new_assignment(self, old_aug_assignment: AugAssignment, constantvalue: int) -> Assignment:
|
||||
new_assignment = Assignment(sourceref=old_aug_assignment.sourceref)
|
||||
new_assignment.parent = old_aug_assignment.parent
|
||||
left = old_aug_assignment.left
|
||||
left.parent = new_assignment
|
||||
new_assignment.nodes.append(left)
|
||||
value = LiteralValue(value=constantvalue, sourceref=old_aug_assignment.sourceref)
|
||||
value.parent = new_assignment
|
||||
new_assignment.nodes.append(value)
|
||||
new_assignment.mark_lhs()
|
||||
return new_assignment
|
||||
|
||||
@no_type_check
|
||||
def _make_aug_assign(self, old_assign: Assignment, target: Union[Register, SymbolName, Dereference],
|
||||
value: Union[int, float], operator: str) -> AugAssignment:
|
||||
assert isinstance(target, (Register, SymbolName, Dereference))
|
||||
a = AugAssignment(operator=operator, sourceref=old_assign.sourceref)
|
||||
a.nodes.append(target)
|
||||
target.parent = a
|
||||
lv = LiteralValue(value=value, sourceref=old_assign.sourceref)
|
||||
a.nodes.append(lv)
|
||||
lv.parent = a
|
||||
a.parent = old_assign.parent
|
||||
a.mark_lhs()
|
||||
return a
|
||||
|
||||
@no_type_check
|
||||
def _make_incrdecr(self, old_stmt: AstNode, target: Union[Register, SymbolName, Dereference],
|
||||
howmuch: Union[int, float], operator: str) -> IncrDecr:
|
||||
assert isinstance(target, (Register, SymbolName, Dereference))
|
||||
a = IncrDecr(operator=operator, howmuch=howmuch, sourceref=old_stmt.sourceref)
|
||||
a.nodes.append(target)
|
||||
target.parent = a
|
||||
a.parent = old_stmt.parent
|
||||
return a
|
||||
|
||||
@no_type_check
|
||||
def remove_unused_subroutines(self) -> None:
|
||||
# some symbols are used by the emitted assembly code from the code generator,
|
||||
# and should never be removed or the assembler will fail
|
||||
never_remove = {"c64.FREADUY", "c64.FTOMEMXY", "c64.FADD", "c64.FSUB",
|
||||
"c64flt.GIVUAYF", "c64flt.copy_mflt", "c64flt.float_add_one", "c64flt.float_sub_one",
|
||||
"c64flt.float_add_SW1_to_XY", "c64flt.float_sub_SW1_from_XY"}
|
||||
num_discarded = 0
|
||||
for sub in self.module.all_nodes(Subroutine):
|
||||
usages = self.module.subroutine_usage[(sub.parent.name, sub.name)]
|
||||
if not usages and sub.parent.name + '.' + sub.name not in never_remove:
|
||||
sub.parent.remove_node(sub)
|
||||
num_discarded += 1
|
||||
# if num_discarded:
|
||||
# print("discarded {:d} unused subroutines".format(num_discarded))
|
||||
|
||||
@no_type_check
|
||||
def optimize_goto_compare_with_zero(self) -> None:
|
||||
# a conditional goto that compares a value with zero will be simplified
|
||||
# the comparison operator and rvalue (0) will be removed and the if-status changed accordingly
|
||||
for goto in self.module.all_nodes(Goto):
|
||||
if isinstance(goto.condition, Expression):
|
||||
pass # @todo optimize goto conditionals
|
||||
# if cond and isinstance(cond.rvalue, (int, float)) and cond.rvalue.value == 0:
|
||||
# simplified = False
|
||||
# if cond.ifstatus in ("true", "ne"):
|
||||
# if cond.comparison_op == "==":
|
||||
# # if_true something == 0 -> if_not something
|
||||
# cond.ifstatus = "not"
|
||||
# cond.comparison_op, cond.rvalue = "", None
|
||||
# simplified = True
|
||||
# elif cond.comparison_op == "!=":
|
||||
# # if_true something != 0 -> if_true something
|
||||
# cond.comparison_op, cond.rvalue = "", None
|
||||
# simplified = True
|
||||
# elif cond.ifstatus in ("not", "eq"):
|
||||
# if cond.comparison_op == "==":
|
||||
# # if_not something == 0 -> if_true something
|
||||
# cond.ifstatus = "true"
|
||||
# cond.comparison_op, cond.rvalue = "", None
|
||||
# simplified = True
|
||||
# elif cond.comparison_op == "!=":
|
||||
# # if_not something != 0 -> if_not something
|
||||
# cond.comparison_op, cond.rvalue = "", None
|
||||
# simplified = True
|
||||
# if simplified:
|
||||
# print("{}: simplified comparison with zero".format(stmt.sourceref))
|
||||
|
||||
def remove_empty_blocks(self) -> None:
|
||||
# remove blocks without name and without address, or that are empty
|
||||
for node in self.module.all_nodes():
|
||||
if isinstance(node, (Subroutine, Block)):
|
||||
if not node.scope:
|
||||
continue
|
||||
if all(isinstance(n, Directive) for n in node.scope.nodes):
|
||||
empty = True
|
||||
for n in node.scope.nodes:
|
||||
empty = empty and n.name not in {"asmbinary", "asminclude"}
|
||||
if empty:
|
||||
self.num_warnings += 1
|
||||
print_warning("ignoring empty block or subroutine", node.sourceref)
|
||||
assert isinstance(node.parent, (Block, Module))
|
||||
node.my_scope().nodes.remove(node)
|
||||
if isinstance(node, Block):
|
||||
if not node.name and node.address is None:
|
||||
self.num_warnings += 1
|
||||
print_warning("ignoring block without name and address", node.sourceref)
|
||||
assert isinstance(node.parent, Module)
|
||||
node.my_scope().nodes.remove(node)
|
||||
|
||||
|
||||
def optimize(mod: Module) -> None:
|
||||
opt = Optimizer(mod)
|
||||
opt.optimize()
|
||||
if opt.num_warnings:
|
||||
print_bold("There are {:d} optimization warnings.".format(opt.num_warnings))
|
@ -1,372 +0,0 @@
|
||||
"""
|
||||
Programming Language for 6502/6510 microprocessors, codename 'Sick'
|
||||
This is the lexer of the IL65 code, that generates a stream of tokens for the parser.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
import ast
|
||||
import sys
|
||||
import ply.lex
|
||||
import attr
|
||||
|
||||
|
||||
@attr.s(slots=True, frozen=True)
|
||||
class SourceRef:
|
||||
file = attr.ib(type=str)
|
||||
line = attr.ib(type=int)
|
||||
column = attr.ib(type=int, default=0)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.column:
|
||||
return "{:s}:{:d}:{:d}".format(self.file, self.line, self.column)
|
||||
if self.line:
|
||||
return "{:s}:{:d}".format(self.file, self.line)
|
||||
return self.file
|
||||
|
||||
|
||||
# token names
|
||||
|
||||
tokens = (
|
||||
"INTEGER",
|
||||
"FLOATINGPOINT",
|
||||
"DOTTEDNAME",
|
||||
"NAME",
|
||||
"IS",
|
||||
"CLOBBEREDREGISTER",
|
||||
"REGISTER",
|
||||
"COMMENT",
|
||||
"DIRECTIVE",
|
||||
"AUGASSIGN",
|
||||
"EQUALS",
|
||||
"NOTEQUALS",
|
||||
"RARROW",
|
||||
"RETURN",
|
||||
"VARTYPE",
|
||||
"SUB",
|
||||
"DATATYPE",
|
||||
"CHARACTER",
|
||||
"STRING",
|
||||
"BOOLEAN",
|
||||
"GOTO",
|
||||
"INCR",
|
||||
"DECR",
|
||||
"LT",
|
||||
"GT",
|
||||
"LE",
|
||||
"GE",
|
||||
"BITAND",
|
||||
"BITOR",
|
||||
"BITXOR",
|
||||
"BITINVERT",
|
||||
"SHIFTLEFT",
|
||||
"SHIFTRIGHT",
|
||||
"LOGICAND",
|
||||
"LOGICOR",
|
||||
"LOGICXOR",
|
||||
"LOGICNOT",
|
||||
"INTEGERDIVIDE",
|
||||
"MODULO",
|
||||
"POWER",
|
||||
"LABEL",
|
||||
"IF",
|
||||
"PRESERVEREGS",
|
||||
"INLINEASM",
|
||||
"ENDL"
|
||||
)
|
||||
|
||||
literals = ['+', '-', '*', '/', '(', ')', '[', ']', '{', '}', '.', ',', '!', '?', ':']
|
||||
|
||||
# regex rules for simple tokens
|
||||
|
||||
t_SHIFTLEFT = r"<<"
|
||||
t_SHIFTRIGHT = r">>"
|
||||
t_INTEGERDIVIDE = r"//"
|
||||
t_BITAND = r"&"
|
||||
t_BITOR = r"\|"
|
||||
t_BITXOR = r"\^"
|
||||
t_BITINVERT = r"~"
|
||||
t_IS = r"="
|
||||
t_AUGASSIGN = r"\+=|-=|/=|//=|\*=|\*\*=|<<=|>>=|&=|\|=|\^="
|
||||
t_DECR = r"--"
|
||||
t_INCR = r"\+\+"
|
||||
t_EQUALS = r"=="
|
||||
t_NOTEQUALS = r"!="
|
||||
t_LT = r"<"
|
||||
t_GT = r">"
|
||||
t_LE = r"<="
|
||||
t_GE = r">="
|
||||
t_IF = "if(_[a-z]+)?"
|
||||
t_RARROW = r"->"
|
||||
t_POWER = r"\*\*"
|
||||
|
||||
|
||||
# ignore inline whitespace
|
||||
t_ignore = " \t"
|
||||
t_inlineasm_ignore = " \t\r\n"
|
||||
|
||||
|
||||
# states for allowing %asm inclusion of raw assembly
|
||||
states = (
|
||||
('inlineasm', 'exclusive'),
|
||||
)
|
||||
|
||||
# reserved words
|
||||
reserved = {
|
||||
"sub": "SUB",
|
||||
"var": "VARTYPE",
|
||||
"memory": "VARTYPE",
|
||||
"const": "VARTYPE",
|
||||
"goto": "GOTO",
|
||||
"return": "RETURN",
|
||||
"true": "BOOLEAN",
|
||||
"false": "BOOLEAN",
|
||||
"not": "LOGICNOT",
|
||||
"and": "LOGICAND",
|
||||
"or": "LOGICOR",
|
||||
"xor": "LOGICXOR",
|
||||
"mod": "MODULO",
|
||||
"AX": "REGISTER",
|
||||
"AY": "REGISTER",
|
||||
"XY": "REGISTER",
|
||||
"SC": "REGISTER",
|
||||
"SI": "REGISTER",
|
||||
"SZ": "REGISTER",
|
||||
"A": "REGISTER",
|
||||
"X": "REGISTER",
|
||||
"Y": "REGISTER",
|
||||
"if": "IF",
|
||||
"if_true": "IF",
|
||||
"if_not": "IF",
|
||||
"if_zero": "IF",
|
||||
"if_ne": "IF",
|
||||
"if_eq": "IF",
|
||||
"if_cc": "IF",
|
||||
"if_cs": "IF",
|
||||
"if_vc": "IF",
|
||||
"if_vs": "IF",
|
||||
"if_ge": "IF",
|
||||
"if_le": "IF",
|
||||
"if_gt": "IF",
|
||||
"if_lt": "IF",
|
||||
"if_pos": "IF",
|
||||
"if_neg": "IF",
|
||||
}
|
||||
|
||||
|
||||
# rules for tokens with some actions
|
||||
|
||||
def t_inlineasm(t):
|
||||
r"""%asm\s*\{[^\S\n]*"""
|
||||
t.lexer.code_start = t.lexer.lexpos # Record start position
|
||||
t.lexer.level = 1 # initial brace level
|
||||
t.lexer.begin("inlineasm") # enter state 'inlineasm'
|
||||
|
||||
|
||||
def t_inlineasm_lbrace(t):
|
||||
r"""\{"""
|
||||
t.lexer.level += 1
|
||||
|
||||
|
||||
def t_inlineasm_rbrace(t):
|
||||
r"""\}"""
|
||||
t.lexer.level -= 1
|
||||
# if closing brace, return code fragment
|
||||
if t.lexer.level == 0:
|
||||
t.value = t.lexer.lexdata[t.lexer.code_start:t.lexer.lexpos-1]
|
||||
t.type = "INLINEASM"
|
||||
t.lexer.lineno += t.value.count("\n")
|
||||
t.lexer.begin("INITIAL") # back to normal lexing rules
|
||||
return t
|
||||
|
||||
|
||||
def t_inlineasm_comment(t):
|
||||
r""";[^\n]*"""
|
||||
pass
|
||||
|
||||
|
||||
def t_inlineasm_string(t):
|
||||
r"""(?x) # verbose mode
|
||||
(?<!\\) # not preceded by a backslash
|
||||
" # a literal double-quote
|
||||
.*? # 1-or-more characters
|
||||
(?<!\\) # not preceded by a backslash
|
||||
" # a literal double-quote
|
||||
|
|
||||
(?<!\\) # not preceded by a backslash
|
||||
' # a literal single quote
|
||||
.*? # 1-or-more characters
|
||||
(?<!\\) # not preceded by a backslash
|
||||
' # a literal double-quote
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def t_inlineasm_nonspace(t):
|
||||
r"""[^\s\{\}\'\"]+"""
|
||||
pass
|
||||
|
||||
|
||||
def t_inlineasm_error(t):
|
||||
# For bad characters, we just skip over it
|
||||
t.lexer.skip(1)
|
||||
|
||||
|
||||
def t_CLOBBEREDREGISTER(t):
|
||||
r"""(AX|AY|XY|A|X|Y)\?"""
|
||||
t.value = t.value[:-1]
|
||||
return t
|
||||
|
||||
|
||||
def t_DATATYPE(t):
|
||||
r"""\.byte|\.wordarray|\.float|\.array|\.word|\.strps|\.strs|\.strp|\.str|\.matrix"""
|
||||
t.value = t.value[1:]
|
||||
return t
|
||||
|
||||
|
||||
def t_LABEL(t):
|
||||
r"""[a-zA-Z_]\w*\s*:"""
|
||||
t.value = t.value[:-1].strip()
|
||||
return t
|
||||
|
||||
|
||||
def t_BOOLEAN(t):
|
||||
r"""true|false"""
|
||||
t.value = t.value == "true"
|
||||
return t
|
||||
|
||||
|
||||
def t_DOTTEDNAME(t):
|
||||
r"""[a-zA-Z_]\w*(\.[a-zA-Z_]\w*)+"""
|
||||
first, second = t.value.split(".")
|
||||
if first in reserved or second in reserved:
|
||||
custom_error(t, "reserved word as part of dotted name")
|
||||
return None
|
||||
return t
|
||||
|
||||
|
||||
def t_NAME(t):
|
||||
r"""[a-zA-Z_]\w*"""
|
||||
t.type = reserved.get(t.value, "NAME") # check for reserved words
|
||||
return t
|
||||
|
||||
|
||||
def t_DIRECTIVE(t):
|
||||
r"""%[a-z]+\b"""
|
||||
t.value = t.value[1:]
|
||||
return t
|
||||
|
||||
|
||||
def t_STRING(t):
|
||||
r"""(?x) # verbose mode
|
||||
(?<!\\) # not preceded by a backslash
|
||||
" # a literal double-quote
|
||||
.*? # 1-or-more characters
|
||||
(?<!\\) # not preceded by a backslash
|
||||
" # a literal double-quote
|
||||
|
|
||||
(?<!\\) # not preceded by a backslash
|
||||
' # a literal single quote
|
||||
.*? # 1-or-more characters
|
||||
(?<!\\) # not preceded by a backslash
|
||||
' # a literal double-quote
|
||||
"""
|
||||
t.value = ast.literal_eval(t.value)
|
||||
if len(t.value) == 1:
|
||||
t.type = "CHARACTER"
|
||||
if len(t.value) == 2 and t.value[0] == '\\':
|
||||
t.type = "CHARACTER"
|
||||
return t
|
||||
|
||||
|
||||
def t_FLOATINGPOINT(t):
|
||||
r"""((?: (?: \d* \. \d+ ) | (?: \d+ \.? ) )(?: [Ee] [+-]? \d+ ) ?)(?![a-z])"""
|
||||
try:
|
||||
t.value = int(t.value)
|
||||
t.type = "INTEGER"
|
||||
except ValueError:
|
||||
t.value = float(t.value)
|
||||
return t
|
||||
|
||||
|
||||
def t_INTEGER(t):
|
||||
r"""\$?[a-fA-F\d]+ | [\$%]?\d+ | %?[01]+"""
|
||||
sign = 1
|
||||
if t.value[0] in "+-":
|
||||
sign = -1 if t.value[0] == "-" else 1
|
||||
t.value = t.value[1:]
|
||||
if t.value[0] == '$':
|
||||
t.value = int(t.value[1:], 16) * sign
|
||||
elif t.value[0] == '%':
|
||||
t.value = int(t.value[1:], 2) * sign
|
||||
else:
|
||||
t.value = int(t.value) * sign
|
||||
return t
|
||||
|
||||
|
||||
def t_COMMENT(t):
|
||||
r"""[ \t]*;[^\n]*""" # dont eat newline
|
||||
return None # don't process comments
|
||||
|
||||
|
||||
def t_PRESERVEREGS(t):
|
||||
r"""!\s*[AXY]{0,3}\s*(?!=)"""
|
||||
t.value = t.value[1:-1].strip()
|
||||
return t
|
||||
|
||||
|
||||
def t_ENDL(t):
|
||||
r"""\n+"""
|
||||
t.lexer.lineno += len(t.value)
|
||||
t.value = "\n"
|
||||
return t # end of lines are significant to the parser
|
||||
|
||||
|
||||
def t_error(t):
|
||||
line, col = t.lineno, find_tok_column(t)
|
||||
filename = getattr(t.lexer, "source_filename", "<unknown-file>")
|
||||
sref = SourceRef(filename, line, col)
|
||||
if hasattr(t.lexer, "error_function"):
|
||||
t.lexer.error_function(sref, "illegal character '{:s}'", t.value[0])
|
||||
else:
|
||||
print("{}: illegal character '{:s}'".format(sref, t.value[0]))
|
||||
t.lexer.skip(1)
|
||||
|
||||
|
||||
def custom_error(t, message):
|
||||
line, col = t.lineno, find_tok_column(t)
|
||||
filename = getattr(t.lexer, "source_filename", "<unknown-file>")
|
||||
sref = SourceRef(filename, line, col)
|
||||
if hasattr(t.lexer, "error_function"):
|
||||
t.lexer.error_function(sref, message)
|
||||
else:
|
||||
print(sref, message)
|
||||
t.lexer.skip(1)
|
||||
|
||||
|
||||
def find_tok_column(token):
|
||||
"""Find the column of the token in its line."""
|
||||
last_cr = lexer.lexdata.rfind('\n', 0, token.lexpos)
|
||||
chunk = lexer.lexdata[last_cr:token.lexpos]
|
||||
return len(chunk.expandtabs())
|
||||
|
||||
|
||||
def print_warning(text: str, sourceref: SourceRef = None) -> None:
|
||||
if sourceref:
|
||||
print_bold("warning: {}: {:s}".format(sourceref, text))
|
||||
else:
|
||||
print_bold("warning: " + text)
|
||||
|
||||
|
||||
def print_bold(text: str) -> None:
|
||||
if sys.stdout.isatty():
|
||||
print("\x1b[1m" + text + "\x1b[0m", flush=True)
|
||||
else:
|
||||
print(text)
|
||||
|
||||
|
||||
lexer = ply.lex.lex()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ply.lex.runmain()
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +0,0 @@
|
||||
[mypy]
|
||||
follow_imports = normal
|
||||
ignore_missing_imports = True
|
||||
incremental = True
|
||||
|
||||
[mypy-il65/parsetab.*]
|
||||
ignore_errors = True
|
||||
|
||||
[mypy-il65/plyparser.*]
|
||||
ignore_errors = True
|
@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.7 (py3)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -1,3 +0,0 @@
|
||||
attrs
|
||||
ply
|
||||
cbmcodecs >= 0.2.0
|
@ -1,12 +0,0 @@
|
||||
import cProfile
|
||||
from il65.compile import PlyParser
|
||||
from il65.optimize import optimize
|
||||
|
||||
|
||||
def parse():
|
||||
parser = PlyParser(enable_floats=True)
|
||||
parsed_module = parser.parse_file("testsource/large.ill")
|
||||
optimize(parsed_module)
|
||||
|
||||
|
||||
cProfile.run("parse()", filename="profile.dat")
|
@ -1,3 +0,0 @@
|
||||
[pycodestyle]
|
||||
max-line-length = 140
|
||||
exclude = .git,__pycache__,.tox,docs,tests,build,dist,parsetab.py
|
@ -1 +0,0 @@
|
||||
# package
|
@ -1,60 +0,0 @@
|
||||
import pytest
|
||||
from il65.datatypes import FLOAT_MAX_NEGATIVE, FLOAT_MAX_POSITIVE
|
||||
from il65.codegen.shared import to_hex, to_mflpt5
|
||||
|
||||
|
||||
def test_to_hex():
|
||||
assert to_hex(0) == "0"
|
||||
assert to_hex(1) == "1"
|
||||
assert to_hex(10) == "10"
|
||||
assert to_hex(15) == "15"
|
||||
assert to_hex(16) == "$10"
|
||||
assert to_hex(255) == "$ff"
|
||||
assert to_hex(256) == "$0100"
|
||||
assert to_hex(20060) == "$4e5c"
|
||||
assert to_hex(65535) == "$ffff"
|
||||
with pytest.raises(OverflowError):
|
||||
to_hex(-1)
|
||||
with pytest.raises(OverflowError):
|
||||
to_hex(65536)
|
||||
|
||||
|
||||
def test_float_to_mflpt5():
|
||||
mflpt = to_mflpt5(1.0)
|
||||
assert type(mflpt) is bytearray
|
||||
assert b"\x00\x00\x00\x00\x00" == to_mflpt5(0)
|
||||
assert b"\x82\x49\x0F\xDA\xA1" == to_mflpt5(3.141592653)
|
||||
assert b"\x82\x49\x0F\xDA\xA2" == to_mflpt5(3.141592653589793)
|
||||
assert b"\x90\x80\x00\x00\x00" == to_mflpt5(-32768)
|
||||
assert b"\x81\x00\x00\x00\x00" == to_mflpt5(1)
|
||||
assert b"\x80\x35\x04\xF3\x34" == to_mflpt5(0.7071067812)
|
||||
assert b"\x80\x35\x04\xF3\x33" == to_mflpt5(0.7071067811865476)
|
||||
assert b"\x81\x35\x04\xF3\x34" == to_mflpt5(1.4142135624)
|
||||
assert b"\x81\x35\x04\xF3\x33" == to_mflpt5(1.4142135623730951)
|
||||
assert b"\x80\x80\x00\x00\x00" == to_mflpt5(-.5)
|
||||
assert b"\x80\x31\x72\x17\xF8" == to_mflpt5(0.69314718061)
|
||||
assert b"\x80\x31\x72\x17\xF7" == to_mflpt5(0.6931471805599453)
|
||||
assert b"\x84\x20\x00\x00\x00" == to_mflpt5(10)
|
||||
assert b"\x9E\x6E\x6B\x28\x00" == to_mflpt5(1000000000)
|
||||
assert b"\x80\x00\x00\x00\x00" == to_mflpt5(.5)
|
||||
assert b"\x81\x38\xAA\x3B\x29" == to_mflpt5(1.4426950408889634)
|
||||
assert b"\x81\x49\x0F\xDA\xA2" == to_mflpt5(1.5707963267948966)
|
||||
assert b"\x83\x49\x0F\xDA\xA2" == to_mflpt5(6.283185307179586)
|
||||
assert b"\x7F\x00\x00\x00\x00" == to_mflpt5(.25)
|
||||
|
||||
|
||||
def test_float_range():
|
||||
assert b"\xff\x7f\xff\xff\xff" == to_mflpt5(FLOAT_MAX_POSITIVE)
|
||||
assert b"\xff\xff\xff\xff\xff" == to_mflpt5(FLOAT_MAX_NEGATIVE)
|
||||
with pytest.raises(OverflowError):
|
||||
to_mflpt5(1.7014118346e+38)
|
||||
with pytest.raises(OverflowError):
|
||||
to_mflpt5(-1.7014118346e+38)
|
||||
with pytest.raises(OverflowError):
|
||||
to_mflpt5(1.7014118347e+38)
|
||||
with pytest.raises(OverflowError):
|
||||
to_mflpt5(-1.7014118347e+38)
|
||||
assert b"\x03\x39\x1d\x15\x63" == to_mflpt5(1.7e-38)
|
||||
assert b"\x00\x00\x00\x00\x00" == to_mflpt5(1.7e-39)
|
||||
assert b"\x03\xb9\x1d\x15\x63" == to_mflpt5(-1.7e-38)
|
||||
assert b"\x00\x00\x00\x00\x00" == to_mflpt5(-1.7e-39)
|
@ -1,102 +0,0 @@
|
||||
import pytest
|
||||
from il65.datatypes import DataType, STRING_DATATYPES, char_to_bytevalue
|
||||
from il65.plyparse import coerce_constant_value, LiteralValue, Scope, SymbolName, VarDef
|
||||
from il65.compile import ParseError
|
||||
from il65.plylex import SourceRef
|
||||
|
||||
|
||||
def test_datatypes():
|
||||
assert all(isinstance(s, DataType) for s in STRING_DATATYPES)
|
||||
assert all(s.isstring() for s in STRING_DATATYPES)
|
||||
assert not any(s.isarray() or s.isnumeric() for s in STRING_DATATYPES)
|
||||
assert DataType.WORDARRAY.isarray()
|
||||
assert not DataType.WORDARRAY.isnumeric()
|
||||
assert not DataType.WORDARRAY.isstring()
|
||||
assert not DataType.WORD.isarray()
|
||||
assert DataType.WORD.isnumeric()
|
||||
assert not DataType.WORD.isstring()
|
||||
|
||||
|
||||
def test_sourceref():
|
||||
s = SourceRef("file", 99, 42)
|
||||
assert str(s) == "file:99:42"
|
||||
s = SourceRef("file", 99)
|
||||
assert str(s) == "file:99"
|
||||
|
||||
|
||||
def test_parseerror():
|
||||
p = ParseError("message", SourceRef("filename", 99, 42))
|
||||
assert p.args == ("message", )
|
||||
assert str(p) == "filename:99:42 message"
|
||||
|
||||
|
||||
def test_char_to_bytevalue():
|
||||
assert char_to_bytevalue('a') == 65
|
||||
assert char_to_bytevalue('\n') == 13
|
||||
assert char_to_bytevalue('π') == 126
|
||||
assert char_to_bytevalue('▒') == 230
|
||||
assert char_to_bytevalue('\x00') == 0
|
||||
assert char_to_bytevalue('\xff') == 255
|
||||
with pytest.raises(AssertionError):
|
||||
char_to_bytevalue('<undefined>')
|
||||
# screencodes not yet implemented: assert datatypes.char_to_bytevalue('a', False) == 65
|
||||
|
||||
|
||||
def test_coerce_value_novars():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
def lv(v) -> LiteralValue:
|
||||
return LiteralValue(value=v, sourceref=sref) # type: ignore
|
||||
assert coerce_constant_value(DataType.BYTE, lv(0)) == (False, lv(0))
|
||||
assert coerce_constant_value(DataType.BYTE, lv(255)) == (False, lv(255))
|
||||
assert coerce_constant_value(DataType.BYTE, lv('@')) == (True, lv(64))
|
||||
assert coerce_constant_value(DataType.WORD, lv(0)) == (False, lv(0))
|
||||
assert coerce_constant_value(DataType.WORD, lv(65535)) == (False, lv(65535))
|
||||
assert coerce_constant_value(DataType.WORD, lv('@')) == (True, lv(64))
|
||||
assert coerce_constant_value(DataType.FLOAT, lv(-999.22)) == (False, lv(-999.22))
|
||||
assert coerce_constant_value(DataType.FLOAT, lv(123.45)) == (False, lv(123.45))
|
||||
assert coerce_constant_value(DataType.FLOAT, lv('@')) == (True, lv(64))
|
||||
assert coerce_constant_value(DataType.BYTE, lv(5.678)) == (True, lv(5))
|
||||
assert coerce_constant_value(DataType.WORD, lv(5.678)) == (True, lv(5))
|
||||
assert coerce_constant_value(DataType.WORD,
|
||||
lv("string")) == (False, lv("string")), "string (address) can be assigned to a word"
|
||||
assert coerce_constant_value(DataType.STRING, lv("string")) == (False, lv("string"))
|
||||
assert coerce_constant_value(DataType.STRING_P, lv("string")) == (False, lv("string"))
|
||||
assert coerce_constant_value(DataType.STRING_S, lv("string")) == (False, lv("string"))
|
||||
assert coerce_constant_value(DataType.STRING_PS, lv("string")) == (False, lv("string"))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(DataType.BYTE, lv(-1))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(DataType.BYTE, lv(256))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(DataType.BYTE, lv(256.12345))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(DataType.WORD, lv(-1))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(DataType.WORD, lv(65536))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(DataType.WORD, lv(65536.12345))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(DataType.FLOAT, lv(-1.7014118346e+38))
|
||||
with pytest.raises(OverflowError):
|
||||
coerce_constant_value(DataType.FLOAT, lv(1.7014118347e+38))
|
||||
with pytest.raises(TypeError):
|
||||
coerce_constant_value(DataType.BYTE, lv("string"))
|
||||
with pytest.raises(TypeError):
|
||||
coerce_constant_value(DataType.FLOAT, lv("string"))
|
||||
|
||||
|
||||
def test_coerce_value_vars():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
scope = Scope(nodes=[], level="block", sourceref=sref)
|
||||
vardef = VarDef(name="constantvar", vartype="const", datatype=None, sourceref=sref)
|
||||
vardef.value = LiteralValue(value=99, sourceref=sref)
|
||||
scope.add_node(vardef)
|
||||
vardef = VarDef(name="varvar", vartype="var", datatype=None, sourceref=sref)
|
||||
vardef.value = LiteralValue(value=42, sourceref=sref)
|
||||
scope.add_node(vardef)
|
||||
vardef = VarDef(name="memvar", vartype="memory", datatype=None, sourceref=sref)
|
||||
vardef.value = LiteralValue(value=0xc000, sourceref=sref)
|
||||
scope.add_node(vardef)
|
||||
value = SymbolName(name="constantvar", sourceref=sref)
|
||||
value.parent = scope
|
||||
assert coerce_constant_value(DataType.BYTE, value) == (True, LiteralValue(value=99, sourceref=sref))
|
@ -1,92 +0,0 @@
|
||||
import pytest
|
||||
from il65.plyparse import IncrDecr, AugAssignment, VarDef, SymbolName
|
||||
from il65.optimize import optimize
|
||||
from .test_parser import parse_source
|
||||
|
||||
|
||||
def test_incrdecr_joins_nonfloat():
|
||||
src = """~ test {
|
||||
X ++
|
||||
X ++
|
||||
X += 10
|
||||
Y--
|
||||
Y--
|
||||
Y-=20
|
||||
}"""
|
||||
result = parse_source(src)
|
||||
testscope = result.scope.nodes[0].nodes[0]
|
||||
assert len(testscope.nodes) == 6
|
||||
assert isinstance(testscope.nodes[0], IncrDecr)
|
||||
assert testscope.nodes[0].howmuch == 1
|
||||
assert isinstance(testscope.nodes[1], IncrDecr)
|
||||
assert testscope.nodes[1].howmuch == 1
|
||||
assert isinstance(testscope.nodes[2], AugAssignment)
|
||||
assert testscope.nodes[2].right.value == 10
|
||||
assert isinstance(testscope.nodes[3], IncrDecr)
|
||||
assert testscope.nodes[3].howmuch == 1
|
||||
assert isinstance(testscope.nodes[4], IncrDecr)
|
||||
assert testscope.nodes[4].howmuch == 1
|
||||
assert isinstance(testscope.nodes[5], AugAssignment)
|
||||
assert testscope.nodes[5].right.value == 20
|
||||
# now optimize the incrdecrs (joins them)
|
||||
optimize(result)
|
||||
testscope = result.scope.nodes[0].nodes[0]
|
||||
assert len(testscope.nodes) == 2 # @todo broken optimization right now
|
||||
assert isinstance(testscope.nodes[0], IncrDecr)
|
||||
assert testscope.nodes[0].operator == "++"
|
||||
assert testscope.nodes[0].howmuch == 12
|
||||
assert isinstance(testscope.nodes[1], IncrDecr)
|
||||
assert testscope.nodes[1].operator == "--"
|
||||
assert testscope.nodes[1].howmuch == 22
|
||||
|
||||
|
||||
def test_incrdecr_joins_float():
|
||||
src = """~ test {
|
||||
var .float flt = 0
|
||||
flt ++
|
||||
flt ++
|
||||
flt += 10
|
||||
flt --
|
||||
flt --
|
||||
flt --
|
||||
flt -= 5
|
||||
}"""
|
||||
result = parse_source(src)
|
||||
testscope = result.scope.nodes[0].nodes[0]
|
||||
assert len(testscope.nodes) == 8
|
||||
# now optimize the incrdecrs (joins them)
|
||||
optimize(result)
|
||||
testscope = result.scope.nodes[0].nodes[0]
|
||||
assert len(testscope.nodes) == 2
|
||||
assert isinstance(testscope.nodes[0], VarDef)
|
||||
assert isinstance(testscope.nodes[1], IncrDecr)
|
||||
assert testscope.nodes[1].operator == "++"
|
||||
assert testscope.nodes[1].howmuch == 4
|
||||
assert isinstance(testscope.nodes[1].target, SymbolName)
|
||||
assert testscope.nodes[1].target.name == "flt"
|
||||
|
||||
|
||||
def test_large_incrdecr_to_augassign():
|
||||
src = """~ test {
|
||||
X ++
|
||||
X ++
|
||||
X += 255
|
||||
Y --
|
||||
Y --
|
||||
Y -= 255
|
||||
}"""
|
||||
result = parse_source(src)
|
||||
testscope = result.scope.nodes[0].nodes[0]
|
||||
assert len(testscope.nodes) == 6
|
||||
# now optimize; joins the incrdecrs then converts to augassign because values are too large.
|
||||
optimize(result)
|
||||
testscope = result.scope.nodes[0].nodes[0]
|
||||
assert len(testscope.nodes) == 2
|
||||
assert isinstance(testscope.nodes[0], AugAssignment)
|
||||
assert testscope.nodes[0].left.name == "X"
|
||||
assert testscope.nodes[0].operator == "+="
|
||||
assert testscope.nodes[0].right.value == 257
|
||||
assert isinstance(testscope.nodes[1], AugAssignment)
|
||||
assert testscope.nodes[1].left.name == "Y"
|
||||
assert testscope.nodes[1].operator == "-="
|
||||
assert testscope.nodes[1].right.value == 257
|
@ -1,544 +0,0 @@
|
||||
import math
|
||||
import pytest
|
||||
from il65.plylex import lexer, tokens, find_tok_column, literals, reserved, SourceRef
|
||||
from il65.plyparse import *
|
||||
from il65.datatypes import DataType, VarType
|
||||
from il65.constantfold import ConstantFold
|
||||
|
||||
|
||||
def lexer_error(sourceref: SourceRef, fmtstring: str, *args: str) -> None:
|
||||
print("ERROR: {}: {}".format(sourceref, fmtstring.format(*args)))
|
||||
|
||||
|
||||
def parse_source(src: str) -> AstNode:
|
||||
lexer.lineno = 1
|
||||
lexer.source_filename = "sourcefile"
|
||||
tfilt = TokenFilter(lexer)
|
||||
result = parser.parse(input=src, tokenfunc=tfilt.token)
|
||||
connect_parents(result, None)
|
||||
return result
|
||||
|
||||
|
||||
lexer.error_function = lexer_error
|
||||
|
||||
|
||||
def test_lexer_definitions():
|
||||
assert "ENDL" in tokens
|
||||
assert "GOTO" in tokens
|
||||
assert '+' in literals
|
||||
assert ';' not in literals
|
||||
assert "return" in reserved
|
||||
assert "sub" in reserved
|
||||
assert "A" in reserved
|
||||
assert "if_cc" in reserved
|
||||
|
||||
|
||||
test_source_1 = """ %output prg, sys
|
||||
|
||||
; c1
|
||||
|
||||
; c2
|
||||
|
||||
|
||||
~ block $c000 {
|
||||
%import a,b
|
||||
|
||||
|
||||
; comment
|
||||
|
||||
var foo = 42+true
|
||||
var .matrix(20,30) m = 9.234556
|
||||
;comment2
|
||||
|
||||
|
||||
sub calculate () -> () {
|
||||
return
|
||||
}
|
||||
|
||||
;z
|
||||
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
def test_lexer():
|
||||
lexer.input(test_source_1)
|
||||
lexer.lineno = 1
|
||||
tokens = list(iter(lexer))
|
||||
token_types = list(t.type for t in tokens)
|
||||
assert token_types == ['DIRECTIVE', 'NAME', ',', 'NAME', 'ENDL', 'ENDL', 'ENDL',
|
||||
'BITINVERT', 'NAME', 'INTEGER', '{', 'ENDL',
|
||||
'DIRECTIVE', 'NAME', ',', 'NAME', 'ENDL', 'ENDL',
|
||||
'VARTYPE', 'NAME', 'IS', 'INTEGER', '+', 'BOOLEAN', 'ENDL',
|
||||
'VARTYPE', 'DATATYPE', '(', 'INTEGER', ',', 'INTEGER', ')', 'NAME', 'IS', 'FLOATINGPOINT', 'ENDL', 'ENDL',
|
||||
'SUB', 'NAME', '(', ')', 'RARROW', '(', ')', '{', 'ENDL', 'RETURN', 'ENDL', '}', 'ENDL', 'ENDL', 'ENDL', 'ENDL',
|
||||
'}', 'ENDL']
|
||||
directive_token = tokens[12]
|
||||
assert directive_token.type == "DIRECTIVE"
|
||||
assert directive_token.value == "import"
|
||||
assert directive_token.lineno == 9
|
||||
assert directive_token.lexpos == lexer.lexdata.index("%import")
|
||||
assert find_tok_column(directive_token) == 10
|
||||
bool_token = tokens[23]
|
||||
assert bool_token.type == "BOOLEAN"
|
||||
assert type(bool_token.value) is bool
|
||||
assert bool_token.value == True
|
||||
|
||||
|
||||
def test_lexer_strings():
|
||||
lexer.input(r"'hello\tbye\n\n' '\n'")
|
||||
lexer.lineno = 1
|
||||
tokens = list(iter(lexer))
|
||||
assert len(tokens) == 2
|
||||
st = tokens[0]
|
||||
assert st.type == "STRING"
|
||||
assert st.value == "hello\tbye\n\n"
|
||||
lexer.input(r"'hello\tbye\n\n'")
|
||||
st = tokens[1]
|
||||
assert st.type == "CHARACTER"
|
||||
assert st.value == '\n'
|
||||
|
||||
|
||||
def test_tokenfilter():
|
||||
lexer.input(test_source_1)
|
||||
lexer.lineno = 1
|
||||
filter = TokenFilter(lexer)
|
||||
tokens = []
|
||||
while True:
|
||||
token = filter.token()
|
||||
if not token:
|
||||
break
|
||||
tokens.append(token)
|
||||
token_types = list(t.type for t in tokens)
|
||||
assert token_types == ['DIRECTIVE', 'NAME', ',', 'NAME', 'ENDL',
|
||||
'BITINVERT', 'NAME', 'INTEGER', '{', 'ENDL',
|
||||
'DIRECTIVE', 'NAME', ',', 'NAME', 'ENDL',
|
||||
'VARTYPE', 'NAME', 'IS', 'INTEGER', '+', 'BOOLEAN', 'ENDL',
|
||||
'VARTYPE', 'DATATYPE', '(', 'INTEGER', ',', 'INTEGER', ')', 'NAME', 'IS', 'FLOATINGPOINT', 'ENDL',
|
||||
'SUB', 'NAME', '(', ')', 'RARROW', '(', ')', '{', 'ENDL', 'RETURN', 'ENDL', '}', 'ENDL',
|
||||
'}', 'ENDL']
|
||||
|
||||
|
||||
def test_parser():
|
||||
result = parse_source(test_source_1)
|
||||
assert isinstance(result, Module)
|
||||
assert result.name == "sourcefile"
|
||||
assert result.scope.name == "<sourcefile global scope>"
|
||||
assert result.subroutine_usage == {}
|
||||
assert result.scope.parent_scope is None
|
||||
sub = result.scope.lookup("block.calculate")
|
||||
assert isinstance(sub, Subroutine)
|
||||
assert sub.name == "calculate"
|
||||
block = result.scope.lookup("block")
|
||||
assert isinstance(block, Block)
|
||||
assert block.name == "block"
|
||||
bool_vdef = block.scope.nodes[1]
|
||||
assert isinstance(bool_vdef, VarDef)
|
||||
assert isinstance(bool_vdef.value, ExpressionWithOperator)
|
||||
assert isinstance(bool_vdef.value.right, LiteralValue)
|
||||
assert isinstance(bool_vdef.value.right.value, int)
|
||||
assert bool_vdef.value.right.value == 1
|
||||
assert block.address == 49152
|
||||
sub2 = block.scope.lookup("calculate")
|
||||
assert sub2 is sub
|
||||
assert sub2.lineref == "src l. 19"
|
||||
all_nodes = list(result.all_nodes())
|
||||
assert len(all_nodes) == 14
|
||||
all_nodes = list(result.all_nodes(Subroutine))
|
||||
assert len(all_nodes) == 1
|
||||
assert isinstance(all_nodes[0], Subroutine)
|
||||
assert isinstance(all_nodes[0].parent, Scope)
|
||||
assert all_nodes[0] in all_nodes[0].parent.nodes
|
||||
assert all_nodes[0].lineref == "src l. 19"
|
||||
assert all_nodes[0].parent.lineref == "src l. 8"
|
||||
|
||||
|
||||
def test_block_nodes():
|
||||
sref = SourceRef("file", 1, 1)
|
||||
sub1 = Subroutine(name="subaddr", param_spec=[], result_spec=[], address=0xc000, sourceref=sref)
|
||||
sub2 = Subroutine(name="subblock", param_spec=[], result_spec=[], sourceref=sref)
|
||||
sub2.scope = Scope(nodes=[Label(name="start", sourceref=sref)], level="block", sourceref=sref)
|
||||
assert sub1.scope is None
|
||||
assert sub1.nodes == []
|
||||
assert sub2.scope is not None
|
||||
assert len(sub2.scope.nodes) > 0
|
||||
|
||||
|
||||
def test_parser_2():
|
||||
src = """~ test {
|
||||
999(1,2)
|
||||
[zz]()
|
||||
}
|
||||
"""
|
||||
result = parse_source(src)
|
||||
block = result.scope.nodes[0]
|
||||
call = block.scope.nodes[0]
|
||||
assert isinstance(call, SubCall)
|
||||
assert len(call.arguments.nodes) == 2
|
||||
assert isinstance(call.target, LiteralValue)
|
||||
assert call.target.value == 999
|
||||
call = block.scope.nodes[1]
|
||||
assert isinstance(call, SubCall)
|
||||
assert len(call.arguments.nodes) == 0
|
||||
assert isinstance(call.target, Dereference)
|
||||
assert call.target.operand.name == "zz"
|
||||
|
||||
|
||||
def test_typespec():
|
||||
src = """~ test {
|
||||
[$c000.word] = 5
|
||||
[$c000 .byte] = 5
|
||||
[AX .word] = 5
|
||||
[AX .float] = 5
|
||||
}
|
||||
"""
|
||||
result = parse_source(src)
|
||||
block = result.scope.nodes[0]
|
||||
assignment1, assignment2, assignment3, assignment4 = block.scope.nodes
|
||||
assert assignment1.right.value == 5
|
||||
assert assignment2.right.value == 5
|
||||
assert assignment3.right.value == 5
|
||||
assert assignment4.right.value == 5
|
||||
assert len(assignment1.left.nodes) == 1
|
||||
assert len(assignment2.left.nodes) == 1
|
||||
assert len(assignment3.left.nodes) == 1
|
||||
assert len(assignment4.left.nodes) == 1
|
||||
t1 = assignment1.left
|
||||
t2 = assignment2.left
|
||||
t3 = assignment3.left
|
||||
t4 = assignment4.left
|
||||
assert isinstance(t1, Dereference)
|
||||
assert isinstance(t2, Dereference)
|
||||
assert isinstance(t3, Dereference)
|
||||
assert isinstance(t4, Dereference)
|
||||
assert isinstance(t1.operand, LiteralValue)
|
||||
assert isinstance(t2.operand, LiteralValue)
|
||||
assert isinstance(t3.operand, Register)
|
||||
assert isinstance(t4.operand, Register)
|
||||
assert t1.operand.value == 0xc000
|
||||
assert t2.operand.value == 0xc000
|
||||
assert t3.operand.name == "AX"
|
||||
assert t4.operand.name == "AX"
|
||||
assert t1.datatype == DataType.WORD
|
||||
assert t2.datatype == DataType.BYTE
|
||||
assert t3.datatype == DataType.WORD
|
||||
assert t4.datatype == DataType.FLOAT
|
||||
assert t1.size is None
|
||||
assert t2.size is None
|
||||
assert t3.size is None
|
||||
assert t4.size is None
|
||||
|
||||
|
||||
def test_char_string():
|
||||
src = """~ test {
|
||||
var x1 = '@'
|
||||
var x2 = 'π'
|
||||
var x3 = 'abc'
|
||||
A = '@'
|
||||
A = 'π'
|
||||
A = 'abc'
|
||||
}
|
||||
"""
|
||||
result = parse_source(src)
|
||||
block = result.scope.nodes[0]
|
||||
var1, var2, var3, assgn1, assgn2, assgn3, = block.scope.nodes
|
||||
assert var1.value.value == '@'
|
||||
assert var2.value.value == 'π'
|
||||
assert var3.value.value == "abc"
|
||||
assert assgn1.right.value == '@'
|
||||
assert assgn2.right.value == 'π'
|
||||
assert assgn3.right.value == "abc"
|
||||
# note: the actual one-charactor-to-bytevalue conversion is done at the very latest, when issuing an assignment statement
|
||||
|
||||
|
||||
def test_boolean_int():
|
||||
src = """~ test {
|
||||
var x1 = true
|
||||
var x2 = false
|
||||
A = true
|
||||
A = false
|
||||
}
|
||||
"""
|
||||
result = parse_source(src)
|
||||
block = result.scope.nodes[0]
|
||||
var1, var2, assgn1, assgn2, = block.scope.nodes
|
||||
assert type(var1.value.value) is int and var1.value.value == 1
|
||||
assert type(var2.value.value) is int and var2.value.value == 0
|
||||
assert type(assgn1.right.value) is int and assgn1.right.value == 1
|
||||
assert type(assgn2.right.value) is int and assgn2.right.value == 0
|
||||
|
||||
|
||||
def test_incrdecr_operators():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
with pytest.raises(ValueError):
|
||||
IncrDecr(operator="??", sourceref=sref)
|
||||
i = IncrDecr(operator="++", sourceref=sref)
|
||||
assert i.howmuch == 1
|
||||
|
||||
|
||||
def test_symbol_lookup():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
var1 = VarDef(name="var1", vartype="const", datatype=DataType.WORD, sourceref=sref)
|
||||
var1.value = LiteralValue(value=42, sourceref=sref)
|
||||
var1.value.parent = var1
|
||||
var2 = VarDef(name="var2", vartype="const", datatype=DataType.FLOAT, sourceref=sref)
|
||||
var2.value = LiteralValue(value=123.456, sourceref=sref)
|
||||
var2.value.parent = var2
|
||||
label1 = Label(name="outerlabel", sourceref=sref)
|
||||
label2 = Label(name="innerlabel", sourceref=sref)
|
||||
scope_inner = Scope(nodes=[
|
||||
label2,
|
||||
var2
|
||||
], level="block", sourceref=sref)
|
||||
scope_inner.name = "inner"
|
||||
var2.parent = label2.parent = scope_inner
|
||||
scope_outer = Scope(nodes=[
|
||||
label1,
|
||||
var1,
|
||||
scope_inner
|
||||
], level="block", sourceref=sref)
|
||||
scope_outer.name = "outer"
|
||||
var1.parent = label1.parent = scope_inner.parent = scope_outer
|
||||
scope_topmost = Scope(nodes=[scope_outer], level="module", sourceref=sref)
|
||||
scope_topmost.name = "topmost"
|
||||
scope_outer.parent = scope_topmost
|
||||
scope_topmost.define_builtin_functions()
|
||||
assert scope_inner.parent_scope is scope_outer
|
||||
assert scope_outer.parent_scope is scope_topmost
|
||||
assert scope_topmost.parent_scope is None
|
||||
assert label1.my_scope() is scope_outer
|
||||
assert var1.my_scope() is scope_outer
|
||||
assert scope_inner.my_scope() is scope_outer
|
||||
assert label2.my_scope() is scope_inner
|
||||
assert var2.my_scope() is scope_inner
|
||||
assert scope_outer.my_scope() is scope_topmost
|
||||
with pytest.raises(LookupError):
|
||||
scope_topmost.my_scope()
|
||||
with pytest.raises(UndefinedSymbolError):
|
||||
scope_inner.lookup("unexisting")
|
||||
with pytest.raises(UndefinedSymbolError):
|
||||
scope_outer.lookup("unexisting")
|
||||
assert scope_inner.lookup("innerlabel") is label2
|
||||
assert scope_inner.lookup("var2") is var2
|
||||
assert scope_inner.lookup("outerlabel") is label1
|
||||
assert scope_inner.lookup("var1") is var1
|
||||
with pytest.raises(UndefinedSymbolError):
|
||||
scope_outer.lookup("innerlabel")
|
||||
with pytest.raises(UndefinedSymbolError):
|
||||
scope_outer.lookup("var2")
|
||||
assert scope_outer.lookup("var1") is var1
|
||||
assert scope_outer.lookup("outerlabel") is label1
|
||||
math_func = scope_inner.lookup("sin")
|
||||
assert isinstance(math_func, BuiltinFunction)
|
||||
assert math_func.name == "sin" and math_func.func is math.sin
|
||||
builtin_func = scope_inner.lookup("abs")
|
||||
assert isinstance(builtin_func, BuiltinFunction)
|
||||
assert builtin_func.name == "abs" and builtin_func.func is abs
|
||||
# test dotted names:
|
||||
with pytest.raises(UndefinedSymbolError):
|
||||
scope_inner.lookup("noscope.nosymbol.nothing")
|
||||
assert scope_inner.lookup("outer.inner.var2") is var2
|
||||
with pytest.raises(UndefinedSymbolError):
|
||||
scope_inner.lookup("outer.inner.var1")
|
||||
with pytest.raises(UndefinedSymbolError):
|
||||
scope_inner.lookup("outer.var2")
|
||||
assert scope_inner.lookup("outer.var1") is var1
|
||||
|
||||
|
||||
def test_const_numeric_expressions():
|
||||
src = """~ test {
|
||||
A = 1+2+3+4+5
|
||||
X = 1+2*5+2
|
||||
Y = (1+2)*(5+2)
|
||||
A = (((10+20)/2)+5)**3
|
||||
X = -10-11-12
|
||||
Y = 1.234 mod (0.9 / 1.2)
|
||||
A = sin(1.234)
|
||||
X = round(4.567)-2
|
||||
Y = 1+abs(-100)
|
||||
A = ~1
|
||||
X = -1
|
||||
A = 4 << (9-3)
|
||||
X = 5000 >> 2
|
||||
Y = 999//88
|
||||
}
|
||||
"""
|
||||
result = parse_source(src)
|
||||
if isinstance(result, Module):
|
||||
result.scope.define_builtin_functions()
|
||||
assignments = list(result.all_nodes(Assignment))
|
||||
e = [a.nodes[1] for a in assignments]
|
||||
assert all(x.is_compiletime_const() for x in e)
|
||||
assert e[0].const_value() == 15 # 1+2+3+4+5
|
||||
assert e[1].const_value() == 13 # 1+2*5+2
|
||||
assert e[2].const_value() == 21 # (1+2)*(5+2)
|
||||
assert e[3].const_value() == 8000 # (((10+20)/2)+5)**3
|
||||
assert e[4].const_value() == -33 # -10-11-12
|
||||
assert e[5].const_value() == 0.484 # 1.234 mod (0.9 / 1.2)
|
||||
assert math.isclose(e[6].const_value(), 0.9438182093746337) # sin(1.234)
|
||||
assert e[7].const_value() == 3 # round(4.567)-2
|
||||
assert e[8].const_value() == 101 # 1+abs(-100)
|
||||
assert e[9].const_value() == -2 # ~1
|
||||
assert e[10].const_value() == -1 # -1
|
||||
assert e[11].const_value() == 256 # 4 << (9-3)
|
||||
assert e[12].const_value() == 1250 # 5000 >> 2
|
||||
assert e[13].const_value() == 11 # 999//88
|
||||
|
||||
|
||||
def test_const_logic_expressions():
|
||||
src = """~ test {
|
||||
A = true or false
|
||||
X = true and false
|
||||
Y = true xor false
|
||||
A = false and false or true
|
||||
X = (false and (false or true))
|
||||
Y = not (false or true)
|
||||
A = 1 < 2
|
||||
X = 1 >= 2
|
||||
Y = 1 == (2+3)
|
||||
}
|
||||
"""
|
||||
result = parse_source(src)
|
||||
assignments = list(result.all_nodes(Assignment))
|
||||
e = [a.nodes[1] for a in assignments]
|
||||
assert all(x.is_compiletime_const() for x in e)
|
||||
assert e[0].const_value() == True
|
||||
assert e[1].const_value() == False
|
||||
assert e[2].const_value() == True
|
||||
assert e[3].const_value() == True
|
||||
assert e[4].const_value() == False
|
||||
assert e[5].const_value() == False
|
||||
assert e[6].const_value() == True
|
||||
assert e[7].const_value() == False
|
||||
assert e[8].const_value() == False
|
||||
|
||||
|
||||
def test_const_other_expressions():
|
||||
src = """~ test {
|
||||
memory memvar = $c123
|
||||
A = &memvar ; constant
|
||||
X = &sin ; non-constant
|
||||
Y = [memvar] ; non-constant
|
||||
}
|
||||
"""
|
||||
result = parse_source(src)
|
||||
if isinstance(result, Module):
|
||||
result.scope.define_builtin_functions()
|
||||
assignments = list(result.all_nodes(Assignment))
|
||||
e = [a.nodes[1] for a in assignments]
|
||||
assert e[0].is_compiletime_const()
|
||||
assert e[0].const_value() == 0xc123
|
||||
assert not e[1].is_compiletime_const()
|
||||
with pytest.raises(TypeError):
|
||||
e[1].const_value()
|
||||
assert not e[2].is_compiletime_const()
|
||||
with pytest.raises(TypeError):
|
||||
e[2].const_value()
|
||||
|
||||
|
||||
def test_vdef_const_folds():
|
||||
src = """~ test {
|
||||
const cb1 = 123
|
||||
const cb2 = cb1
|
||||
const cb3 = cb1*3
|
||||
}
|
||||
"""
|
||||
result = parse_source(src)
|
||||
if isinstance(result, Module):
|
||||
result.scope.define_builtin_functions()
|
||||
vd = list(result.all_nodes(VarDef))
|
||||
assert vd[0].name == "cb1"
|
||||
assert vd[0].vartype == VarType.CONST
|
||||
assert vd[0].datatype == DataType.BYTE
|
||||
assert isinstance(vd[0].value, LiteralValue)
|
||||
assert vd[0].value.value == 123
|
||||
assert vd[1].name == "cb2"
|
||||
assert vd[1].vartype == VarType.CONST
|
||||
assert vd[1].datatype == DataType.BYTE
|
||||
assert isinstance(vd[1].value, SymbolName)
|
||||
assert vd[1].value.name == "cb1"
|
||||
assert vd[2].name == "cb3"
|
||||
assert vd[2].vartype == VarType.CONST
|
||||
assert vd[2].datatype == DataType.BYTE
|
||||
assert isinstance(vd[2].value, ExpressionWithOperator)
|
||||
cf = ConstantFold(result)
|
||||
cf.fold_constants()
|
||||
vd = list(result.all_nodes(VarDef))
|
||||
assert vd[0].name == "cb1"
|
||||
assert vd[0].vartype == VarType.CONST
|
||||
assert vd[0].datatype == DataType.BYTE
|
||||
assert isinstance(vd[0].value, LiteralValue)
|
||||
assert vd[0].value.value == 123
|
||||
assert vd[1].name == "cb2"
|
||||
assert vd[1].vartype == VarType.CONST
|
||||
assert vd[1].datatype == DataType.BYTE
|
||||
assert isinstance(vd[1].value, LiteralValue)
|
||||
assert vd[1].value.value == 123
|
||||
assert vd[2].name == "cb3"
|
||||
assert vd[2].vartype == VarType.CONST
|
||||
assert vd[2].datatype == DataType.BYTE
|
||||
assert isinstance(vd[2].value, LiteralValue)
|
||||
assert vd[2].value.value == 369
|
||||
|
||||
|
||||
def test_vdef_const_expressions():
|
||||
src = """~ test {
|
||||
var bvar = 99
|
||||
var .float fvar = sin(1.2-0.3)
|
||||
var .float flt2 = -9.87e-6
|
||||
|
||||
bvar ++
|
||||
fvar ++
|
||||
flt2 ++
|
||||
bvar += 2+2
|
||||
fvar += 2+3
|
||||
flt2 += 2+4
|
||||
bvar = 2+5
|
||||
fvar = 2+6
|
||||
flt2 = 2+7
|
||||
}
|
||||
"""
|
||||
result = parse_source(src)
|
||||
if isinstance(result, Module):
|
||||
result.scope.define_builtin_functions()
|
||||
cf = ConstantFold(result)
|
||||
cf.fold_constants()
|
||||
vd = list(result.all_nodes(VarDef))
|
||||
assert len(vd)==3
|
||||
assert vd[0].name == "bvar"
|
||||
assert isinstance(vd[0].value, LiteralValue)
|
||||
assert vd[0].value.value == 99
|
||||
assert vd[1].name == "fvar"
|
||||
assert isinstance(vd[1].value, LiteralValue)
|
||||
assert type(vd[1].value.value) is float
|
||||
assert math.isclose(vd[1].value.value, math.sin(0.9))
|
||||
assert vd[2].name == "flt2"
|
||||
assert isinstance(vd[2].value, LiteralValue)
|
||||
assert math.isclose(-9.87e-6, vd[2].value.value)
|
||||
# test incrdecr assignment target
|
||||
nodes = list(result.all_nodes(IncrDecr))
|
||||
assert len(nodes) == 3
|
||||
assert isinstance(nodes[0].target, SymbolName)
|
||||
assert nodes[0].target.name == "bvar"
|
||||
assert isinstance(nodes[1].target, SymbolName)
|
||||
assert nodes[1].target.name == "fvar"
|
||||
assert isinstance(nodes[2].target, SymbolName)
|
||||
assert nodes[2].target.name == "flt2"
|
||||
# test augassign assignment target
|
||||
nodes = list(result.all_nodes(AugAssignment))
|
||||
assert len(nodes) == 3
|
||||
assert isinstance(nodes[0].left, SymbolName)
|
||||
assert nodes[0].left.name == "bvar"
|
||||
assert isinstance(nodes[1].left, SymbolName)
|
||||
assert nodes[1].left.name == "fvar"
|
||||
assert isinstance(nodes[2].left, SymbolName)
|
||||
assert nodes[2].left.name == "flt2"
|
||||
# test assign assignment target
|
||||
nodes = list(result.all_nodes(Assignment))
|
||||
assert len(nodes) == 3
|
||||
assert isinstance(nodes[0].left, SymbolName)
|
||||
assert nodes[0].left.name == "bvar"
|
||||
assert isinstance(nodes[1].left, SymbolName)
|
||||
assert nodes[1].left.name == "fvar"
|
||||
assert isinstance(nodes[2].left, SymbolName)
|
||||
assert nodes[2].left.name == "flt2"
|
@ -1,112 +0,0 @@
|
||||
import pytest
|
||||
from il65.datatypes import DataType, VarType
|
||||
from il65.plyparse import (LiteralValue, VarDef, DatatypeNode, ExpressionWithOperator,
|
||||
Scope, AddressOf, SymbolName, UndefinedSymbolError)
|
||||
from il65.plylex import SourceRef
|
||||
|
||||
|
||||
def test_creation():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
v = VarDef(name="v1", vartype="const", datatype=None, sourceref=sref)
|
||||
assert v.name == "v1"
|
||||
assert v.vartype == VarType.CONST
|
||||
assert v.datatype == DataType.BYTE
|
||||
assert v.size == [1]
|
||||
assert v.value is None
|
||||
assert v.zp_address is None
|
||||
v = VarDef(name="v2", vartype="memory", datatype=None, sourceref=sref)
|
||||
assert v.vartype == VarType.MEMORY
|
||||
assert isinstance(v.value, LiteralValue)
|
||||
assert v.value.value == 0
|
||||
dt = DatatypeNode(name="float", sourceref=sref)
|
||||
v = VarDef(name="v2", vartype="var", datatype=dt, sourceref=sref)
|
||||
assert v.vartype == VarType.VAR
|
||||
assert v.datatype == DataType.FLOAT
|
||||
assert isinstance(v.value, LiteralValue)
|
||||
assert v.value.value == 0
|
||||
dt = DatatypeNode(name="matrix", sourceref=sref)
|
||||
with pytest.raises(ValueError):
|
||||
VarDef(name="v2", vartype="var", datatype=dt, sourceref=sref)
|
||||
dt.dimensions = [2, 3]
|
||||
v = VarDef(name="v2", vartype="var", datatype=dt, sourceref=sref)
|
||||
assert v.vartype == VarType.VAR
|
||||
assert v.datatype == DataType.MATRIX
|
||||
assert v.size == [2, 3]
|
||||
assert isinstance(v.value, LiteralValue)
|
||||
assert v.value.value == 0
|
||||
dt = DatatypeNode(name="str", sourceref=sref)
|
||||
v = VarDef(name="v2", vartype="var", datatype=dt, sourceref=sref)
|
||||
assert v.vartype == VarType.VAR
|
||||
assert v.datatype == DataType.STRING
|
||||
assert v.size == [1]
|
||||
assert v.value is None
|
||||
|
||||
|
||||
def test_set_value():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
v = VarDef(name="v1", vartype="var", datatype=DatatypeNode(name="word", sourceref=sref), sourceref=sref)
|
||||
assert v.datatype == DataType.WORD
|
||||
assert v.value.value == 0
|
||||
v.value = LiteralValue(value=42, sourceref=sref)
|
||||
assert v.value.value == 42
|
||||
v = VarDef(name="v1", vartype="var", datatype=DatatypeNode(name="str", sourceref=sref), sourceref=sref)
|
||||
assert v.datatype == DataType.STRING
|
||||
assert v.value is None
|
||||
v.value = LiteralValue(value="hello", sourceref=sref)
|
||||
assert v.value.value == "hello"
|
||||
e = ExpressionWithOperator(operator="-", sourceref=sref)
|
||||
e.left = LiteralValue(value=42, sourceref=sref)
|
||||
v.value = e
|
||||
assert v.value is e
|
||||
|
||||
|
||||
def test_const_value():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
scope = Scope(nodes=[], level="block", sourceref=sref)
|
||||
vardef = VarDef(name="constvar", vartype="const", datatype=None, sourceref=sref)
|
||||
vardef.value = LiteralValue(value=43, sourceref=sref)
|
||||
scope.add_node(vardef)
|
||||
vardef = VarDef(name="varvar", vartype="var", datatype=None, sourceref=sref)
|
||||
vardef.value = LiteralValue(value=44, sourceref=sref)
|
||||
scope.add_node(vardef)
|
||||
vardef = VarDef(name="memvar", vartype="memory", datatype=None, sourceref=sref)
|
||||
vardef.value = LiteralValue(value=45, sourceref=sref)
|
||||
scope.add_node(vardef)
|
||||
v = VarDef(name="v1", vartype="var", datatype=DatatypeNode(name="word", sourceref=sref), sourceref=sref)
|
||||
with pytest.raises(TypeError):
|
||||
v.const_value()
|
||||
v = VarDef(name="v1", vartype="memory", datatype=DatatypeNode(name="word", sourceref=sref), sourceref=sref)
|
||||
with pytest.raises(TypeError):
|
||||
v.const_value()
|
||||
v = VarDef(name="v1", vartype="const", datatype=DatatypeNode(name="word", sourceref=sref), sourceref=sref)
|
||||
with pytest.raises(ValueError):
|
||||
v.const_value()
|
||||
v.value = LiteralValue(value=42, sourceref=sref)
|
||||
assert v.const_value() == 42
|
||||
v = VarDef(name="v1", vartype="const", datatype=DatatypeNode(name="float", sourceref=sref), sourceref=sref)
|
||||
with pytest.raises(ValueError):
|
||||
v.const_value()
|
||||
v.value = LiteralValue(value=42.9988, sourceref=sref)
|
||||
assert v.const_value() == 42.9988
|
||||
e = ExpressionWithOperator(operator="-", sourceref=sref)
|
||||
e.left = LiteralValue(value=42, sourceref=sref)
|
||||
v.value = e
|
||||
assert v.const_value() == -42
|
||||
s = SymbolName(name="unexisting", sourceref=sref)
|
||||
s.parent = scope
|
||||
v.value = s
|
||||
with pytest.raises(UndefinedSymbolError):
|
||||
v.const_value()
|
||||
s = SymbolName(name="constvar", sourceref=sref)
|
||||
s.parent = scope
|
||||
v.value = s
|
||||
assert v.const_value() == 43
|
||||
a = AddressOf(name="varvar", sourceref=sref)
|
||||
a.parent = scope
|
||||
v.value = a
|
||||
with pytest.raises(TypeError):
|
||||
v.const_value()
|
||||
a = AddressOf(name="memvar", sourceref=sref)
|
||||
a.parent = scope
|
||||
v.value = a
|
||||
assert v.const_value() == 45
|
@ -1,60 +0,0 @@
|
||||
import pytest
|
||||
from tinyvm.core import Memory
|
||||
|
||||
|
||||
def test_memory_unsigned():
|
||||
m = Memory()
|
||||
m.set_byte(1000, 1)
|
||||
m.set_byte(1001, 2)
|
||||
m.set_byte(1002, 3)
|
||||
m.set_byte(1003, 4)
|
||||
m.set_byte(2000, 252)
|
||||
m.set_byte(2001, 253)
|
||||
m.set_byte(2002, 254)
|
||||
m.set_byte(2003, 255)
|
||||
assert 1 == m.get_byte(1000)
|
||||
assert 2 == m.get_byte(1001)
|
||||
assert 3 == m.get_byte(1002)
|
||||
assert 4 == m.get_byte(1003)
|
||||
assert 252 == m.get_byte(2000)
|
||||
assert 253 == m.get_byte(2001)
|
||||
assert 254 == m.get_byte(2002)
|
||||
assert 255 == m.get_byte(2003)
|
||||
assert b"\x01\x02\x03\x04" == m.get_bytes(1000, 4)
|
||||
assert 0x0201 == m.get_word(1000)
|
||||
assert 0xfffe == m.get_word(2002)
|
||||
m.set_word(2002, 40000)
|
||||
assert 40000 == m.get_word(2002)
|
||||
assert 0x40 == m.get_byte(2002)
|
||||
assert 0x9c == m.get_byte(2003)
|
||||
|
||||
|
||||
def test_memory_signed():
|
||||
m = Memory()
|
||||
m.set_byte(1000, 1)
|
||||
m.set_byte(1001, 2)
|
||||
m.set_byte(1002, 3)
|
||||
m.set_byte(1003, 4)
|
||||
m.set_byte(2000, 252)
|
||||
m.set_byte(2001, 253)
|
||||
m.set_byte(2002, 254)
|
||||
m.set_byte(2003, 255)
|
||||
assert 1 == m.get_sbyte(1000)
|
||||
assert 2 == m.get_sbyte(1001)
|
||||
assert 3 == m.get_sbyte(1002)
|
||||
assert 4 == m.get_sbyte(1003)
|
||||
assert -4 == m.get_sbyte(2000)
|
||||
assert -3 == m.get_sbyte(2001)
|
||||
assert -2 == m.get_sbyte(2002)
|
||||
assert -1 == m.get_sbyte(2003)
|
||||
assert 0x0201 == m.get_sword(1000)
|
||||
assert -2 == m.get_sword(2002)
|
||||
m.set_sword(2002, 30000)
|
||||
assert 30000 == m.get_sword(2002)
|
||||
assert 0x30 == m.get_sbyte(2002)
|
||||
assert 0x75 == m.get_sbyte(2003)
|
||||
m.set_sword(2002, -30000)
|
||||
assert -30000 == m.get_sword(2002)
|
||||
assert 0x8ad0 == m.get_word(2002)
|
||||
assert 0xd0 == m.get_byte(2002)
|
||||
assert 0x8a == m.get_byte(2003)
|
@ -1,83 +0,0 @@
|
||||
import pytest
|
||||
from il65.compile import Zeropage, CompileError
|
||||
from il65.plyparse import ZpOptions, VarDef
|
||||
from il65.plylex import SourceRef
|
||||
from il65.datatypes import DataType
|
||||
|
||||
|
||||
def test_zp_names():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
zp = Zeropage(ZpOptions.NOCLOBBER, False)
|
||||
with pytest.raises(AssertionError):
|
||||
zp.allocate(VarDef(name="", vartype="memory", datatype=DataType.BYTE, sourceref=sref))
|
||||
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||
zp.allocate(VarDef(name="varname", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||
with pytest.raises(AssertionError):
|
||||
zp.allocate(VarDef(name="varname", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||
zp.allocate(VarDef(name="varname2", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||
|
||||
|
||||
def test_zp_noclobber_allocation():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
zp = Zeropage(ZpOptions.NOCLOBBER, True)
|
||||
assert zp.available() == 9
|
||||
with pytest.raises(CompileError):
|
||||
# in regular zp there aren't 5 sequential bytes free
|
||||
zp.allocate(VarDef(name="impossible", vartype="var", datatype=DataType.FLOAT, sourceref=sref))
|
||||
for i in range(zp.available()):
|
||||
loc = zp.allocate(VarDef(name="bvar"+str(i), vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||
assert loc > 0
|
||||
assert zp.available() == 0
|
||||
with pytest.raises(CompileError):
|
||||
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||
with pytest.raises(CompileError):
|
||||
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
|
||||
|
||||
|
||||
def test_zp_float_enable():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
zp = Zeropage(ZpOptions.CLOBBER, False)
|
||||
with pytest.raises(TypeError):
|
||||
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.FLOAT, sourceref=sref))
|
||||
zp = Zeropage(ZpOptions.CLOBBER, True)
|
||||
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.FLOAT, sourceref=sref))
|
||||
|
||||
|
||||
def test_zp_clobber_allocation():
|
||||
sref = SourceRef("test", 1, 1)
|
||||
zp = Zeropage(ZpOptions.CLOBBER, True)
|
||||
assert zp.available() == 239
|
||||
loc = zp.allocate(VarDef(name="", vartype="var", datatype=DataType.FLOAT, sourceref=sref))
|
||||
assert loc > 3 and loc not in zp.free
|
||||
num, rest = divmod(zp.available(), 5)
|
||||
for _ in range(num-3):
|
||||
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.FLOAT, sourceref=sref))
|
||||
assert zp.available() == 19
|
||||
with pytest.raises(CompileError):
|
||||
# can't allocate because no more sequential bytes, only fragmented
|
||||
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.FLOAT, sourceref=sref))
|
||||
for _ in range(14):
|
||||
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
|
||||
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
|
||||
with pytest.raises(CompileError):
|
||||
zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
|
||||
assert zp.available() == 1
|
||||
zp.allocate(VarDef(name="last", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||
with pytest.raises(CompileError):
|
||||
zp.allocate(VarDef(name="impossible", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||
|
||||
|
||||
def test_zp_efficient_allocation():
|
||||
# free = [0x04, 0x05, 0x06, 0x2a, 0x52, 0xf7, 0xf8, 0xf9, 0xfa]
|
||||
sref = SourceRef("test", 1, 1)
|
||||
zp = Zeropage(ZpOptions.NOCLOBBER, False)
|
||||
assert zp.available() == 9
|
||||
assert 0x2a == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||
assert 0x52 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||
assert 0x04 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
|
||||
assert 0xf7 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
|
||||
assert 0x06 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.BYTE, sourceref=sref))
|
||||
assert 0xf9 == zp.allocate(VarDef(name="", vartype="var", datatype=DataType.WORD, sourceref=sref))
|
||||
assert zp.available() == 0
|
@ -1,159 +0,0 @@
|
||||
; call tests
|
||||
|
||||
~ foo {
|
||||
|
||||
|
||||
|
||||
var word var1 = 99
|
||||
memory word mem1 = $cff0
|
||||
|
||||
var byte varb1 = 99
|
||||
memory byte memb1 = $cff0
|
||||
const word constw = $2355
|
||||
const byte constb = $23
|
||||
const float constf = 3.4556677
|
||||
const str constt = "derp"
|
||||
|
||||
sub sub1 () -> (X?) = $ffdd
|
||||
sub sub2 (A) -> (Y?) = $eecc
|
||||
sub sub3 (XY) -> (Y?) = $ddaa
|
||||
sub sub4 (string: XY, other : A) -> (Y?) = $dd22
|
||||
|
||||
bar2:
|
||||
|
||||
bar: goto $c000
|
||||
goto var1 ; jumps to the address in var1
|
||||
goto mem1 ; jumps to the address in mem1
|
||||
goto [var1]
|
||||
goto [$c000.word]
|
||||
goto [var1]
|
||||
goto [mem1]
|
||||
|
||||
; ----
|
||||
|
||||
goto sub1
|
||||
return sub2 (1 )
|
||||
return sub3 (3)
|
||||
return sub3 ("hello")
|
||||
return sub3 ("hello, there")
|
||||
return sub4 (string="hello, there", other = 42)
|
||||
return sub4 ("hello", 42)
|
||||
return sub4 ("hello", other= 42)
|
||||
return sub4 (string="hello", other = 42)
|
||||
return bar ()
|
||||
goto [AX]
|
||||
goto [var1]
|
||||
goto [mem1] ; comment
|
||||
goto $c000
|
||||
goto 64738
|
||||
64738(1,2) ; @todo should be jsr $64738
|
||||
return 9999() ; @todo should be jmp 9999 ?
|
||||
return [AX]()
|
||||
return [var1] () ; comment
|
||||
return [mem1] ()
|
||||
goto $c000
|
||||
return $c000 ( )
|
||||
goto $c2
|
||||
return $c2()
|
||||
goto [$c2.word]
|
||||
return 33
|
||||
return [$c2.word]
|
||||
return [$c2.word] (4)
|
||||
return [$c2.word] (4)
|
||||
return [$c2.word] (4)
|
||||
return [$c2.word] (4)
|
||||
return [$c2dd.word] ( )
|
||||
goto [$c2dd.word]
|
||||
|
||||
%asm {
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
}
|
||||
|
||||
sub1!()
|
||||
sub2!(11)
|
||||
sub3 !(3)
|
||||
sub3! ("hello")
|
||||
sub3! ("hello, there")
|
||||
sub4! ("hello", 42)
|
||||
sub4! ("hello", other=42)
|
||||
sub4! (string="hello", other = 42)
|
||||
sub4! (string="hello, there", other = 42)
|
||||
|
||||
sub3 (81)
|
||||
sub3 !(81)
|
||||
sub3 !A (81)
|
||||
sub3 !X (81)
|
||||
sub3 !Y (81)
|
||||
sub3 !XY (81)
|
||||
sub3 !AXY (81)
|
||||
|
||||
bar!()
|
||||
bar !()
|
||||
[XY]! ()
|
||||
[XY] ! ()
|
||||
[var1]!()
|
||||
[mem1]!()
|
||||
[$c2.word]!()
|
||||
[$c2dd.word]!()
|
||||
$c000!()
|
||||
$c2!()
|
||||
|
||||
%asm {
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
}
|
||||
|
||||
sub1()
|
||||
sub2(11)
|
||||
sub3 (3)
|
||||
sub3 ("hello")
|
||||
sub3 ("hello, there")
|
||||
sub4 ("hello", 42)
|
||||
sub4 ("hello", other= 42)
|
||||
sub4 (string="hello", other = 42)
|
||||
sub4 (string="hello, there", other = 42)
|
||||
bar ()
|
||||
[AX]()
|
||||
[var1] ( )
|
||||
[mem1] ()
|
||||
A= [$c2.word]
|
||||
A= [$c2dd.word ]
|
||||
$c000()
|
||||
$c2()
|
||||
|
||||
|
||||
%asm {
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
}
|
||||
|
||||
constw()
|
||||
sub1()
|
||||
main.start()
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
|
||||
~ main {
|
||||
|
||||
start:
|
||||
foo.bar()
|
||||
return
|
||||
|
||||
sub unused_sub ()->() {
|
||||
A=X
|
||||
X=Y
|
||||
Y=A
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
%output basic
|
||||
|
||||
%import c64lib
|
||||
|
||||
|
||||
~ main {
|
||||
|
||||
var word value
|
||||
memory word memvalue = $8000
|
||||
|
||||
|
||||
start:
|
||||
A = 100
|
||||
|
||||
|
||||
; conditional if, without conditional expression. needs explicit if status.
|
||||
if_not goto label
|
||||
if_true goto label
|
||||
if_zero goto label
|
||||
if_cc goto label
|
||||
if_lt goto label
|
||||
if_le goto label
|
||||
if_ge goto label
|
||||
if_gt goto label
|
||||
if_cc goto value
|
||||
;if_cc goto memvalue
|
||||
;if_cc goto #memvalue
|
||||
if_cc goto [value]
|
||||
;if_cc goto [memvalue]
|
||||
;if_cc goto $c000
|
||||
;if_cc goto [$c000.word]
|
||||
|
||||
label:
|
||||
; conditional if with a single 'truth' value (register)
|
||||
if_true A goto label2
|
||||
if_not A goto label2
|
||||
if_zero A goto label2
|
||||
if A goto label2
|
||||
if_true X goto label2
|
||||
if_not X goto label2
|
||||
if_zero X goto label2
|
||||
if X goto label2
|
||||
if_true Y goto label2
|
||||
if_not Y goto label2
|
||||
if_zero Y goto label2
|
||||
if Y goto label2
|
||||
if_true XY goto label2
|
||||
if_not XY goto label2
|
||||
if_zero XY goto label2
|
||||
if XY goto label2
|
||||
|
||||
label2:
|
||||
; conditional if with a single 'truth' value (variable)
|
||||
if_true value goto label3
|
||||
if_not value goto label3
|
||||
if_zero value goto label3
|
||||
if value goto label3
|
||||
if_true memvalue goto label3
|
||||
if_not memvalue goto label3
|
||||
if_zero memvalue goto label3
|
||||
if memvalue goto label3
|
||||
|
||||
label3:
|
||||
; conditional if with a single 'truth' value (indirect address)
|
||||
if_true [$c000] goto label4
|
||||
if_not [$c000] goto label4
|
||||
if_zero [$c000] goto label4
|
||||
if [$c000] goto label4
|
||||
if_true [XY] goto label4
|
||||
if_true [AY] goto label4
|
||||
if_true [AX] goto label4
|
||||
|
||||
label4:
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
~ conditionals {
|
||||
var bytevar = 22 + 23
|
||||
var str name = "?"*80
|
||||
var bytevar2 = 23
|
||||
var word wordvar = 22345
|
||||
|
||||
|
||||
start:
|
||||
c64.init_system()
|
||||
|
||||
A = 0
|
||||
printloop:
|
||||
c64scr.print_byte_decimal(A)
|
||||
c64.CHROUT('\n')
|
||||
A++
|
||||
if A <20 goto printloop
|
||||
return
|
||||
|
||||
label1:
|
||||
if_true A==123 goto label1
|
||||
if_true A == 123 goto label1
|
||||
if_true A!=0 goto label1
|
||||
if_not X!=0 goto label1
|
||||
|
||||
if_true A <= 1 goto label1
|
||||
if_true A==1 goto label1
|
||||
if_true A!=1 goto label1
|
||||
if_not X < 1 goto label1
|
||||
if_not Y>1 goto label1
|
||||
if_not X!=1 goto label1
|
||||
|
||||
if_true 22<=Y goto label1
|
||||
if_true A<=22 goto label1
|
||||
if_true A<X goto label1
|
||||
if_true X>Y goto label1
|
||||
if_true X>A goto label1
|
||||
if A<=22 goto label1
|
||||
if A <= 22 goto label1
|
||||
if_zero A goto label1
|
||||
if_zero X goto label1
|
||||
if Y goto label1
|
||||
if_true XY goto label1
|
||||
if_not XY goto label1
|
||||
if A goto label1
|
||||
if_not goto label1
|
||||
|
||||
if_true bytevar<=A goto label2
|
||||
if_true bytevar<=22 goto label2
|
||||
if bytevar<=22 goto label2
|
||||
if bytevar<=22 goto label2
|
||||
if bytevar goto label2
|
||||
if bytevar>bytevar2 goto label2
|
||||
if_zero bytevar goto label2
|
||||
if_not wordvar goto label2
|
||||
if_zero wordvar goto label2
|
||||
if_true wordvar goto label2
|
||||
|
||||
|
||||
label2:
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1,311 +0,0 @@
|
||||
; var definitions and immediate primitive data type tests
|
||||
|
||||
%output raw
|
||||
%zp clobber
|
||||
|
||||
|
||||
%import c64lib
|
||||
|
||||
~ ZP {
|
||||
; ZeroPage block definition:
|
||||
; base address is set to $04 (because $00 and $01 are used by the hardware, and $02/$03 are scratch)
|
||||
; everything here ends up in the zeropage, as long as there is space.
|
||||
; you can NOT put subroutines in here (yet).
|
||||
}
|
||||
|
||||
~ ZP {
|
||||
var zpvar1
|
||||
var zpvar2
|
||||
memory zpmem1 = $f0
|
||||
const zpconst = 1.234
|
||||
var word zpvarw1
|
||||
var word zpvarw2
|
||||
var float zpvarflt1 = 11.11
|
||||
var float zpvarflt2 = 22.22
|
||||
var float zpvarflt3
|
||||
|
||||
}
|
||||
|
||||
~ ZP {
|
||||
; will be merged with other ZP blocks!
|
||||
var zpvar1b = $88
|
||||
}
|
||||
|
||||
|
||||
~ foo {
|
||||
var foovar1
|
||||
memory foomem1 = $f0f0
|
||||
const fooconst = 1.234
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
~ main {
|
||||
; variables
|
||||
var uninitbyte1
|
||||
var byte uninitbyte2
|
||||
var initbyte1 = $12
|
||||
var initbyte1b = true
|
||||
var byte initbyte2 = $12
|
||||
var byte initbyte2b = false
|
||||
var byte initbyte3 = 99.876
|
||||
var initchar1 = '@'
|
||||
var byte initchar2 = '@'
|
||||
var word uninitword
|
||||
var word initword1 = $1234
|
||||
var word initword1b = true
|
||||
var word initword2 = false
|
||||
var word initword3 = 9876.554321
|
||||
var word initword5 = 20
|
||||
var float uninitfloat
|
||||
var float initfloat1 = 0
|
||||
var float initfloat1b = true
|
||||
var float initfloat2 = -1.234e-14
|
||||
var float initfloat3 = 9.87e+14
|
||||
var float initfloat4 = 1.70141183e+38
|
||||
var float initfloat5 = -1.70141183e+38
|
||||
var float initfloat6 = 1.234
|
||||
|
||||
var wordarray( 256 ) uninit_wordarray
|
||||
var wordarray(10) init_wordarray = $1234
|
||||
var wordarray(10) init_wordarrayb = true
|
||||
var array( 256) uninit_bytearray
|
||||
var array(10 ) init_bytearray =$12
|
||||
var array(10 ) init_bytearrayb =true
|
||||
var array(10 ) init_bytearrayc ='@'
|
||||
|
||||
var str text = "hello-null"
|
||||
var strp ptext = 'hello-pascal'
|
||||
var strs stext = 'screencodes-null'
|
||||
var strps pstext = "screencodes-pascal"
|
||||
|
||||
var matrix( 2, 128 ) uninitmatrix
|
||||
var matrix(10, 20) initmatrix1 = $12
|
||||
var matrix(10, 20) initmatrix1b = true
|
||||
var matrix(10, 20) initmatrix1c = '@'
|
||||
var matrix(10, 20) initmatrix1d = 123.456
|
||||
|
||||
; memory-mapped variables
|
||||
memory membyte1 = $cf01
|
||||
memory byte membyte2 = $c222
|
||||
memory word memword1 = $cf03
|
||||
memory float memfloat = $cf04
|
||||
memory array(10 ) membytes = $cf05
|
||||
memory wordarray( 10) memwords = $cf06
|
||||
memory matrix( 10, 20 ) memmatrix = $cf07
|
||||
|
||||
; constants (= names for constant values, can never occur as lvalue)
|
||||
const cbyte1 = 1
|
||||
const cbyte1b = false
|
||||
const byte cbyte2 = 1
|
||||
const byte cbyte3 = '@'
|
||||
const byte cbyte4 = true
|
||||
const word cword1 = false
|
||||
const word cword2 = $1234
|
||||
const word cword5 = 9876.5432
|
||||
const cfloat1 = 1.2345
|
||||
const float cfloat2 = 2.3456
|
||||
const float cfloat2b = cfloat2*3.44
|
||||
const float cfloat3 = true
|
||||
const str ctext3 = "constant-text"
|
||||
const strp ctext4 = "constant-ptext"
|
||||
const strs ctext5 = "constant-stext"
|
||||
const strps ctext6 = "constant-pstext"
|
||||
|
||||
; taking the address of various things:
|
||||
var word vmemaddr1 = &membyte1
|
||||
var word vmemaddr2 = &memword1
|
||||
var word vmemaddr3 = &memfloat
|
||||
var word vmemaddr4 = &membytes
|
||||
var word vmemaddr5 = &memwords
|
||||
var word vmemaddr6 = &memmatrix
|
||||
var word vmemaddr8 = 100*sin(cbyte1)
|
||||
var word vmemaddr9 = cword2+$5432
|
||||
var word vmemaddr10 = cfloat2b
|
||||
|
||||
; taking the address of things from the ZP will work even when it is a var
|
||||
; because zp-vars get assigned a specific address (from a pool). Also, it's a byte.
|
||||
|
||||
var word initword0a = &ZP.zpmem1
|
||||
var initbytea0 = &ZP.zpmem1
|
||||
|
||||
|
||||
; (constant) expressions
|
||||
var word expr_byte1b = -1-2-3-4-$22+$80+ZP.zpconst
|
||||
var byte expr_fault2 = 1 + (8/3)+sin(33) + len("abc")
|
||||
|
||||
|
||||
sin:
|
||||
return
|
||||
|
||||
max:
|
||||
return
|
||||
|
||||
|
||||
start:
|
||||
|
||||
; --- immediate primitive value assignments ----
|
||||
|
||||
A = [$99]
|
||||
A = [$aabb]
|
||||
A = $99
|
||||
A = [cbyte3]
|
||||
A = 0
|
||||
A = '@'
|
||||
A = 1.2345
|
||||
A = false
|
||||
A = 255
|
||||
A = X
|
||||
A = [$99]
|
||||
A = [$c020.byte]
|
||||
A = [$c020]
|
||||
A = cbyte3
|
||||
A = membyte2
|
||||
A = uninitbyte1
|
||||
|
||||
|
||||
XY = 0
|
||||
XY = '@'
|
||||
XY = 1.2345
|
||||
XY = 456.66
|
||||
XY = 65535
|
||||
XY = true
|
||||
XY = false
|
||||
XY = text
|
||||
XY = cbyte3
|
||||
XY = [cbyte3]
|
||||
XY = [cword2]
|
||||
XY = uninitbyte1
|
||||
XY = "text-immediate"
|
||||
AY = "text-immediate"
|
||||
AX = ctext3
|
||||
AX = ""
|
||||
AX = XY
|
||||
AX = Y
|
||||
XY = membyte2
|
||||
XY = membyte2
|
||||
XY = memword1
|
||||
XY = sin
|
||||
XY = &sin ; @todo not yet implemented
|
||||
|
||||
|
||||
[$c000] = A
|
||||
[$c000] = 255
|
||||
[$c000] = '@'
|
||||
[$c000] = true
|
||||
[$c000] = false
|
||||
[$c000] = cbyte3
|
||||
[$c000] = uninitbyte1
|
||||
[$c000] = membyte2
|
||||
[$c000] = cbyte2
|
||||
[$c000] = [cword2]
|
||||
|
||||
[$c000.word] = A
|
||||
[$c000.word] = AX
|
||||
[$c000.word] = cbyte3
|
||||
[$c000.word] = cword2
|
||||
[$c000.word] = ctext3
|
||||
[$c000.word] = 65535
|
||||
[$c000.word] = "text"
|
||||
[$c000.word] = ""
|
||||
[$c000.word] = uninitbyte1
|
||||
[$c000.word] = membyte2
|
||||
[$c000.word] = &membyte2 ; @todo not yet implemented
|
||||
[$c000.word] = [cword2]
|
||||
[$c000.word] = memword1
|
||||
[$c000.float] = 65535
|
||||
[$c000.float] = 456.66
|
||||
[$c000.float] = 1.70141183e+38
|
||||
[$c000.float] = cbyte3
|
||||
[$c000.float] = cword2
|
||||
|
||||
[$c001] = [$c002]
|
||||
[$c111.word] = [$c222]
|
||||
[$c112.word] = [$c223.byte]
|
||||
[$c222.word] = [$c333.word]
|
||||
[$c333.word] = sin
|
||||
[$c333.word] = &sin ; @todo not yet implemented
|
||||
|
||||
|
||||
SC = 0
|
||||
SC = 1
|
||||
SC = false
|
||||
SI = 1
|
||||
SI = 0
|
||||
SI = false
|
||||
|
||||
uninitbyte1 = 99
|
||||
uninitbyte1 = 1.234
|
||||
uninitbyte1 = '@'
|
||||
initbyte1 = 99
|
||||
initbyte1 = 1.234
|
||||
initbyte1 = '@'
|
||||
initbyte1 = A
|
||||
initbyte1 = cbyte3
|
||||
uninitword = 99
|
||||
uninitword = 5.6778
|
||||
uninitword = "test"
|
||||
uninitword = '@'
|
||||
uninitword = A
|
||||
uninitword = XY
|
||||
uninitword = ctext3
|
||||
initword1 = cbyte3
|
||||
initword1 = cword2
|
||||
initfloat1 = 99
|
||||
initfloat1 = 9.8765
|
||||
initfloat1 = '@'
|
||||
initfloat1 = cbyte3
|
||||
initfloat1 = cword2
|
||||
uninitfloat = 99
|
||||
uninitfloat = 9.8765
|
||||
uninitfloat = '@'
|
||||
initword1 = sin
|
||||
initword1 = &sin ; @todo not yet implemented
|
||||
|
||||
|
||||
membyte1 = A
|
||||
membyte1 = cbyte3
|
||||
memword1 = A
|
||||
memword1 = AX
|
||||
memword1 = cbyte3
|
||||
memword1 = cword2
|
||||
memword1 = ctext3
|
||||
|
||||
|
||||
membyte1 = 22
|
||||
memword1 = 2233
|
||||
memfloat = 3.4567
|
||||
memword1 = sin
|
||||
memword1 = &sin ; @todo not yet implemented
|
||||
|
||||
membyte1 = A
|
||||
memword1 = A
|
||||
memword1 = XY
|
||||
memfloat = cbyte3
|
||||
memfloat = cword2
|
||||
|
||||
; float assignments that require ROM functions from c64lib:
|
||||
memfloat = Y
|
||||
memfloat = XY
|
||||
uninitfloat = Y
|
||||
uninitfloat = XY
|
||||
initfloat2 = Y
|
||||
initfloat2 = XY
|
||||
initfloat2 = initbyte2
|
||||
initfloat2 = initword2
|
||||
initfloat1 = uninitfloat
|
||||
initfloat1 = initfloat2
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
~ footer {
|
||||
XY = "text-immediate" ; reuses existing
|
||||
AY = "text-immediate" ; reuses existing
|
||||
AX = "another"
|
||||
AX = ""
|
||||
[$c000.word] = "another" ; must reuse string
|
||||
[$c100.word] = "text-immediate" ; must reuse string
|
||||
[$c200.word] = "" ; must reuse string
|
||||
return
|
||||
}
|
@ -1,148 +0,0 @@
|
||||
; floating point tests
|
||||
|
||||
%output basic
|
||||
|
||||
%import c64lib
|
||||
|
||||
~ main_testing {
|
||||
start:
|
||||
var float myfloat1 = 1234.56789
|
||||
var float myfloat2 = 9876.54321
|
||||
var float myfloatneg1 = -555.666
|
||||
var float myfloat_large1 = 987654.1111
|
||||
var float myfloat_large2 = -123456.2222
|
||||
var str myfloatstr = "1234.998877"
|
||||
var float myfloatzero = 0
|
||||
var float myfloatsmall1 = 1.234
|
||||
var float myfloatsmall2 = 2.6677
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
~ main {
|
||||
|
||||
byte bbvar
|
||||
|
||||
float@
|
||||
|
||||
var float flt_pi = 3.141592653589793
|
||||
var float flt_minus32768 = -32768
|
||||
var float flt_1 = 1
|
||||
var float flt_half_sqr2 = 0.7071067811865476
|
||||
var float flt_sqr2 = 1.4142135623730951
|
||||
var float flt_minus_half = -.5
|
||||
var float flt_log_2 = 0.6931471805599453
|
||||
var float flt_10 = 10
|
||||
var float flt_1e9 = 1000000000
|
||||
var float flt_half = .5
|
||||
var float flt_one_over_log_2 = 1.4426950408889634
|
||||
var float flt_half_pi = 1.5707963267948966
|
||||
var float flt_double_pi = 6.283185307179586
|
||||
var float flt_point25 = .25
|
||||
var float my_float
|
||||
memory .word some_address = $ccdd
|
||||
memory .byte some_addressb = $ccee
|
||||
var .byte bytevar = $cc
|
||||
var .word wordvar = $cdef
|
||||
|
||||
|
||||
sub printflt (float: AY) -> (?) {
|
||||
c64.MOVFM!(AY)
|
||||
goto c64.FPRINTLN
|
||||
; c64.FOUT!()
|
||||
; c64scr.print_string!(AY)
|
||||
;goto c64.CHROUT('\n')
|
||||
}
|
||||
|
||||
|
||||
start:
|
||||
; assign some float values to the memory
|
||||
AY = &flt_pi
|
||||
some_address = & flt_pi
|
||||
some_address = 4123.2342342222
|
||||
[$c000.word] = & flt_pi
|
||||
|
||||
my_float ++
|
||||
my_float += 1
|
||||
my_float += 0.5
|
||||
my_float += 999.222
|
||||
my_float --
|
||||
my_float -= 1
|
||||
my_float -= 0.5
|
||||
my_float -= 999.222
|
||||
|
||||
; print some floating points from source and compare them with ROM
|
||||
|
||||
printflt(&flt_pi)
|
||||
printflt(&c64.FL_PIVAL)
|
||||
|
||||
printflt(&flt_minus32768)
|
||||
printflt(&c64.FL_N32768)
|
||||
|
||||
printflt(&flt_1)
|
||||
printflt(&c64.FL_FONE)
|
||||
|
||||
printflt(&flt_half_sqr2)
|
||||
printflt( & c64.FL_SQRHLF)
|
||||
|
||||
printflt(&flt_sqr2)
|
||||
printflt(&c64.FL_SQRTWO)
|
||||
|
||||
printflt(&flt_minus_half)
|
||||
printflt(&c64.FL_NEGHLF)
|
||||
|
||||
printflt(&flt_log_2)
|
||||
printflt(&c64.FL_LOG2)
|
||||
|
||||
printflt(&flt_10)
|
||||
printflt(&c64.FL_TENC)
|
||||
|
||||
printflt(&flt_1e9)
|
||||
printflt(&c64.FL_NZMIL)
|
||||
|
||||
printflt(&flt_half)
|
||||
printflt(&c64.FL_FHALF)
|
||||
|
||||
printflt(&flt_one_over_log_2)
|
||||
printflt(&c64.FL_LOGEB2)
|
||||
|
||||
printflt(&flt_half_pi)
|
||||
printflt(&c64.FL_PIHALF)
|
||||
|
||||
printflt(&flt_double_pi)
|
||||
printflt(&c64.FL_TWOPI)
|
||||
|
||||
printflt(& flt_point25)
|
||||
printflt(&c64.FL_FR4)
|
||||
|
||||
reg_to_float:
|
||||
c64.CHROUT!('\n')
|
||||
|
||||
A=33
|
||||
X=44
|
||||
Y=55
|
||||
|
||||
my_float = A
|
||||
printflt(&my_float)
|
||||
|
||||
my_float = X
|
||||
printflt(&my_float)
|
||||
|
||||
my_float = Y
|
||||
printflt(&my_float)
|
||||
|
||||
XY = 11122
|
||||
my_float = XY
|
||||
printflt(&my_float)
|
||||
|
||||
AX = 33344
|
||||
my_float = AX
|
||||
printflt(&my_float)
|
||||
|
||||
AY = 55566
|
||||
my_float = AY
|
||||
printflt(&my_float)
|
||||
|
||||
return
|
||||
}
|
Binary file not shown.
@ -1,2 +0,0 @@
|
||||
; this assembly code is included as-is
|
||||
nop
|
@ -1,51 +0,0 @@
|
||||
%output basic
|
||||
|
||||
%import c64lib
|
||||
|
||||
~ main {
|
||||
var .str name = "????????????????????????????????????????????????????????????????????????????????" ; 80
|
||||
var .word orig_irq
|
||||
|
||||
start:
|
||||
c64.init_system()
|
||||
|
||||
orig_irq = c64.CINV
|
||||
SI = 1
|
||||
c64.CINV = &irq_handler
|
||||
SI = 0
|
||||
|
||||
|
||||
c64scr.print_string("enter your name: ")
|
||||
c64scr.input_chars(name)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
blop:
|
||||
return
|
||||
%breakpoint
|
||||
return
|
||||
|
||||
; yeah!
|
||||
|
||||
c64scr.print_string("thank you, mr or mrs: ")
|
||||
c64scr.print_string(name)
|
||||
c64.CHROUT('\n')
|
||||
|
||||
SI = 1
|
||||
c64.CINV = orig_irq
|
||||
SI = 0
|
||||
|
||||
return
|
||||
|
||||
|
||||
irq_handler:
|
||||
%asm {
|
||||
lda c64.SFDX
|
||||
cmp #$40 ; nothing pressed?
|
||||
beq +
|
||||
inc c64.EXTCOL ; otherwise change color
|
||||
+ jmp c64.IRQDFRT
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,106 +0,0 @@
|
||||
%output basic
|
||||
%import c64lib
|
||||
%import mathlib
|
||||
|
||||
~ main {
|
||||
var .str name = '?' * 80
|
||||
var .str guess = '?' * 80
|
||||
var secretnumber
|
||||
var attempts_left = 10
|
||||
|
||||
|
||||
start:
|
||||
c64.init_system()
|
||||
|
||||
A = c64.VMCSB
|
||||
A |= 2
|
||||
c64.VMCSB = A
|
||||
c64.VMCSB |= 2 ; @todo when this works it replaces the three lines above
|
||||
|
||||
; greeting
|
||||
c64scr.print_string("Enter your name: ")
|
||||
Y = c64scr.input_chars(name)
|
||||
c64.CHROUT('\n')
|
||||
c64.CHROUT('\n')
|
||||
c64scr.print_string("Hello, ")
|
||||
c64scr.print_string(name)
|
||||
c64.CHROUT('.')
|
||||
c64.CHROUT('\n')
|
||||
|
||||
; create a secret random number from 1-100
|
||||
c64.RNDA(0) ; fac = rnd(0)
|
||||
c64.MUL10() ; fac *= 10
|
||||
c64.MUL10() ; .. and now *100
|
||||
c64.FADDH() ; add 0.5..
|
||||
c64.FADDH() ; and again, so +1 total
|
||||
AY = c64flt.GETADRAY()
|
||||
secretnumber=A
|
||||
;A=math.randbyte()
|
||||
;A+=c64.RASTER
|
||||
;A-=c64.TIME_LO
|
||||
;X,secretnumber=math.divmod_bytes(A, 99)
|
||||
|
||||
c64scr.print_string("I am thinking of a number from 1 to 100!You'll have to guess it!\n")
|
||||
|
||||
printloop:
|
||||
c64scr.print_string("\nYou have ")
|
||||
c64scr.print_byte_decimal(attempts_left)
|
||||
c64scr.print_string(" guess")
|
||||
|
||||
; @todo comparison expression so we can do if attempts_left>0 ...
|
||||
A = attempts_left
|
||||
A--
|
||||
if_zero A goto ask_guess
|
||||
c64scr.print_string("es")
|
||||
ask_guess:
|
||||
c64scr.print_string(" left.\nWhat is your next guess? ")
|
||||
Y = c64scr.input_chars(guess)
|
||||
c64.CHROUT('\n')
|
||||
[$22.word] = guess
|
||||
c64.FREADSTR(A)
|
||||
AY = c64flt.GETADRAY()
|
||||
A -= secretnumber ; @todo condition so we can do if guess > secretnumber.... # @todo "removed statement that has no effect" is WRONG!!
|
||||
A -= secretnumber ; # @todo "removed statement that has no effect" is WRONG!!
|
||||
if_zero goto correct_guess
|
||||
if_gt goto too_high
|
||||
c64scr.print_string("That is too ")
|
||||
c64scr.print_string("low!\n")
|
||||
goto continue
|
||||
|
||||
correct_guess:
|
||||
c64scr.print_string("\nThat's my number, impressive!\n")
|
||||
goodbye()
|
||||
return
|
||||
|
||||
too_high:
|
||||
c64scr.print_string("That is too ")
|
||||
c64scr.print_string("high!\n")
|
||||
|
||||
continue:
|
||||
attempts_left--
|
||||
if_zero attempts_left goto game_over
|
||||
goto printloop
|
||||
|
||||
game_over:
|
||||
c64scr.print_string("\nToo bad! It was: ")
|
||||
c64scr.print_byte_decimal(secretnumber)
|
||||
c64.CHROUT('\n')
|
||||
return goodbye()
|
||||
goodbye() ; @todo fix subroutine usage tracking, it doesn't register this one
|
||||
return
|
||||
return
|
||||
return
|
||||
return
|
||||
return
|
||||
return
|
||||
return
|
||||
return
|
||||
return
|
||||
return
|
||||
|
||||
sub goodbye ()->() {
|
||||
c64scr.print_string("\nThanks for playing. Bye!\n")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
@ -1,227 +0,0 @@
|
||||
; source IL file
|
||||
; these are comments
|
||||
; line 2 comment
|
||||
; line 3 comment
|
||||
|
||||
|
||||
%output basic ; create a c-64 program with basic SYS call to launch it
|
||||
%zp restore , clobber ; clobber over the zp memory normally used by basic/kernal rom, frees up more zp
|
||||
|
||||
options
|
||||
|
||||
|
||||
%blocks
|
||||
|
||||
|
||||
~ main
|
||||
{
|
||||
; this is the main block with the start routine.
|
||||
|
||||
memory screen = $d021
|
||||
memory border = $d020
|
||||
memory cursor = 646
|
||||
const cyan = 3
|
||||
const .word redw = $2002
|
||||
var uninitbyte1
|
||||
var .byte uninitbyte2
|
||||
var .word uninitword1
|
||||
var .byte initbyte33 = 33
|
||||
var .word initword9876 = $9876
|
||||
var .array(10) bytes2 = $44
|
||||
var .wordarray(10) words2 = $bbcc
|
||||
]
|
||||
|
||||
|
||||
start: ;foo
|
||||
X = 55
|
||||
Y = $77
|
||||
Y = X
|
||||
Y = X
|
||||
X = 66
|
||||
screen = 0
|
||||
screen = 66
|
||||
border = 66
|
||||
cursor = 66
|
||||
X = 66
|
||||
Y = 66
|
||||
A = 66
|
||||
X = 66
|
||||
Y = 66
|
||||
A = 66
|
||||
border = 66
|
||||
cursor = 66
|
||||
border = 66
|
||||
cursor = 66
|
||||
border = false
|
||||
border = true
|
||||
border = 0
|
||||
border = 0
|
||||
[$d020] = 0
|
||||
screen = X
|
||||
cursor = Y
|
||||
[646] = Y
|
||||
uninitbyte1 = 123
|
||||
uninitword1 = 12345
|
||||
uninitword1 = A
|
||||
initbyte33 = 133
|
||||
initword9876 = 9999
|
||||
initword9876 = 1
|
||||
initword9876 = Y
|
||||
|
||||
initbyte33 ++
|
||||
initword9876 ++
|
||||
|
||||
Y = 0
|
||||
Y = 1
|
||||
Y = 2
|
||||
Y = true
|
||||
Y = false
|
||||
A = 0
|
||||
Y = 0
|
||||
X = 0
|
||||
|
||||
; [646,Y] = [$d020,X]
|
||||
|
||||
|
||||
_loop: block2.zpw1 ++
|
||||
A ++
|
||||
X ++
|
||||
Y ++
|
||||
[$d020] ++
|
||||
A --
|
||||
X --
|
||||
Y--
|
||||
[$d020]--
|
||||
block2.zpw2 = 99
|
||||
fidget.subroutine()
|
||||
goto _loop
|
||||
return 155,2,%00000101 ; will end up in A, X, Y
|
||||
}
|
||||
|
||||
|
||||
|
||||
~ block2 {
|
||||
|
||||
return
|
||||
|
||||
sub memsub () -> () = $fff2
|
||||
|
||||
sub customsub (Y)->() {
|
||||
|
||||
%asm {
|
||||
nop
|
||||
nop
|
||||
lda #99
|
||||
nop
|
||||
nop
|
||||
}
|
||||
|
||||
A=2
|
||||
X=33
|
||||
return fidget.subroutine()
|
||||
|
||||
}
|
||||
|
||||
|
||||
somelabel1222:
|
||||
|
||||
customsub(2)
|
||||
return
|
||||
|
||||
var zp1
|
||||
var zp2
|
||||
var .word zpw1
|
||||
var .word zpw2
|
||||
var .array(100) some_array1 = $aa ; initialized with $aa bytes
|
||||
const white = 1
|
||||
const red = 2
|
||||
const .word border2 = $d020
|
||||
const .word screen2 = $d021
|
||||
return
|
||||
}
|
||||
|
||||
~ block3 {
|
||||
somelabel1:
|
||||
%asm {
|
||||
nop
|
||||
nop
|
||||
nop
|
||||
}
|
||||
|
||||
goto somelabel1
|
||||
goto block2.somelabel1222
|
||||
A=99
|
||||
X=99
|
||||
Y=99
|
||||
A=99
|
||||
X=99
|
||||
Y=99
|
||||
A=99
|
||||
X=99
|
||||
Y=99
|
||||
[$d020]=55
|
||||
[$d021]=55
|
||||
[$d020]=55
|
||||
[$d021]=55
|
||||
|
||||
A=1
|
||||
X=1
|
||||
[$d020]=1
|
||||
[$aa]=1
|
||||
[$bb]=1
|
||||
Y=1
|
||||
X=1
|
||||
A=1
|
||||
[$d021]=1
|
||||
A=2
|
||||
[$cc]=1
|
||||
[$cd]=1
|
||||
[$ce]=1
|
||||
[$cf]=1
|
||||
[$cf00]=1
|
||||
[$cf01]=2
|
||||
[$cf02]=1
|
||||
Y=2
|
||||
A=2
|
||||
X=1
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
; comments before block 4
|
||||
|
||||
~ block4 {
|
||||
A=1
|
||||
A=2
|
||||
A=3
|
||||
A=4
|
||||
A=5
|
||||
X=8
|
||||
X=9
|
||||
Y=10
|
||||
[$cc]=1
|
||||
[$cc]=2
|
||||
[$cc]=3
|
||||
[$cc]=4
|
||||
[$dd]=A
|
||||
[$dd]=X
|
||||
[$dd]=Y
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
~ fidget $0c00
|
||||
{
|
||||
|
||||
subroutine:
|
||||
|
||||
; subroutine (A, X, Y)
|
||||
;[border2] = red
|
||||
;[screen2] = white
|
||||
return $99
|
||||
|
||||
}
|
||||
|
||||
|
||||
; comment at end
|
||||
; another one
|
@ -1,27 +0,0 @@
|
||||
%output prg
|
||||
%address $c000
|
||||
|
||||
~ test {
|
||||
var .byte localvar = 33
|
||||
return
|
||||
}
|
||||
|
||||
~ main {
|
||||
start:
|
||||
;A=0
|
||||
;[$d020]=A
|
||||
;X=false
|
||||
;Y=true
|
||||
;return
|
||||
|
||||
;included_assembly
|
||||
;%asminclude " included.sourcelynx walla derp ", test_include
|
||||
;%asminclude " included.sourcelynx walla derp ", "test_include"
|
||||
|
||||
;included_binary
|
||||
;%asmbinary " included.binary 234 "
|
||||
;%asmbinary " included.binary", $40
|
||||
%asmbinary "included.binary", $40, $200
|
||||
|
||||
return
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
|
||||
%output basic ; create a c-64 program with basic SYS call to launch it
|
||||
|
||||
|
||||
%import c64lib ; searched in several locations and with .ill file extension added
|
||||
|
||||
~ main
|
||||
{
|
||||
memory screen2 = $0401
|
||||
memory screen3 = $0402
|
||||
memory .word screenw = $0500
|
||||
|
||||
; ascii to petscii, 0 terminated
|
||||
var .str hello = "hello everyone out there."
|
||||
|
||||
; ascii to petscii, with length as first byte
|
||||
var .strp hellopascalstr = "Hello!\0x00\x01\x02d ag\0x01."
|
||||
|
||||
; ascii to screen codes, 0 terminated
|
||||
var .strs hello_screen = "Hello!\n guys123."
|
||||
|
||||
; ascii to screen codes, length as first byte
|
||||
var .strps hellopascal_screen = "Hello! \n."
|
||||
|
||||
var .str hello2 = "@@\f\b\n\r\t@@"
|
||||
|
||||
start:
|
||||
global2.make_screen_black()
|
||||
|
||||
A='?'
|
||||
[$d020] = '?'
|
||||
[$d021] = '?'
|
||||
[$d022] = '?'
|
||||
[$d023] = 'q'
|
||||
c64.BGCOL0 = 'a'
|
||||
screen2 = 'a'
|
||||
screen3 = 'a'
|
||||
screenw = '2'
|
||||
A='?'
|
||||
X='?'
|
||||
Y='?'
|
||||
A='\002'
|
||||
X=A
|
||||
A='\xf2'
|
||||
X=A
|
||||
c64.CHROUT('A')
|
||||
A='\f'
|
||||
X=A
|
||||
A='\b'
|
||||
X=A
|
||||
A='\n'
|
||||
X=A
|
||||
A='\r'
|
||||
X=A
|
||||
A='\t'
|
||||
A='0'
|
||||
c64.CHROUT('0')
|
||||
c64.CHROUT('1')
|
||||
c64.CHROUT('2')
|
||||
c64scr.print_string(hello)
|
||||
return c64.CHROUT('!')
|
||||
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
~ global2 {
|
||||
|
||||
make_screen_black:
|
||||
c64.EXTCOL = 0
|
||||
c64.BGCOL0 = 0
|
||||
c64.COLOR = 3
|
||||
Y = true
|
||||
return
|
||||
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
%output basic ; create a c-64 program with basic SYS to() launch it
|
||||
|
||||
%import "c64lib.ill"
|
||||
|
||||
~ main
|
||||
{
|
||||
var .str greeting = "hello world!\r12345678 is a big number.\r"
|
||||
var .strp p_greeting = "hello world!\r12345678 is a big number.\r"
|
||||
const .word BORDER = $d020
|
||||
|
||||
start:
|
||||
c64scr.print_pimmediate() ; this prints the pstring immediately following it
|
||||
%asm {
|
||||
.strp "hello-pimmediate!{cr}"
|
||||
}
|
||||
|
||||
c64scr.print_byte_decimal0 (19)
|
||||
c64.CHROUT (13)
|
||||
c64scr.print_byte_decimal (19)
|
||||
c64.CHROUT (13)
|
||||
|
||||
|
||||
c64scr.print_word_decimal0 ($0102)
|
||||
c64.CHROUT (13)
|
||||
c64scr.print_word_decimal ($0102)
|
||||
c64.CHROUT (13)
|
||||
return
|
||||
|
||||
start2:
|
||||
global2.make_screen_black()
|
||||
c64.CLEARSCR()
|
||||
c64scr.print_string(greeting)
|
||||
c64scr.print_pstring(p_greeting)
|
||||
c64scr.print_byte_decimal(0)
|
||||
c64scr.print_byte_hex(0, 0)
|
||||
c64.CHROUT(13)
|
||||
c64scr.print_byte_decimal(13)
|
||||
c64scr.print_byte_hex(0, 13)
|
||||
c64.CHROUT(13)
|
||||
c64scr.print_byte_decimal(255)
|
||||
c64scr.print_byte_hex(0, 254)
|
||||
c64scr.print_byte_hex(0, 129)
|
||||
c64.CHROUT(13)
|
||||
|
||||
c64.CHROUT(13)
|
||||
c64scr.print_word_decimal($0100)
|
||||
c64.CHROUT(13)
|
||||
return
|
||||
|
||||
|
||||
}
|
||||
|
||||
~ global2 {
|
||||
|
||||
make_screen_black:
|
||||
c64.EXTCOL = 0
|
||||
c64.BGCOL0 = 0
|
||||
c64.COLOR = 3
|
||||
return
|
||||
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
~ main {
|
||||
var .float flt1 = 9.87e-21
|
||||
var .float flt = -9.87e-21
|
||||
const .word border = $0099
|
||||
var counter = 1
|
||||
|
||||
start:
|
||||
counter ++
|
||||
main.counter ++
|
||||
; @todo float augassign
|
||||
flt += 1000.1
|
||||
flt *= 2.34
|
||||
flt *= flt
|
||||
|
||||
;[border] &= 2 ; @todo augassign on dereference
|
||||
|
||||
flt = flt+ 1 ; @todo optimize into augassign
|
||||
XY*=3 ; @todo operator
|
||||
XY=XY/0 ; @todo zerodiv (during expression to code generation) @todo operator
|
||||
XY=XY//0 ; @todo zerodiv (during expression to code generation) @todo operator
|
||||
|
||||
; @todo incr by more than 1
|
||||
[AX]++
|
||||
[AX]++
|
||||
A=0
|
||||
; @todo decr by more than 1
|
||||
[AX]--
|
||||
[AX]--
|
||||
|
||||
|
||||
block2.b2var++
|
||||
block2.start()
|
||||
return 44.123
|
||||
|
||||
sub goodbye ()->() {
|
||||
var xxxxxx ; @todo vars in sub?
|
||||
memory y = $c000 ; @todo memvars in sub?
|
||||
const q = 22 ; @todo const in sub?
|
||||
|
||||
y++
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
sub derp ()->() {
|
||||
const q = 22
|
||||
A = q *4 ; @todo fix scope not found error
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
~ block2 {
|
||||
var b2var = 0
|
||||
|
||||
start:
|
||||
return
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
~ main {
|
||||
|
||||
var b1
|
||||
var .word w1
|
||||
var .float f1
|
||||
memory .byte border = $d020
|
||||
|
||||
start:
|
||||
X++
|
||||
X++
|
||||
return ; dummy
|
||||
X+=2
|
||||
return ; dummy
|
||||
X+=255
|
||||
return ; dummy
|
||||
X--
|
||||
X--
|
||||
return ; dummy
|
||||
X-=2
|
||||
return ; dummy
|
||||
X-=255
|
||||
return ; dummy
|
||||
XY++
|
||||
XY++
|
||||
return ; dummy
|
||||
XY+=2
|
||||
return ; dummy
|
||||
XY+=255
|
||||
return ; dummy
|
||||
XY--
|
||||
XY--
|
||||
return ; dummy
|
||||
XY-=2
|
||||
return ; dummy
|
||||
XY-=255
|
||||
return ; dummy
|
||||
b1++
|
||||
b1++
|
||||
w1++
|
||||
w1++
|
||||
f1++
|
||||
f1++
|
||||
b1--
|
||||
b1--
|
||||
w1--
|
||||
w1--
|
||||
f1--
|
||||
f1--
|
||||
b1+=255
|
||||
w1+=255
|
||||
f1+=255
|
||||
b1-=255
|
||||
w1-=255
|
||||
f1-=255
|
||||
|
||||
[$c000]++
|
||||
[$c000]++
|
||||
[$c000]--
|
||||
[$c000]--
|
||||
[border]++
|
||||
[border]++
|
||||
[border]--
|
||||
[border]--
|
||||
|
||||
X ++
|
||||
X ++
|
||||
X += 255
|
||||
Y--
|
||||
Y--
|
||||
Y-=255
|
||||
XY ++
|
||||
XY ++
|
||||
XY += 255
|
||||
|
||||
;[$c000]+=2
|
||||
;[$c000]+=255
|
||||
;[$c000]+=255
|
||||
;[$c000 .word]+=255
|
||||
;[$c000]-=2
|
||||
;[$c000]-=255
|
||||
;[$c000 .word]-=255
|
||||
;[border]+=2
|
||||
;[border]+=255
|
||||
;[border]-=2
|
||||
;[border]-=255
|
||||
%noreturn
|
||||
}
|
@ -1 +0,0 @@
|
||||
# package
|
@ -1,141 +0,0 @@
|
||||
"""
|
||||
Simplistic 8/16 bit Virtual Machine to execute a stack based instruction language.
|
||||
Core data structures and definitions.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
import enum
|
||||
import struct
|
||||
import array
|
||||
from typing import Callable, Union
|
||||
from il65.codegen.shared import mflpt5_to_float, to_mflpt5
|
||||
|
||||
|
||||
class DataType(enum.IntEnum):
|
||||
BOOL = 1
|
||||
BYTE = 2
|
||||
SBYTE = 3
|
||||
WORD = 4
|
||||
SWORD = 5
|
||||
FLOAT = 6
|
||||
ARRAY_BYTE = 7
|
||||
ARRAY_SBYTE = 8
|
||||
ARRAY_WORD = 9
|
||||
ARRAY_SWORD = 10
|
||||
MATRIX_BYTE = 11
|
||||
MATRIX_SBYTE = 12
|
||||
|
||||
@staticmethod
|
||||
def guess_datatype_for(value: Union[bool, int, float, bytearray, array.array]) -> 'DataType':
|
||||
if isinstance(value, int):
|
||||
if 0 <= value <= 255:
|
||||
return DataType.BYTE
|
||||
if -128 <= value <= 127:
|
||||
return DataType.SBYTE
|
||||
if 0 <= value <= 65535:
|
||||
return DataType.WORD
|
||||
if -32768 <= value <= 32767:
|
||||
return DataType.SWORD
|
||||
raise OverflowError("integer value too large for byte or word", value)
|
||||
if isinstance(value, bool):
|
||||
return DataType.BOOL
|
||||
if isinstance(value, float):
|
||||
return DataType.FLOAT
|
||||
if isinstance(value, bytearray):
|
||||
return DataType.ARRAY_BYTE
|
||||
if isinstance(value, array.array):
|
||||
if value.typecode == "B":
|
||||
return DataType.ARRAY_BYTE
|
||||
if value.typecode == "H":
|
||||
return DataType.ARRAY_WORD
|
||||
if value.typecode == "b":
|
||||
return DataType.ARRAY_SBYTE
|
||||
if value.typecode == "h":
|
||||
return DataType.ARRAY_SWORD
|
||||
raise ValueError("invalid array typecode", value.typecode)
|
||||
raise TypeError("invalid value type", value)
|
||||
|
||||
|
||||
class ExecutionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class TerminateExecution(SystemExit):
|
||||
pass
|
||||
|
||||
|
||||
class MemoryAccessError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Memory:
|
||||
def __init__(self):
|
||||
self.mem = bytearray(65536)
|
||||
self.readonly = bytearray(65536)
|
||||
self.mmap_io_charout_addr = -1
|
||||
self.mmap_io_charout_callback = None
|
||||
self.mmap_io_charin_addr = -1
|
||||
self.mmap_io_charin_callback = None
|
||||
|
||||
def mark_readonly(self, start: int, end: int) -> None:
|
||||
self.readonly[start:end+1] = [1] * (end-start+1)
|
||||
|
||||
def memmapped_io_charout(self, address: int, callback: Callable) -> None:
|
||||
self.mmap_io_charout_addr = address
|
||||
self.mmap_io_charout_callback = callback
|
||||
|
||||
def memmapped_io_charin(self, address: int, callback: Callable) -> None:
|
||||
self.mmap_io_charin_addr = address
|
||||
self.mmap_io_charin_callback = callback
|
||||
|
||||
def get_byte(self, index: int) -> int:
|
||||
if self.mmap_io_charin_addr == index:
|
||||
self.mem[index] = self.mmap_io_charin_callback()
|
||||
return self.mem[index]
|
||||
|
||||
def get_bytes(self, startindex: int, amount: int) -> bytearray:
|
||||
return self.mem[startindex: startindex+amount]
|
||||
|
||||
def get_sbyte(self, index: int) -> int:
|
||||
if self.mmap_io_charin_addr == index:
|
||||
self.mem[index] = self.mmap_io_charin_callback()
|
||||
return struct.unpack("b", self.mem[index:index+1])[0]
|
||||
|
||||
def get_word(self, index: int) -> int:
|
||||
return self.mem[index] + 256 * self.mem[index+1]
|
||||
|
||||
def get_sword(self, index: int) -> int:
|
||||
return struct.unpack("<h", self.mem[index:index+2])[0]
|
||||
|
||||
def get_float(self, index: int) -> float:
|
||||
return mflpt5_to_float(self.mem[index: index+5])
|
||||
|
||||
def set_byte(self, index: int, value: int) -> None:
|
||||
if self.readonly[index]:
|
||||
raise MemoryAccessError("read-only", index)
|
||||
self.mem[index] = value
|
||||
if self.mmap_io_charout_addr == index:
|
||||
self.mmap_io_charout_callback(value)
|
||||
|
||||
def set_sbyte(self, index: int, value: int) -> None:
|
||||
if self.readonly[index]:
|
||||
raise MemoryAccessError("read-only", index)
|
||||
self.mem[index] = struct.pack("b", bytes([value]))[0]
|
||||
if self.mmap_io_charout_addr == index:
|
||||
self.mmap_io_charout_callback(self.mem[index])
|
||||
|
||||
def set_word(self, index: int, value: int) -> None:
|
||||
if self.readonly[index] or self.readonly[index+1]:
|
||||
raise MemoryAccessError("read-only", index)
|
||||
self.mem[index], self.mem[index + 1] = struct.pack("<H", value)
|
||||
|
||||
def set_sword(self, index: int, value: int) -> None:
|
||||
if self.readonly[index] or self.readonly[index+1]:
|
||||
raise MemoryAccessError("read-only", index)
|
||||
self.mem[index], self.mem[index + 1] = struct.pack("<h", value)
|
||||
|
||||
def set_float(self, index: int, value: float) -> None:
|
||||
if any(self.readonly[index:index+5]):
|
||||
raise MemoryAccessError("read-only", index)
|
||||
self.mem[index: index+5] = to_mflpt5(value)
|
@ -1,26 +0,0 @@
|
||||
; source code for a tinyvm program; memory mapped I/O
|
||||
%block b1
|
||||
%vardefs
|
||||
const byte chr_i 105
|
||||
const byte chr_r 114
|
||||
const byte chr_m 109
|
||||
const byte chr_e 101
|
||||
const byte chr_n 110
|
||||
const byte chr_EOL 10
|
||||
const word chrout 53248
|
||||
const word chrin 53249
|
||||
%end_vardefs
|
||||
%instructions
|
||||
loop:
|
||||
push chrin
|
||||
syscall memread_byte
|
||||
push chrout
|
||||
swap
|
||||
syscall memwrite_byte
|
||||
push chrout
|
||||
push chr_EOL
|
||||
syscall memwrite_byte
|
||||
syscall delay
|
||||
jump loop
|
||||
%end_instructions
|
||||
%end_block ;b1
|
@ -1,22 +0,0 @@
|
||||
; source code for a tinyvm program for the timer
|
||||
%block b1_timer
|
||||
%vardefs
|
||||
var byte teller_timer 32
|
||||
const word screenloc_timer 1028
|
||||
const byte one_timer 1
|
||||
%end_vardefs
|
||||
|
||||
%instructions
|
||||
push teller_timer
|
||||
push one_timer
|
||||
add
|
||||
dup
|
||||
pop teller_timer
|
||||
push screenloc_timer
|
||||
swap
|
||||
syscall memwrite_word
|
||||
return 0
|
||||
%end_instructions
|
||||
%subblocks
|
||||
%end_subblocks
|
||||
%end_block ;b1_timer
|
@ -1,39 +0,0 @@
|
||||
; source code for a tinyvm program
|
||||
%block b1
|
||||
%vardefs
|
||||
var word teller 0
|
||||
var word numbertoprint 0
|
||||
const byte one 1
|
||||
const word thousand 1000
|
||||
const byte space_chr 32
|
||||
const word screenstart 1024
|
||||
%end_vardefs
|
||||
|
||||
%instructions
|
||||
back:
|
||||
push teller
|
||||
push one
|
||||
add
|
||||
dup
|
||||
dup
|
||||
dup
|
||||
push screenstart
|
||||
swap
|
||||
syscall memwrite_word
|
||||
pop teller
|
||||
call 1 printnumber
|
||||
push thousand
|
||||
cmp_lt
|
||||
jump_if_true back
|
||||
return 0
|
||||
printnumber:
|
||||
syscall decimalstr_unsigned
|
||||
syscall printstr
|
||||
push space_chr
|
||||
syscall printchr
|
||||
return 0
|
||||
%end_instructions
|
||||
|
||||
%subblocks
|
||||
%end_subblocks
|
||||
%end_block ;b1
|
@ -1,32 +0,0 @@
|
||||
"""
|
||||
Simplistic 8/16 bit Virtual Machine to execute a stack based instruction language.
|
||||
Main entry point to launch a VM to execute the given programs.
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
import sys
|
||||
from .parse import Parser
|
||||
from .vm import VM
|
||||
|
||||
mainprogram = None
|
||||
timerprogram = None
|
||||
|
||||
if len(sys.argv) >= 2:
|
||||
source = open(sys.argv[1]).read()
|
||||
parser = Parser(source)
|
||||
mainprogram = parser.parse()
|
||||
|
||||
if len(sys.argv) == 3:
|
||||
source = open(sys.argv[2]).read()
|
||||
parser = Parser(source)
|
||||
timerprogram = parser.parse()
|
||||
|
||||
if len(sys.argv) not in (2, 3):
|
||||
raise SystemExit("provide 1 or 2 program file names as arguments")
|
||||
|
||||
# ZeroPage and hardware stack of a 6502 cpu are off limits for now
|
||||
VM.readonly_mem_ranges = [(0x00, 0xff), (0x100, 0x1ff)]
|
||||
vm = VM(mainprogram, timerprogram)
|
||||
vm.enable_charscreen(0x0400, 40, 25)
|
||||
vm.run()
|
@ -1,218 +0,0 @@
|
||||
"""
|
||||
Simplistic 8/16 bit Virtual Machine to execute a stack based instruction language.
|
||||
Parser for the simplistic text based program representation
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
import array
|
||||
from typing import Optional, List, Tuple, Dict, Any
|
||||
from .program import Opcode, Program, Block, Variable, Instruction, Value
|
||||
from .vm import StackValueType
|
||||
from .core import DataType
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Parser:
|
||||
def __init__(self, source: str) -> None:
|
||||
self.source = source.splitlines()
|
||||
self.lineno = 0
|
||||
|
||||
def parse(self) -> Program:
|
||||
blocklist = []
|
||||
while self.lineno < len(self.source):
|
||||
self.skip_empty()
|
||||
blocklist.append(self.parse_block(None))
|
||||
return Program(blocklist)
|
||||
|
||||
def skip_empty(self) -> None:
|
||||
while self.lineno < len(self.source):
|
||||
line = self.source[self.lineno].strip()
|
||||
if not line or line.startswith(";"):
|
||||
self.lineno += 1
|
||||
else:
|
||||
break
|
||||
|
||||
def parse_block(self, parent: Optional[Block]) -> Block:
|
||||
assert self.source[self.lineno].startswith("%block")
|
||||
blockname = self.source[self.lineno].split()[1]
|
||||
variables = [] # type: List[Variable]
|
||||
instructions = [] # type: List[Instruction]
|
||||
labels = {} # type: Dict[str, Instruction]
|
||||
self.lineno += 1
|
||||
self.skip_empty()
|
||||
if self.source[self.lineno].startswith("%vardefs"):
|
||||
variables = self.parse_vardefs()
|
||||
self.skip_empty()
|
||||
if self.source[self.lineno].startswith("%instructions"):
|
||||
instructions, labels = self.parse_instructions()
|
||||
block = Block(blockname, parent, variables, instructions, labels, [])
|
||||
self.skip_empty()
|
||||
if self.source[self.lineno].startswith("%subblocks"):
|
||||
block.blocks = self.parse_subblocks(block)
|
||||
self.skip_empty()
|
||||
assert self.source[self.lineno].startswith("%end_block")
|
||||
self.lineno += 1
|
||||
return block
|
||||
|
||||
def get_array_type(self, dtype: DataType) -> str:
|
||||
return {
|
||||
DataType.ARRAY_BYTE: 'B',
|
||||
DataType.ARRAY_SBYTE: 'b',
|
||||
DataType.ARRAY_WORD: 'H',
|
||||
DataType.ARRAY_SWORD: 'h',
|
||||
DataType.MATRIX_BYTE: 'B',
|
||||
DataType.MATRIX_SBYTE: 'b'
|
||||
}[dtype]
|
||||
|
||||
def parse_vardefs(self) -> List[Variable]:
|
||||
assert self.source[self.lineno].startswith("%vardefs")
|
||||
self.lineno += 1
|
||||
variables = []
|
||||
while not self.source[self.lineno].startswith("%"):
|
||||
vartype, datatype, name, argstr = self.source[self.lineno].split(maxsplit=3)
|
||||
dtype = DataType[datatype.upper()]
|
||||
length = height = 0
|
||||
value = None # type: StackValueType
|
||||
if dtype in (DataType.BYTE, DataType.WORD, DataType.SBYTE, DataType.SWORD):
|
||||
value = Value(dtype, int(argstr))
|
||||
elif dtype == DataType.FLOAT:
|
||||
value = Value(dtype, float(argstr))
|
||||
elif dtype == DataType.BOOL:
|
||||
value = Value(dtype, argstr.lower() not in ("0", "false"))
|
||||
elif dtype in (DataType.ARRAY_BYTE, DataType.ARRAY_SBYTE, DataType.ARRAY_WORD, DataType.ARRAY_SWORD):
|
||||
args = argstr.split(maxsplit=1)
|
||||
length = int(args[0])
|
||||
valuestr = args[1]
|
||||
typecode = self.get_array_type(dtype)
|
||||
if valuestr[0] == '[' and valuestr[-1] == ']':
|
||||
value = Value(dtype, array.array(typecode, [int(v) for v in valuestr[1:-1].split()]))
|
||||
else:
|
||||
value = Value(dtype, array.array(typecode, [int(valuestr)]) * length)
|
||||
elif dtype in (DataType.MATRIX_BYTE, DataType.MATRIX_SBYTE):
|
||||
args = argstr.split(maxsplit=2)
|
||||
length = int(args[0])
|
||||
height = int(args[1])
|
||||
valuestr = args[2]
|
||||
typecode = self.get_array_type(dtype)
|
||||
if valuestr[0] == '[' and valuestr[-1] == ']':
|
||||
value = Value(dtype, array.array(typecode, [int(v) for v in valuestr[1:-1].split()]))
|
||||
else:
|
||||
value = Value(dtype, array.array(typecode, [int(valuestr)] * length * height))
|
||||
else:
|
||||
raise TypeError("weird dtype", dtype)
|
||||
variables.append(Variable(name, dtype, value, vartype == "const"))
|
||||
self.lineno += 1
|
||||
self.skip_empty()
|
||||
assert self.source[self.lineno].startswith("%end_vardefs")
|
||||
self.lineno += 1
|
||||
return variables
|
||||
|
||||
def parse_instructions(self) -> Tuple[List[Instruction], Dict[str, Instruction]]:
|
||||
assert self.source[self.lineno].startswith("%instructions")
|
||||
self.lineno += 1
|
||||
instructions = []
|
||||
labels = {} # type: Dict[str, Instruction]
|
||||
|
||||
def parse_instruction(ln: str) -> Instruction:
|
||||
parts = ln.split(maxsplit=1)
|
||||
opcode = Opcode[parts[0].upper()]
|
||||
args = [] # type: List[Any]
|
||||
if len(parts) == 2:
|
||||
args = parts[1].split()
|
||||
else:
|
||||
args = []
|
||||
if opcode in (Opcode.CALL, Opcode.RETURN):
|
||||
args[0] = int(args[0]) # the number of arguments/parameters
|
||||
return Instruction(opcode, args, None, None)
|
||||
|
||||
while not self.source[self.lineno].startswith("%"):
|
||||
line = self.source[self.lineno].strip()
|
||||
if line.endswith(":"):
|
||||
# a label that points to an instruction
|
||||
label = line[:-1].rstrip()
|
||||
self.lineno += 1
|
||||
line = self.source[self.lineno]
|
||||
next_instruction = parse_instruction(line)
|
||||
labels[label] = next_instruction
|
||||
instructions.append(next_instruction)
|
||||
self.lineno += 1
|
||||
else:
|
||||
instructions.append(parse_instruction(line))
|
||||
self.lineno += 1
|
||||
self.skip_empty()
|
||||
assert self.source[self.lineno].startswith("%end_instructions")
|
||||
self.lineno += 1
|
||||
return instructions, labels
|
||||
|
||||
def parse_subblocks(self, parent: Block) -> List[Block]:
|
||||
assert self.source[self.lineno].startswith("%subblocks")
|
||||
self.lineno += 1
|
||||
blocks = []
|
||||
while not self.source[self.lineno].startswith("%end_subblocks"):
|
||||
self.lineno += 1
|
||||
while True:
|
||||
if self.source[self.lineno].startswith("%block"):
|
||||
blocks.append(self.parse_block(parent))
|
||||
else:
|
||||
break
|
||||
self.skip_empty()
|
||||
self.lineno += 1
|
||||
return blocks
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
src = """
|
||||
%block b1
|
||||
%vardefs
|
||||
var byte v1 0
|
||||
var word w1 2222
|
||||
var sword ws -3333
|
||||
const byte c1 99
|
||||
const sword cws -5444
|
||||
var array_byte ba 10 33
|
||||
var array_byte ba2 10 [1 2 3 4 5 6 7 8 9 10]
|
||||
var matrix_byte mxb 4 5 33
|
||||
var matrix_byte mxb2 3 2 [1 2 3 4 5 6]
|
||||
%end_vardefs
|
||||
|
||||
%instructions
|
||||
nop
|
||||
nop
|
||||
l1:
|
||||
nop
|
||||
push c1
|
||||
push2 c1 cws
|
||||
call 3 l1
|
||||
return 2
|
||||
%end_instructions
|
||||
|
||||
%subblocks
|
||||
|
||||
%block b2
|
||||
%vardefs
|
||||
%end_vardefs
|
||||
%end_block ; b2
|
||||
|
||||
%end_subblocks
|
||||
%end_block ;b1
|
||||
|
||||
|
||||
|
||||
%block b3
|
||||
%vardefs
|
||||
%end_vardefs
|
||||
%instructions
|
||||
nop
|
||||
nop
|
||||
l1:
|
||||
nop
|
||||
return 99
|
||||
%end_instructions
|
||||
%end_block ; b3
|
||||
"""
|
||||
parser = Parser(src)
|
||||
program = parser.parse()
|
@ -1,193 +0,0 @@
|
||||
"""
|
||||
Simplistic 8/16 bit Virtual Machine to execute a stack based instruction language.
|
||||
These are the program/instruction definitions that make up a program for the vm
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
import enum
|
||||
import array
|
||||
import operator
|
||||
from typing import List, Dict, Optional, Union, Callable, Any
|
||||
from .core import DataType
|
||||
|
||||
|
||||
class Opcode(enum.IntEnum):
|
||||
TERMINATE = 0
|
||||
NOP = 1
|
||||
PUSH = 10
|
||||
PUSH2 = 11
|
||||
PUSH3 = 12
|
||||
POP = 13
|
||||
POP2 = 14
|
||||
POP3 = 15
|
||||
DUP = 16
|
||||
DUP2 = 17
|
||||
SWAP = 18
|
||||
ADD = 50
|
||||
SUB = 51
|
||||
MUL = 52
|
||||
DIV = 53
|
||||
AND = 70
|
||||
OR = 71
|
||||
XOR = 72
|
||||
NOT = 73
|
||||
TEST = 100
|
||||
CMP_EQ = 101
|
||||
CMP_LT = 102
|
||||
CMP_GT = 103
|
||||
CMP_LTE = 104
|
||||
CMP_GTE = 105
|
||||
CALL = 200
|
||||
RETURN = 201
|
||||
SYSCALL = 202
|
||||
JUMP = 203
|
||||
JUMP_IF_TRUE = 204
|
||||
JUMP_IF_FALSE = 205
|
||||
JUMP_IF_STATUS_ZERO = 206
|
||||
JUMP_IF_STATUS_NE = 207
|
||||
JUMP_IF_STATUS_EQ = 208
|
||||
JUMP_IF_STATUS_CC = 209
|
||||
JUMP_IF_STATUS_CS = 210
|
||||
JUMP_IF_STATUS_VC = 211
|
||||
JUMP_IF_STATUS_VS = 212
|
||||
JUMP_IF_STATUS_GE = 213
|
||||
JUMP_IF_STATUS_LE = 214
|
||||
JUMP_IF_STATUS_GT = 215
|
||||
JUMP_IF_STATUS_LT = 216
|
||||
JUMP_IF_STATUS_POS = 217
|
||||
JUMP_IF_STATUS_NEG = 218
|
||||
|
||||
|
||||
class Value:
|
||||
__slots__ = ["dtype", "value", "length", "height"]
|
||||
|
||||
def __init__(self, dtype: DataType, value: Union[int, float, bytearray, array.array], length: int=0, height: int=0) -> None:
|
||||
self.dtype = dtype
|
||||
self.value = value
|
||||
self.length = length
|
||||
self.height = height
|
||||
|
||||
def __str__(self):
|
||||
return repr(self)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Value dtype={} val={}>".format(self.dtype.name, self.value)
|
||||
|
||||
def number_arithmetic(self, v1: 'Value', oper: Callable, v2: 'Value') -> 'Value':
|
||||
if v1.dtype != DataType.FLOAT and v2.dtype == DataType.FLOAT:
|
||||
raise TypeError("cannot use a float in arithmetic operation on an integer", v1, oper.__name__, v2)
|
||||
if v1.dtype == DataType.BYTE:
|
||||
return Value(DataType.BYTE, oper(v1.value, v2.value) & 255)
|
||||
if v1.dtype == DataType.SBYTE:
|
||||
result = oper(v1.value, v2.value)
|
||||
if result < -128 or result > 127:
|
||||
raise OverflowError("sbyte", result)
|
||||
return Value(DataType.SBYTE, result)
|
||||
if v1.dtype == DataType.WORD:
|
||||
return Value(DataType.WORD, oper(v1.value, v2.value) & 65535)
|
||||
if v1.dtype == DataType.SWORD:
|
||||
result = oper(v1.value, v2.value)
|
||||
if result < -32768 or result > 32767:
|
||||
raise OverflowError("sword", result)
|
||||
return Value(DataType.SWORD, result)
|
||||
if v1.dtype == DataType.FLOAT:
|
||||
return Value(DataType.FLOAT, oper(v1.value, v2.value))
|
||||
raise TypeError("cannot {} {}, {}".format(oper.__name__, v1, v2))
|
||||
|
||||
def number_comparison(self, v1: 'Value', oper: Callable, v2: 'Value') -> bool:
|
||||
if v1.dtype != DataType.FLOAT and v2.dtype == DataType.FLOAT:
|
||||
raise TypeError("cannot use a float in logical operation on an integer", v1, oper.__name__, v2)
|
||||
return oper(v1.value, v2.value)
|
||||
|
||||
def __add__(self, other: 'Value') -> 'Value':
|
||||
return self.number_arithmetic(self, operator.add, other)
|
||||
|
||||
def __sub__(self, other: 'Value') -> 'Value':
|
||||
return self.number_arithmetic(self, operator.sub, other)
|
||||
|
||||
def __mul__(self, other: 'Value') -> 'Value':
|
||||
return self.number_arithmetic(self, operator.sub, other)
|
||||
|
||||
def __truediv__(self, other: 'Value') -> 'Value':
|
||||
return self.number_arithmetic(self, operator.truediv, other)
|
||||
|
||||
def __floordiv__(self, other: 'Value') -> 'Value':
|
||||
return self.number_arithmetic(self, operator.floordiv, other)
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
if not isinstance(other, Value):
|
||||
return False
|
||||
return self.number_comparison(self, operator.eq, other)
|
||||
|
||||
def __lt__(self, other: 'Value') -> bool:
|
||||
return self.number_comparison(self, operator.lt, other)
|
||||
|
||||
def __le__(self, other: 'Value') -> bool:
|
||||
return self.number_comparison(self, operator.le, other)
|
||||
|
||||
def __gt__(self, other: 'Value') -> bool:
|
||||
return self.number_comparison(self, operator.gt, other)
|
||||
|
||||
def __ge__(self, other: 'Value') -> bool:
|
||||
return self.number_comparison(self, operator.ge, other)
|
||||
|
||||
|
||||
class Variable:
|
||||
__slots__ = ["name", "value", "dtype", "length", "height", "const"]
|
||||
|
||||
def __init__(self, name: str, dtype: DataType, value: Value, const: bool=False) -> None:
|
||||
self.name = name
|
||||
self.value = value
|
||||
self.dtype = dtype
|
||||
self.const = const
|
||||
|
||||
def __str__(self):
|
||||
return repr(self)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Var name={} value={} const? {}>".format(self.name, self.value, self.const)
|
||||
|
||||
|
||||
class Instruction:
|
||||
__slots__ = ["opcode", "args", "next", "alt_next"]
|
||||
|
||||
def __init__(self, opcode: Opcode, args: List[Union[Value, int, str]],
|
||||
nxt: Optional['Instruction']=None, alt_next: Optional['Instruction']=None) -> None:
|
||||
self.opcode = opcode
|
||||
self.args = args
|
||||
self.next = nxt # regular next statement, None=end
|
||||
self.alt_next = alt_next # alternate next statement (for condition nodes, and return instruction for call nodes)
|
||||
|
||||
def __str__(self):
|
||||
return repr(self)
|
||||
|
||||
def __repr__(self):
|
||||
return "<Instruction {} args: {}>".format(self.opcode.name, self.args)
|
||||
|
||||
|
||||
class Block:
|
||||
def __init__(self, name: str, parent: Optional['Block'],
|
||||
variables: List[Variable] = None,
|
||||
instructions: List[Instruction] = None,
|
||||
labels: Dict[str, Instruction] = None, # named entry points
|
||||
subblocks: List['Block'] = None) -> None:
|
||||
self.name = name
|
||||
self.parent = parent
|
||||
self.variables = variables or []
|
||||
self.blocks = subblocks or []
|
||||
self.instructions = instructions or []
|
||||
self.labels = labels or {}
|
||||
|
||||
def __str__(self):
|
||||
return repr(self)
|
||||
|
||||
def __repr__(self):
|
||||
if self.parent:
|
||||
return "<Block '{}' in '{}'>".format(self.name, self.parent.name)
|
||||
return "<Block '{}'>".format(self.name)
|
||||
|
||||
|
||||
class Program:
|
||||
def __init__(self, blocks: List[Block]) -> None:
|
||||
self.blocks = blocks
|
@ -1,761 +0,0 @@
|
||||
"""
|
||||
Simplistic 8/16 bit Virtual Machine to execute a stack based instruction language.
|
||||
This is the VM itself (execution engine)
|
||||
|
||||
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
|
||||
"""
|
||||
|
||||
# 8/16 bit virtual machine
|
||||
|
||||
# machine specs:
|
||||
|
||||
# MEMORY: 64K bytes, treated as one single array, indexed per byte, ONLY DATA - NO CODE
|
||||
# elements addressable as one of three elementary data types:
|
||||
# 8-bit byte (singed and unsigned),
|
||||
# 16-bit words (two 8-bit bytes, signed and unsigned) (stored in LSB order),
|
||||
# 5-byte MFLPT floating point
|
||||
# addressing is possible via byte index (for the $0000-$00ff range) or via an unsigned word.
|
||||
# there is NO memory management at all; all of the mem is globally shared and always available in full.
|
||||
# certain blocks of memory can be marked as read-only (write attempts will then crash the vm)
|
||||
#
|
||||
# MEMORY ACCESS: via explicit load and store instructions,
|
||||
# to put a value onto the stack or store the value on the top of the stack,
|
||||
# or in one of the dynamic variables.
|
||||
#
|
||||
# I/O: either via programmed I/O routines:
|
||||
# write [byte/bytearray to text output/screen] : syscall_printstr / syscall_printchr,
|
||||
# read [byte/bytearray from keyboard] : syscall_input / syscall_getchr (both blocking)
|
||||
# or via memory-mapped I/O (text screen matrix, keyboard scan register)
|
||||
#
|
||||
# CPU: single threaded, stack based execution,
|
||||
# no registers, but unlimited dynamic variables (v0, v1, ...) that have a value and a type.
|
||||
# types:
|
||||
# 1-bit boolean,
|
||||
# 8-bit byte (singed and unsigned),
|
||||
# 16-bit words (two 8-bit bytes, signed and unsigned),
|
||||
# floating point,
|
||||
# array of bytes (signed and unsigned),
|
||||
# array of words (signed and unsigned),
|
||||
# matrix (2-dimensional array) of bytes (signed and unsigned).
|
||||
# all of these can have the flag CONST as well which means they cannot be modified.
|
||||
#
|
||||
# CPU INSTRUCTIONS:
|
||||
# stack manipulation mainly:
|
||||
# nop
|
||||
# push var / push2 var1, var2
|
||||
# pop var / pop2 var1, var2
|
||||
# various arithmetic operations, logical operations, boolean test and comparison operations
|
||||
# jump label
|
||||
# jump_if_true label, jump_if_false label
|
||||
# jump_if_status_XX label special system dependent status register conditional check such as carry bit or overflow bit)
|
||||
# call function (arguments are on stack)
|
||||
# return (return values on stack)
|
||||
# syscall function (special system dependent implementation)
|
||||
#
|
||||
# TIMER 'INTERRUPT': triggered around each 1/60th of a second.
|
||||
# executes on a DIFFERENT stack and with a different PROGRAM LIST,
|
||||
# but with access to ALL THE SAME DYNAMIC VARIABLES.
|
||||
# This suspends the main program until the timer program RETURNs!
|
||||
#
|
||||
|
||||
import time
|
||||
import itertools
|
||||
import collections
|
||||
import array
|
||||
import threading
|
||||
import pprint
|
||||
import tkinter
|
||||
import tkinter.font
|
||||
from typing import Dict, List, Tuple, Union, no_type_check
|
||||
from .program import Instruction, Variable, Block, Program, Opcode, Value
|
||||
from .core import Memory, DataType, TerminateExecution, ExecutionError
|
||||
|
||||
|
||||
class CallFrameMarker:
|
||||
__slots__ = ["returninstruction"]
|
||||
|
||||
def __init__(self, instruction: Instruction) -> None:
|
||||
self.returninstruction = instruction
|
||||
|
||||
def __str__(self) -> str:
|
||||
return repr(self)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "<CallFrameMarker returninstruction={:s}>".format(str(self.returninstruction))
|
||||
|
||||
|
||||
StackValueType = Union[Value, CallFrameMarker]
|
||||
|
||||
|
||||
class Stack:
|
||||
def __init__(self):
|
||||
self.stack = []
|
||||
self.pop_history = collections.deque(maxlen=10)
|
||||
|
||||
def debug_peek(self, size: int) -> List[StackValueType]:
|
||||
return self.stack[-size:]
|
||||
|
||||
def size(self) -> int:
|
||||
return len(self.stack)
|
||||
|
||||
def pop(self) -> StackValueType:
|
||||
x = self.stack.pop()
|
||||
self.pop_history.append(x)
|
||||
return x
|
||||
|
||||
def pop2(self) -> Tuple[StackValueType, StackValueType]:
|
||||
x, y = self.stack.pop(), self.stack.pop()
|
||||
self.pop_history.append(x)
|
||||
self.pop_history.append(y)
|
||||
return x, y
|
||||
|
||||
def pop3(self) -> Tuple[StackValueType, StackValueType, StackValueType]:
|
||||
x, y, z = self.stack.pop(), self.stack.pop(), self.stack.pop()
|
||||
self.pop_history.append(x)
|
||||
self.pop_history.append(y)
|
||||
self.pop_history.append(z)
|
||||
return x, y, z
|
||||
|
||||
def pop_under(self, number: int) -> StackValueType:
|
||||
return self.stack.pop(-1-number)
|
||||
|
||||
def push(self, item: StackValueType) -> None:
|
||||
self._typecheck(item)
|
||||
self.stack.append(item)
|
||||
|
||||
def push2(self, first: StackValueType, second: StackValueType) -> None:
|
||||
self._typecheck(first)
|
||||
self._typecheck(second)
|
||||
self.stack.append(first)
|
||||
self.stack.append(second)
|
||||
|
||||
def push3(self, first: StackValueType, second: StackValueType, third: StackValueType) -> None:
|
||||
self._typecheck(first)
|
||||
self._typecheck(second)
|
||||
self._typecheck(third)
|
||||
self.stack.extend([first, second, third])
|
||||
|
||||
def push_under(self, number: int, value: StackValueType) -> None:
|
||||
self.stack.insert(-number, value)
|
||||
|
||||
def peek(self) -> StackValueType:
|
||||
return self.stack[-1] if self.stack else None
|
||||
|
||||
def swap(self) -> None:
|
||||
x = self.stack[-1]
|
||||
self.stack[-1] = self.stack[-2]
|
||||
self.stack[-2] = x
|
||||
|
||||
def _typecheck(self, value: StackValueType):
|
||||
if not isinstance(value, (Value, CallFrameMarker)):
|
||||
raise TypeError("invalid item type pushed", value)
|
||||
|
||||
|
||||
# noinspection PyPep8Naming,PyUnusedLocal,PyMethodMayBeStatic
|
||||
class VM:
|
||||
str_encoding = "iso-8859-15"
|
||||
str_alt_encoding = "iso-8859-15"
|
||||
readonly_mem_ranges = [] # type: List[Tuple[int, int]]
|
||||
timer_irq_resolution = 1/30
|
||||
charout_address = 0xd000
|
||||
charin_address = 0xd001
|
||||
|
||||
def __init__(self, program: Program, timerprogram: Program=None) -> None:
|
||||
for opcode in Opcode:
|
||||
if opcode not in self.dispatch_table:
|
||||
raise NotImplementedError("missing opcode dispatch for " + opcode.name)
|
||||
for oc in Opcode:
|
||||
if oc not in self.dispatch_table:
|
||||
raise NotImplementedError("no dispatch entry in table for " + oc.name)
|
||||
self.memory = Memory()
|
||||
self.memory.memmapped_io_charout(self.charout_address, self.memmapped_charout)
|
||||
self.memory.memmapped_io_charin(self.charin_address, self.memmapped_charin)
|
||||
for start, end in self.readonly_mem_ranges:
|
||||
self.memory.mark_readonly(start, end)
|
||||
self.main_stack = Stack()
|
||||
self.timer_stack = Stack()
|
||||
self.main_program, self.timer_program, self.variables, self.labels = self.flatten_programs(program, timerprogram or Program([]))
|
||||
self.connect_instruction_pointers(self.main_program)
|
||||
self.connect_instruction_pointers(self.timer_program)
|
||||
self.program = self.main_program
|
||||
self.stack = self.main_stack
|
||||
self.pc = None # type: Instruction
|
||||
self.charscreen_address = 0
|
||||
self.charscreen_width = 0
|
||||
self.charscreen_height = 0
|
||||
self.keyboard_scancode = 0
|
||||
self.system = System(self)
|
||||
assert all(i.next for i in self.main_program
|
||||
if i.opcode != Opcode.TERMINATE), "main: all instrs next must be set"
|
||||
assert all(i.next for i in self.timer_program
|
||||
if i.opcode not in (Opcode.TERMINATE, Opcode.RETURN)), "timer: all instrs next must be set"
|
||||
assert all(i.alt_next for i in self.main_program
|
||||
if i.opcode in (Opcode.CALL, Opcode.JUMP_IF_FALSE, Opcode.JUMP_IF_TRUE)), "main: alt_nexts must be set"
|
||||
assert all(i.alt_next for i in self.timer_program
|
||||
if i.opcode in (Opcode.CALL, Opcode.JUMP_IF_FALSE, Opcode.JUMP_IF_TRUE)), "timer: alt_nexts must be set"
|
||||
print("[TinyVM starting up.]")
|
||||
|
||||
def enable_charscreen(self, screen_address: int, width: int, height: int) -> None:
|
||||
self.charscreen_address = screen_address
|
||||
self.charscreen_width, self.charscreen_height = width, height
|
||||
|
||||
def flatten_programs(self, main: Program, timer: Program) \
|
||||
-> Tuple[List[Instruction], List[Instruction], Dict[str, Variable], Dict[str, Instruction]]:
|
||||
variables = {} # type: Dict[str, Variable]
|
||||
labels = {} # type: Dict[str, Instruction]
|
||||
instructions_main = [] # type: List[Instruction]
|
||||
instructions_timer = [] # type: List[Instruction]
|
||||
for block in main.blocks:
|
||||
flat = self.flatten(block, variables, labels)
|
||||
instructions_main.extend(flat)
|
||||
instructions_main.append(Instruction(Opcode.TERMINATE, [], None, None))
|
||||
for block in timer.blocks:
|
||||
flat = self.flatten(block, variables, labels)
|
||||
instructions_timer.extend(flat)
|
||||
return instructions_main, instructions_timer, variables, labels
|
||||
|
||||
def flatten(self, block: Block, variables: Dict[str, Variable], labels: Dict[str, Instruction]) -> List[Instruction]:
|
||||
def block_prefix(b: Block) -> str:
|
||||
if b.parent:
|
||||
return block_prefix(b.parent) + "." + b.name
|
||||
else:
|
||||
return b.name
|
||||
prefix = block_prefix(block)
|
||||
instructions = block.instructions
|
||||
for ins in instructions:
|
||||
if ins.opcode == Opcode.SYSCALL:
|
||||
continue
|
||||
if ins.args:
|
||||
newargs = [] # type: List[Union[str, int, Value]]
|
||||
for a in ins.args:
|
||||
if isinstance(a, str):
|
||||
newargs.append(prefix + "." + a)
|
||||
else:
|
||||
newargs.append(a) # type: ignore
|
||||
ins.args = newargs
|
||||
for vardef in block.variables:
|
||||
vname = prefix + "." + vardef.name
|
||||
assert vname not in variables
|
||||
variables[vname] = vardef
|
||||
for name, instr in block.labels.items():
|
||||
name = prefix + "." + name
|
||||
assert name not in labels
|
||||
labels[name] = instr
|
||||
for subblock in block.blocks:
|
||||
instructions.extend(self.flatten(subblock, variables, labels))
|
||||
del block.instructions
|
||||
del block.variables
|
||||
del block.labels
|
||||
return instructions
|
||||
|
||||
def connect_instruction_pointers(self, instructions: List[Instruction]) -> None:
|
||||
i1, i2 = itertools.tee(instructions)
|
||||
next(i2, None)
|
||||
for i, nexti in itertools.zip_longest(i1, i2):
|
||||
if i.opcode in (Opcode.JUMP_IF_TRUE, Opcode.JUMP_IF_FALSE):
|
||||
i.next = nexti # normal flow target
|
||||
i.alt_next = self.labels[i.args[0]] # conditional jump target
|
||||
elif i.opcode == Opcode.JUMP:
|
||||
i.next = self.labels[i.args[0]] # jump target
|
||||
elif i.opcode == Opcode.CALL:
|
||||
i.next = self.labels[i.args[1]] # call target
|
||||
i.alt_next = nexti # return instruction
|
||||
else:
|
||||
i.next = nexti
|
||||
|
||||
def run(self) -> None:
|
||||
if self.charscreen_address:
|
||||
threading.Thread(target=ScreenViewer.create,
|
||||
args=(self, self.charscreen_address, self.charscreen_width, self.charscreen_height),
|
||||
name="screenviewer", daemon=True).start()
|
||||
time.sleep(0.05)
|
||||
|
||||
self.pc = self.program[0] # first instruction of the main program
|
||||
self.stack.push(CallFrameMarker(None)) # enter the call frame so the timer program can end with a RETURN
|
||||
try:
|
||||
counter = 0
|
||||
previous_timer_irq = time.perf_counter()
|
||||
while self.pc is not None:
|
||||
next_pc = self.dispatch_table[self.pc.opcode](self, self.pc)
|
||||
if next_pc:
|
||||
self.pc = self.pc.next
|
||||
counter += 1
|
||||
if self.charscreen_address and counter % 1000 == 0:
|
||||
time.sleep(0.001) # allow the tkinter window to update
|
||||
time_since_irq = time.perf_counter() - previous_timer_irq
|
||||
if time_since_irq > 1/60:
|
||||
self.timer_irq()
|
||||
previous_timer_irq = time.perf_counter()
|
||||
except TerminateExecution as x:
|
||||
why = str(x)
|
||||
print("[TinyVM execution terminated{:s}]\n".format(": "+why if why else "."))
|
||||
return
|
||||
except Exception as x:
|
||||
print("EXECUTION ERROR")
|
||||
self.debug_stack(5)
|
||||
raise
|
||||
else:
|
||||
print("[TinyVM execution ended.]")
|
||||
|
||||
def timer_irq(self) -> None:
|
||||
# This is the timer 'irq' handler. It is called to run the timer program at a certain interval.
|
||||
# During the execution the main program is halted
|
||||
if self.timer_program:
|
||||
previous_pc = self.pc
|
||||
previous_program = self.program
|
||||
previous_stack = self.stack
|
||||
self.stack = self.timer_stack
|
||||
self.program = self.timer_program
|
||||
self.pc = self.program[0]
|
||||
self.stack.push(CallFrameMarker(None)) # enter the call frame so the timer program can end with a RETURN
|
||||
while self.pc is not None:
|
||||
next_pc = self.dispatch_table[self.pc.opcode](self, self.pc)
|
||||
if next_pc:
|
||||
self.pc = self.pc.next
|
||||
self.pc = previous_pc
|
||||
self.program = previous_program
|
||||
self.stack = previous_stack
|
||||
|
||||
def debug_stack(self, size: int=5) -> None:
|
||||
stack = self.stack.debug_peek(size)
|
||||
if len(stack) > 0:
|
||||
print("** stack (top {:d}):".format(size))
|
||||
for i, value in enumerate(reversed(stack), start=1):
|
||||
print(" {:d}. {:s} {:s}".format(i, type(value).__name__, str(value)))
|
||||
else:
|
||||
print("** stack is empty.")
|
||||
if self.stack.pop_history:
|
||||
print("** last {:d} values popped from stack (most recent on top):".format(self.stack.pop_history.maxlen))
|
||||
pprint.pprint(list(reversed(self.stack.pop_history)), indent=2, compact=True, width=20) # type: ignore
|
||||
if self.pc is not None:
|
||||
print("* instruction:", self.pc)
|
||||
|
||||
def memmapped_charout(self, value: int) -> None:
|
||||
string = self.system.decodestr(bytearray([value]))
|
||||
print(string, end="")
|
||||
|
||||
def memmapped_charin(self) -> int:
|
||||
return self.keyboard_scancode
|
||||
|
||||
def assign_variable(self, variable: Variable, value: Value) -> None:
|
||||
assert not variable.const, "cannot modify a const"
|
||||
assert isinstance(value, Value)
|
||||
variable.value = value
|
||||
|
||||
def opcode_NOP(self, instruction: Instruction) -> bool:
|
||||
# do nothing
|
||||
return True
|
||||
|
||||
def opcode_TERMINATE(self, instruction: Instruction) -> bool:
|
||||
raise TerminateExecution()
|
||||
|
||||
def opcode_PUSH(self, instruction: Instruction) -> bool:
|
||||
value = self.variables[instruction.args[0]].value # type: ignore
|
||||
self.stack.push(value)
|
||||
return True
|
||||
|
||||
def opcode_DUP(self, instruction: Instruction) -> bool:
|
||||
self.stack.push(self.stack.peek())
|
||||
return True
|
||||
|
||||
def opcode_DUP2(self, instruction: Instruction) -> bool:
|
||||
x = self.stack.peek()
|
||||
self.stack.push(x)
|
||||
self.stack.push(x)
|
||||
return True
|
||||
|
||||
def opcode_SWAP(self, instruction: Instruction) -> bool:
|
||||
value2, value1 = self.stack.pop2()
|
||||
self.stack.push2(value2, value1)
|
||||
return True
|
||||
|
||||
@no_type_check
|
||||
def opcode_PUSH2(self, instruction: Instruction) -> bool:
|
||||
value1 = self.variables[instruction.args[0]].value
|
||||
value2 = self.variables[instruction.args[1]].value
|
||||
self.stack.push2(value1, value2)
|
||||
return True
|
||||
|
||||
@no_type_check
|
||||
def opcode_PUSH3(self, instruction: Instruction) -> bool:
|
||||
value1 = self.variables[instruction.args[0]].value
|
||||
value2 = self.variables[instruction.args[1]].value
|
||||
value3 = self.variables[instruction.args[2]].value
|
||||
self.stack.push3(value1, value2, value3)
|
||||
return True
|
||||
|
||||
@no_type_check
|
||||
def opcode_POP(self, instruction: Instruction) -> bool:
|
||||
value = self.stack.pop()
|
||||
variable = self.variables[instruction.args[0]]
|
||||
self.assign_variable(variable, value)
|
||||
return True
|
||||
|
||||
@no_type_check
|
||||
def opcode_POP2(self, instruction: Instruction) -> bool:
|
||||
value1, value2 = self.stack.pop2()
|
||||
variable = self.variables[instruction.args[0]]
|
||||
self.assign_variable(variable, value1)
|
||||
variable = self.variables[instruction.args[1]]
|
||||
self.assign_variable(variable, value2)
|
||||
return True
|
||||
|
||||
@no_type_check
|
||||
def opcode_POP3(self, instruction: Instruction) -> bool:
|
||||
value1, value2, value3 = self.stack.pop3()
|
||||
variable = self.variables[instruction.args[0]]
|
||||
self.assign_variable(variable, value1)
|
||||
variable = self.variables[instruction.args[1]]
|
||||
self.assign_variable(variable, value2)
|
||||
variable = self.variables[instruction.args[2]]
|
||||
self.assign_variable(variable, value3)
|
||||
return True
|
||||
|
||||
@no_type_check
|
||||
def opcode_ADD(self, instruction: Instruction) -> bool:
|
||||
second, first = self.stack.pop2()
|
||||
self.stack.push(first + second)
|
||||
return True
|
||||
|
||||
@no_type_check
|
||||
def opcode_SUB(self, instruction: Instruction) -> bool:
|
||||
second, first = self.stack.pop2()
|
||||
self.stack.push(first - second)
|
||||
return True
|
||||
|
||||
@no_type_check
|
||||
def opcode_MUL(self, instruction: Instruction) -> bool:
|
||||
second, first = self.stack.pop2()
|
||||
self.stack.push(first * second)
|
||||
return True
|
||||
|
||||
@no_type_check
|
||||
def opcode_DIV(self, instruction: Instruction) -> bool:
|
||||
second, first = self.stack.pop2()
|
||||
self.stack.push(first / second)
|
||||
return True
|
||||
|
||||
def opcode_AND(self, instruction: Instruction) -> bool:
|
||||
second, first = self.stack.pop2()
|
||||
self.stack.push(first and second)
|
||||
return True
|
||||
|
||||
def opcode_OR(self, instruction: Instruction) -> bool:
|
||||
second, first = self.stack.pop2()
|
||||
self.stack.push(first or second)
|
||||
return True
|
||||
|
||||
def opcode_XOR(self, instruction: Instruction) -> bool:
|
||||
second, first = self.stack.pop2()
|
||||
ifirst = 1 if first else 0
|
||||
isecond = 1 if second else 0
|
||||
self.stack.push(Value(DataType.BOOL, bool(ifirst ^ isecond)))
|
||||
return True
|
||||
|
||||
def opcode_NOT(self, instruction: Instruction) -> bool:
|
||||
self.stack.push(Value(DataType.BOOL, not self.stack.pop()))
|
||||
return True
|
||||
|
||||
def opcode_TEST(self, instruction: Instruction) -> bool:
|
||||
self.stack.push(Value(DataType.BOOL, bool(self.stack.pop())))
|
||||
return True
|
||||
|
||||
def opcode_CMP_EQ(self, instruction: Instruction) -> bool:
|
||||
second, first = self.stack.pop2()
|
||||
self.stack.push(Value(DataType.BOOL, first == second))
|
||||
return True
|
||||
|
||||
@no_type_check
|
||||
def opcode_CMP_LT(self, instruction: Instruction) -> bool:
|
||||
second, first = self.stack.pop2()
|
||||
self.stack.push(Value(DataType.BOOL, first < second))
|
||||
return True
|
||||
|
||||
@no_type_check
|
||||
def opcode_CMP_GT(self, instruction: Instruction) -> bool:
|
||||
second, first = self.stack.pop2()
|
||||
self.stack.push(Value(DataType.BOOL, first > second))
|
||||
return True
|
||||
|
||||
@no_type_check
|
||||
def opcode_CMP_LTE(self, instruction: Instruction) -> bool:
|
||||
second, first = self.stack.pop2()
|
||||
self.stack.push(Value(DataType.BOOL, first <= second))
|
||||
return True
|
||||
|
||||
@no_type_check
|
||||
def opcode_CMP_GTE(self, instruction: Instruction) -> bool:
|
||||
second, first = self.stack.pop2()
|
||||
self.stack.push(Value(DataType.BOOL, first >= second))
|
||||
return True
|
||||
|
||||
def opcode_CALL(self, instruction: Instruction) -> bool:
|
||||
# arguments are already on the stack
|
||||
num_args = instruction.args[0]
|
||||
assert isinstance(num_args, int)
|
||||
self.stack.push_under(num_args, CallFrameMarker(instruction.alt_next))
|
||||
return True
|
||||
|
||||
def opcode_RETURN(self, instruction: Instruction) -> bool:
|
||||
num_returnvalues = instruction.args[0]
|
||||
assert isinstance(num_returnvalues, int)
|
||||
callframe = self.stack.pop_under(num_returnvalues)
|
||||
assert isinstance(callframe, CallFrameMarker), callframe
|
||||
self.pc = callframe.returninstruction
|
||||
return False
|
||||
|
||||
def opcode_SYSCALL(self, instruction: Instruction) -> bool:
|
||||
syscall = instruction.args[0]
|
||||
assert isinstance(syscall, str)
|
||||
call = getattr(self.system, "syscall_" + syscall, None)
|
||||
if call:
|
||||
return call()
|
||||
else:
|
||||
raise RuntimeError("no syscall method for " + syscall)
|
||||
|
||||
def opcode_JUMP(self, instruction: Instruction) -> bool:
|
||||
return True # jump simply points to the next instruction elsewhere
|
||||
|
||||
def opcode_JUMP_IF_TRUE(self, instruction: Instruction) -> bool:
|
||||
result = self.stack.pop()
|
||||
assert isinstance(result, Value)
|
||||
if result.value:
|
||||
self.pc = self.pc.alt_next # alternative next instruction
|
||||
return False
|
||||
return True
|
||||
|
||||
def opcode_JUMP_IF_FALSE(self, instruction: Instruction) -> bool:
|
||||
result = self.stack.pop()
|
||||
if result.value: # type: ignore
|
||||
return True
|
||||
self.pc = self.pc.alt_next # alternative next instruction
|
||||
return False
|
||||
|
||||
def opcode_JUMP_IF_STATUS_UNSUPPORTED_FLAG(self, instruction: Instruction) -> bool:
|
||||
raise ExecutionError("unsupported conditional jump", instruction) # @todo implement hardware specific status register flags
|
||||
|
||||
dispatch_table = {
|
||||
Opcode.TERMINATE: opcode_TERMINATE,
|
||||
Opcode.NOP: opcode_NOP,
|
||||
Opcode.PUSH: opcode_PUSH,
|
||||
Opcode.PUSH2: opcode_PUSH2,
|
||||
Opcode.PUSH3: opcode_PUSH3,
|
||||
Opcode.POP: opcode_POP,
|
||||
Opcode.POP2: opcode_POP2,
|
||||
Opcode.POP3: opcode_POP3,
|
||||
Opcode.DUP: opcode_DUP,
|
||||
Opcode.DUP2: opcode_DUP2,
|
||||
Opcode.SWAP: opcode_SWAP,
|
||||
Opcode.ADD: opcode_ADD,
|
||||
Opcode.SUB: opcode_SUB,
|
||||
Opcode.MUL: opcode_MUL,
|
||||
Opcode.DIV: opcode_DIV,
|
||||
Opcode.AND: opcode_AND,
|
||||
Opcode.OR: opcode_OR,
|
||||
Opcode.XOR: opcode_XOR,
|
||||
Opcode.NOT: opcode_NOT,
|
||||
Opcode.TEST: opcode_TEST,
|
||||
Opcode.CMP_EQ: opcode_CMP_EQ,
|
||||
Opcode.CMP_LT: opcode_CMP_LT,
|
||||
Opcode.CMP_GT: opcode_CMP_GT,
|
||||
Opcode.CMP_LTE: opcode_CMP_LTE,
|
||||
Opcode.CMP_GTE: opcode_CMP_GTE,
|
||||
Opcode.CALL: opcode_CALL,
|
||||
Opcode.RETURN: opcode_RETURN,
|
||||
Opcode.SYSCALL: opcode_SYSCALL,
|
||||
Opcode.JUMP: opcode_JUMP,
|
||||
Opcode.JUMP_IF_TRUE: opcode_JUMP_IF_TRUE,
|
||||
Opcode.JUMP_IF_FALSE: opcode_JUMP_IF_FALSE,
|
||||
Opcode.JUMP_IF_STATUS_ZERO: opcode_JUMP_IF_STATUS_UNSUPPORTED_FLAG,
|
||||
Opcode.JUMP_IF_STATUS_NE: opcode_JUMP_IF_STATUS_UNSUPPORTED_FLAG,
|
||||
Opcode.JUMP_IF_STATUS_EQ: opcode_JUMP_IF_STATUS_UNSUPPORTED_FLAG,
|
||||
Opcode.JUMP_IF_STATUS_CC: opcode_JUMP_IF_STATUS_UNSUPPORTED_FLAG,
|
||||
Opcode.JUMP_IF_STATUS_CS: opcode_JUMP_IF_STATUS_UNSUPPORTED_FLAG,
|
||||
Opcode.JUMP_IF_STATUS_VC: opcode_JUMP_IF_STATUS_UNSUPPORTED_FLAG,
|
||||
Opcode.JUMP_IF_STATUS_VS: opcode_JUMP_IF_STATUS_UNSUPPORTED_FLAG,
|
||||
Opcode.JUMP_IF_STATUS_GE: opcode_JUMP_IF_STATUS_UNSUPPORTED_FLAG,
|
||||
Opcode.JUMP_IF_STATUS_LE: opcode_JUMP_IF_STATUS_UNSUPPORTED_FLAG,
|
||||
Opcode.JUMP_IF_STATUS_GT: opcode_JUMP_IF_STATUS_UNSUPPORTED_FLAG,
|
||||
Opcode.JUMP_IF_STATUS_LT: opcode_JUMP_IF_STATUS_UNSUPPORTED_FLAG,
|
||||
Opcode.JUMP_IF_STATUS_POS: opcode_JUMP_IF_STATUS_UNSUPPORTED_FLAG,
|
||||
Opcode.JUMP_IF_STATUS_NEG: opcode_JUMP_IF_STATUS_UNSUPPORTED_FLAG,
|
||||
}
|
||||
|
||||
|
||||
class System:
|
||||
def __init__(self, vm: VM) -> None:
|
||||
self.vm = vm
|
||||
|
||||
def encodestr(self, string: str, alt: bool=False) -> bytearray:
|
||||
return bytearray(string, self.vm.str_alt_encoding if alt else self.vm.str_encoding)
|
||||
|
||||
def decodestr(self, bb: Union[bytearray, array.array], alt: bool=False) -> str:
|
||||
return str(bb, self.vm.str_alt_encoding if alt else self.vm.str_encoding) # type: ignore
|
||||
|
||||
def syscall_printstr(self) -> bool:
|
||||
value = self.vm.stack.pop()
|
||||
assert isinstance(value, Value)
|
||||
if value.dtype == DataType.ARRAY_BYTE:
|
||||
print(self.decodestr(value.value), end="") # type: ignore
|
||||
return True
|
||||
else:
|
||||
raise TypeError("printstr expects bytearray", value)
|
||||
|
||||
def syscall_printchr(self) -> bool:
|
||||
charactervalue = self.vm.stack.pop()
|
||||
assert isinstance(charactervalue, Value)
|
||||
if charactervalue.dtype == DataType.BYTE:
|
||||
print(self.decodestr(bytearray([charactervalue.value])), end="") # type: ignore
|
||||
return True
|
||||
else:
|
||||
raise TypeError("printchr expects BYTE", charactervalue)
|
||||
|
||||
def syscall_input(self) -> bool:
|
||||
self.vm.stack.push(Value(DataType.ARRAY_BYTE, self.encodestr(input())))
|
||||
return True
|
||||
|
||||
def syscall_getchr(self) -> bool:
|
||||
self.vm.stack.push(Value(DataType.BYTE, self.encodestr(input() + '\n')[0]))
|
||||
return True
|
||||
|
||||
def syscall_decimalstr_signed(self) -> bool:
|
||||
value = self.vm.stack.pop()
|
||||
assert isinstance(value, Value)
|
||||
if value.dtype in (DataType.SBYTE, DataType.SWORD):
|
||||
self.vm.stack.push(Value(DataType.ARRAY_BYTE, self.encodestr(str(value.value))))
|
||||
return True
|
||||
else:
|
||||
raise TypeError("decimalstr_signed expects signed int", value)
|
||||
|
||||
def syscall_decimalstr_unsigned(self) -> bool:
|
||||
value = self.vm.stack.pop()
|
||||
assert isinstance(value, Value)
|
||||
if value.dtype in (DataType.BYTE, DataType.WORD):
|
||||
self.vm.stack.push(Value(DataType.ARRAY_BYTE, self.encodestr(str(value.value))))
|
||||
return True
|
||||
else:
|
||||
raise TypeError("decimalstr_signed expects unsigned int", value)
|
||||
|
||||
def syscall_hexstr_signed(self) -> bool:
|
||||
value = self.vm.stack.pop()
|
||||
if type(value) is int:
|
||||
if value >= 0: # type: ignore
|
||||
strvalue = "${:x}".format(value)
|
||||
else:
|
||||
strvalue = "-${:x}".format(-value) # type: ignore
|
||||
self.vm.stack.push(Value(DataType.ARRAY_BYTE, self.encodestr(strvalue)))
|
||||
return True
|
||||
else:
|
||||
raise TypeError("hexstr expects int", value)
|
||||
|
||||
def syscall_memwrite_byte(self) -> bool:
|
||||
value, address = self.vm.stack.pop2()
|
||||
assert isinstance(value, Value) and isinstance(address, Value)
|
||||
assert value.dtype == DataType.BYTE and address.dtype == DataType.WORD
|
||||
self.vm.memory.set_byte(address.value, value.value) # type: ignore
|
||||
return True
|
||||
|
||||
def syscall_memwrite_sbyte(self) -> bool:
|
||||
value, address = self.vm.stack.pop2()
|
||||
assert isinstance(value, Value) and isinstance(address, Value)
|
||||
assert value.dtype == DataType.SBYTE and address.dtype == DataType.WORD
|
||||
self.vm.memory.set_sbyte(address.value, value.value) # type: ignore
|
||||
return True
|
||||
|
||||
def syscall_memwrite_word(self) -> bool:
|
||||
value, address = self.vm.stack.pop2()
|
||||
assert isinstance(value, Value) and isinstance(address, Value)
|
||||
assert value.dtype in (DataType.WORD, DataType.BYTE) and address.dtype == DataType.WORD
|
||||
self.vm.memory.set_word(address.value, value.value) # type: ignore
|
||||
return True
|
||||
|
||||
def syscall_memwrite_sword(self) -> bool:
|
||||
value, address = self.vm.stack.pop2()
|
||||
assert isinstance(value, Value) and isinstance(address, Value)
|
||||
assert value.dtype in (DataType.SWORD, DataType.SBYTE, DataType.BYTE) and address.dtype == DataType.WORD
|
||||
self.vm.memory.set_sword(address.value, value.value) # type: ignore
|
||||
return True
|
||||
|
||||
def syscall_memwrite_float(self) -> bool:
|
||||
value, address = self.vm.stack.pop2()
|
||||
assert isinstance(value, Value) and isinstance(address, Value)
|
||||
assert value.dtype == DataType.FLOAT and address.dtype == DataType.WORD
|
||||
self.vm.memory.set_float(address.value, value.value) # type: ignore
|
||||
return True
|
||||
|
||||
def syscall_memwrite_str(self) -> bool:
|
||||
strbytes, address = self.vm.stack.pop2()
|
||||
assert isinstance(strbytes, Value) and isinstance(address, Value)
|
||||
assert strbytes.dtype == DataType.ARRAY_BYTE and address.dtype == DataType.WORD
|
||||
for i, b in enumerate(strbytes.value): # type: ignore
|
||||
self.vm.memory.set_byte(address+i, b) # type: ignore
|
||||
return True
|
||||
|
||||
def syscall_memread_byte(self) -> bool:
|
||||
address = self.vm.stack.pop()
|
||||
assert isinstance(address, Value)
|
||||
assert address.dtype == DataType.WORD
|
||||
self.vm.stack.push(Value(DataType.BYTE, self.vm.memory.get_byte(address.value))) # type: ignore
|
||||
return True
|
||||
|
||||
def syscall_smalldelay(self) -> bool:
|
||||
time.sleep(1/100)
|
||||
return True
|
||||
|
||||
def syscall_delay(self) -> bool:
|
||||
time.sleep(0.1)
|
||||
return True
|
||||
|
||||
|
||||
class ScreenViewer(tkinter.Tk):
|
||||
def __init__(self, vm: VM, screen_addr: int, screen_width: int, screen_height: int) -> None:
|
||||
super().__init__()
|
||||
self.title("IL65 tinyvm")
|
||||
self.fontsize = 16
|
||||
self.vm = vm
|
||||
self.address = screen_addr
|
||||
self.width = screen_width
|
||||
self.height = screen_height
|
||||
self.monospace = tkinter.font.Font(self, family="Courier", weight="bold", size=self.fontsize) # type: ignore
|
||||
cw = self.monospace.measure("x")*self.width+8
|
||||
self.canvas = tkinter.Canvas(self, width=cw, height=self.fontsize*self.height+8, bg="blue")
|
||||
self.canvas.pack()
|
||||
self.bind("<KeyPress>", self.keypress)
|
||||
self.bind("<KeyRelease>", self.keyrelease)
|
||||
self.after(10, self.update_screen)
|
||||
|
||||
def keypress(self, e) -> None:
|
||||
key = e.char or e.keysym
|
||||
if len(key) == 1:
|
||||
self.vm.keyboard_scancode = self.vm.system.encodestr(key)[0]
|
||||
elif len(key) > 1:
|
||||
code = 0
|
||||
if key == "Up":
|
||||
code = ord("w")
|
||||
elif key == "Down":
|
||||
code = ord("s")
|
||||
elif key == "Left":
|
||||
code = ord("a")
|
||||
elif key == "Right":
|
||||
code = ord("d")
|
||||
self.vm.keyboard_scancode = code
|
||||
else:
|
||||
self.vm.keyboard_scancode = 0
|
||||
|
||||
def keyrelease(self, e) -> None:
|
||||
self.vm.keyboard_scancode = 0
|
||||
|
||||
def update_screen(self) -> None:
|
||||
self.canvas.delete(tkinter.ALL)
|
||||
lines = []
|
||||
for y in range(self.height):
|
||||
line = self.vm.system.decodestr(self.vm.memory.get_bytes(self.address+y*self.width, self.width))
|
||||
lines.append("".join(c if c.isprintable() else " " for c in line))
|
||||
for y, line in enumerate(lines):
|
||||
self.canvas.create_text(4, self.fontsize*y, text=line, fill="white", font=self.monospace, anchor=tkinter.NW)
|
||||
self.after(30, self.update_screen)
|
||||
|
||||
@classmethod
|
||||
def create(cls, vm: VM, screen_addr: int, screen_width: int, screen_height: int) -> None:
|
||||
viewer = cls(vm, screen_addr, screen_width, screen_height)
|
||||
viewer.mainloop()
|
Loading…
Reference in New Issue
Block a user