cleanups, fix scope of certain generated nodes in for loops

This commit is contained in:
Irmen de Jong 2018-12-25 00:33:04 +01:00
parent d55bbcf706
commit 904e317781
87 changed files with 449 additions and 17788 deletions

View File

@ -1,5 +1,4 @@
%import c64utils
%import mathlib
%option enable_floats

View File

@ -1,5 +1,4 @@
%import c64utils
%import mathlib
%option enable_floats

View File

@ -1,5 +1,4 @@
%import c64utils
%import mathlib
%option enable_floats

View File

@ -1,5 +1,4 @@
%import c64utils
%import mathlib
%option enable_floats

View File

@ -1,5 +1,4 @@
%import c64utils
%import mathlib
%option enable_floats

View File

@ -1,5 +1,4 @@
%import c64utils
%import mathlib
~ main {

View File

@ -1,5 +1,4 @@
%import c64utils
%import mathlib
%option enable_floats

View File

@ -1,5 +1,4 @@
%import c64utils
%import mathlib
~ main {

View File

@ -1,5 +1,4 @@
%import c64utils
%import mathlib
~ main {

View File

@ -1,5 +1,4 @@
%import c64utils
%import mathlib
~ main {

View File

@ -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

View File

@ -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

View File

@ -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])
}

View File

@ -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")

View File

@ -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

View File

@ -1,5 +1,4 @@
%import c64utils
%import mathlib
; The classic number guessing game.
; This version uses mostly high level subroutine calls and loops.

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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() {

View File

@ -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]]

View File

@ -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) }

View File

@ -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
}

View File

@ -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

View File

@ -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:

View File

@ -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".

View File

@ -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

View File

@ -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
}}
}
}

View File

@ -124,8 +124,32 @@ stack_uw2float .proc
jsr c64flt.GIVUAYFAY
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
; (taking 3 stack positions = 6 bytes of which 1 is padding)
@ -1005,30 +1029,7 @@ 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
lda ESTACK_LO+1,x
@ -1486,74 +1487,256 @@ func_rndf .proc
jmp push_float
_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
rts
_strlen2233
;-- return the length of the (zero-terminated) string at $22/$23, in Y
ldy #0
- lda ($22),y
beq +
iny
bne -
+ rts
.pend
func_str2word .proc
rts
.warn "str2word not implemented"
.pend
func_str2byte .proc
rts
.warn "str2byte not implemented"
.pend
func_str2float .proc
rts
.warn "str2float not implemented"
.pend
}}
}
~ 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
}}
}
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
}}
}
}

View File

@ -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
}}
}

View File

@ -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.

View File

@ -1 +0,0 @@
# package

View File

@ -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()

View File

@ -1 +0,0 @@
# package

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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
"""

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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
})

View File

@ -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)

View File

@ -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")

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -1,3 +0,0 @@
attrs
ply
cbmcodecs >= 0.2.0

View File

@ -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")

View File

@ -1,3 +0,0 @@
[pycodestyle]
max-line-length = 140
exclude = .git,__pycache__,.tox,docs,tests,build,dist,parsetab.py

View File

@ -1 +0,0 @@
# package

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1,2 +0,0 @@
; this assembly code is included as-is
nop

View File

@ -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

View File

@ -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
}
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -1 +0,0 @@
# package

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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()

View File

@ -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

View File

@ -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()