2017-12-21 13:52:30 +00:00
"""
2017-12-25 15:00:25 +00:00
Programming Language for 6502 / 6510 microprocessors , codename ' Sick '
2017-12-21 21:16:46 +00:00
This is the assembly code generator ( from the parse tree )
2017-12-21 13:52:30 +00:00
Written by Irmen de Jong ( irmen @razorvine.net )
License : GNU GPL 3.0 , see LICENSE
"""
import io
2017-12-27 22:45:22 +00:00
import re
2017-12-21 13:52:30 +00:00
import math
import datetime
import subprocess
import contextlib
from functools import partial
2017-12-28 18:08:33 +00:00
from typing import TextIO , Set , Union , List , Tuple , Callable
2017-12-21 21:16:46 +00:00
from . parse import ProgramFormat , ParseResult , Parser
2017-12-23 13:36:23 +00:00
from . symbols import Zeropage , DataType , ConstantDef , VariableDef , SubroutineDef , \
2017-12-28 03:20:59 +00:00
STRING_DATATYPES , REGISTER_WORDS , REGISTER_BYTES , FLOAT_MAX_NEGATIVE , FLOAT_MAX_POSITIVE
2017-12-21 13:52:30 +00:00
class CodeError ( Exception ) :
pass
class CodeGenerator :
2017-12-27 22:45:22 +00:00
BREAKPOINT_COMMENT_SIGNATURE = " ~~~BREAKPOINT~~~ "
BREAKPOINT_COMMENT_DETECTOR = r " .(?P<address> \ w+) \ s+ea \ s+nop \ s+; \ s+ {:s} .* " . format ( BREAKPOINT_COMMENT_SIGNATURE )
2017-12-21 13:52:30 +00:00
def __init__ ( self , parsed : ParseResult ) - > None :
self . parsed = parsed
self . generated_code = io . StringIO ( )
self . p = partial ( print , file = self . generated_code )
self . previous_stmt_was_assignment = False
self . cur_block = None # type: ParseResult.Block
def generate ( self ) - > None :
2017-12-21 21:16:46 +00:00
print ( " \n generating assembly code " )
2017-12-21 13:52:30 +00:00
self . sanitycheck ( )
self . header ( )
self . initialize_variables ( )
self . blocks ( )
self . footer ( )
def sanitycheck ( self ) - > None :
# duplicate block names?
all_blocknames = [ b . name for b in self . parsed . blocks if b . name ]
unique_blocknames = set ( all_blocknames )
if len ( all_blocknames ) != len ( unique_blocknames ) :
for name in unique_blocknames :
all_blocknames . remove ( name )
raise CodeError ( " there are duplicate block names " , all_blocknames )
# ZP block contains no code?
for zpblock in [ b for b in self . parsed . blocks if b . name == " ZP " ] :
if zpblock . label_names :
raise CodeError ( " ZP block cannot contain labels " )
2017-12-21 22:05:35 +00:00
# can only contain code comments, or nothing at all
if not all ( isinstance ( s , ParseResult . Comment ) for s in zpblock . statements ) :
raise CodeError ( " ZP block cannot contain code statements, only definitions and comments " )
2017-12-21 13:52:30 +00:00
def optimize ( self ) - > None :
# optimize the generated assembly code
pass
def write_assembly ( self , out : TextIO ) - > None :
out . write ( self . generated_code . getvalue ( ) )
def header ( self ) - > None :
self . p ( " ; code generated by il65.py - codename ' Sick ' " )
self . p ( " ; source file: " , self . parsed . sourcefile )
if self . parsed . with_sys :
self . p ( " ; output format: " , self . parsed . format . value , " (with basic program SYS) " )
else :
self . p ( " ; output format: " , self . parsed . format . value )
self . p ( " ; assembler syntax is for 64tasm " )
self . p ( " .cpu ' 6502 ' \n .enc ' none ' \n " )
if self . parsed . format == ProgramFormat . PRG :
if self . parsed . with_sys :
self . p ( " ; ---- basic program with sys call ---- " )
2017-12-21 21:16:46 +00:00
self . p ( " * = " + Parser . to_hex ( self . parsed . start_address ) )
2017-12-21 13:52:30 +00:00
year = datetime . datetime . now ( ) . year
self . p ( " \t \t .word (+), {:d} " . format ( year ) )
self . p ( " \t \t .null $9e, format( ' %d ' , _il65_sysaddr), $3a, $8f, ' il65 by idj ' " )
self . p ( " + \t \t .word 0 " )
self . p ( " _il65_sysaddr \t \t ; assembly code starts here \n " )
else :
self . p ( " ; ---- program without sys call ---- " )
2017-12-21 21:16:46 +00:00
self . p ( " * = " + Parser . to_hex ( self . parsed . start_address ) + " \n " )
2017-12-21 13:52:30 +00:00
if self . parsed . format == ProgramFormat . RAW :
self . p ( " ; ---- raw assembler program ---- " )
2017-12-21 21:16:46 +00:00
self . p ( " * = " + Parser . to_hex ( self . parsed . start_address ) + " \n " )
2017-12-21 13:52:30 +00:00
@staticmethod
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 " )
@staticmethod
def mflpt5_to_float ( mflpt : bytearray ) - > float :
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
def initialize_variables ( self ) - > None :
must_save_zp = self . parsed . clobberzp and self . parsed . restorezp
if must_save_zp :
self . p ( " ; save zp " )
self . p ( " \t \t sei " )
self . p ( " \t \t ldx #2 " )
self . p ( " - \t \t lda $00,x " )
self . p ( " \t \t sta _il65_zp_backup-2,x " )
self . p ( " \t \t inx " )
self . p ( " \t \t bne - " )
# Only the vars from the ZeroPage need to be initialized here,
# the vars in all other blocks are just defined and pre-filled there.
zpblocks = [ b for b in self . parsed . blocks if b . name == " ZP " ]
if zpblocks :
assert len ( zpblocks ) == 1
zpblock = zpblocks [ 0 ]
vars_to_init = [ v for v in zpblock . symbols . iter_variables ( )
if v . allocate and v . type in ( DataType . BYTE , DataType . WORD , DataType . FLOAT ) ]
# @todo optimize sort order (sort on value first, then type, then blockname, then address/name)
# (str(self.value) or "", self.blockname, self.name or "", self.address or 0, self.seq_nr)
prev_value = 0 # type: Union[str, int, float]
if vars_to_init :
self . p ( " ; init zp vars " )
self . p ( " \t \t lda #0 \n \t \t ldx #0 " )
for variable in vars_to_init :
2017-12-23 00:53:48 +00:00
vname = zpblock . label + ' . ' + variable . name
2017-12-21 13:52:30 +00:00
vvalue = variable . value
if variable . type == DataType . BYTE :
if vvalue != prev_value :
self . p ( " \t \t lda #$ {:02x} " . format ( vvalue ) )
prev_value = vvalue
self . p ( " \t \t sta {:s} " . format ( vname ) )
elif variable . type == DataType . WORD :
if vvalue != prev_value :
self . p ( " \t \t lda #<$ {:04x} " . format ( vvalue ) )
self . p ( " \t \t ldx #>$ {:04x} " . format ( vvalue ) )
prev_value = vvalue
self . p ( " \t \t sta {:s} " . format ( vname ) )
self . p ( " \t \t stx {:s} +1 " . format ( vname ) )
elif variable . type == DataType . FLOAT :
2017-12-27 18:01:14 +00:00
raise CodeError ( " floats cannot be stored in the zp " )
2017-12-21 13:52:30 +00:00
self . p ( " ; end init zp vars " )
else :
self . p ( " \t \t ; there are no zp vars to initialize " )
else :
self . p ( " \t \t ; there is no zp block to initialize " )
main_block_label = [ b . label for b in self . parsed . blocks if b . name == " main " ] [ 0 ]
if must_save_zp :
self . p ( " \t \t jsr {:s} .start \t \t ; call user code " . format ( main_block_label ) )
self . p ( " ; restore zp " )
self . p ( " \t \t cld " )
self . p ( " \t \t php \n \t \t pha \n \t \t txa \n \t \t pha \n \t \t sei " )
self . p ( " \t \t ldx #2 " )
self . p ( " - \t \t lda _il65_zp_backup-2,x " )
self . p ( " \t \t sta $00,x " )
self . p ( " \t \t inx " )
self . p ( " \t \t bne - " )
self . p ( " \t \t cli \n \t \t pla \n \t \t tax \n \t \t pla \n \t \t plp " )
self . p ( " \t \t rts " )
self . p ( " _il65_zp_backup \t \t .fill 254, 0 " )
else :
self . p ( " \t \t jmp {:s} .start \t \t ; call user code " . format ( main_block_label ) )
def blocks ( self ) - > None :
2017-12-21 22:05:35 +00:00
# if there's a <header> block, it always goes second
for block in [ b for b in self . parsed . blocks if b . name == " <header> " ] :
self . cur_block = block
for s in block . statements :
if isinstance ( s , ParseResult . Comment ) :
self . p ( s . text )
else :
raise CodeError ( " header block cannot contain any other statements beside comments " )
self . p ( " \n " )
# if there's a Zeropage block, it always goes second
2017-12-21 13:52:30 +00:00
for zpblock in [ b for b in self . parsed . blocks if b . name == " ZP " ] :
self . cur_block = zpblock
self . p ( " \n ; ---- zero page block: ' {:s} ' ---- \t \t ; src l. {:d} \n " . format ( zpblock . sourceref . file , zpblock . sourceref . line ) )
2017-12-21 22:05:35 +00:00
for s in zpblock . statements :
if isinstance ( s , ParseResult . Comment ) :
self . p ( s . text )
else :
raise CodeError ( " zp cannot contain any other statements beside comments " )
2017-12-21 13:52:30 +00:00
self . p ( " {:s} \t .proc \n " . format ( zpblock . label ) )
self . generate_block_vars ( zpblock )
self . p ( " \t .pend \n " )
# make sure the main.start routine clears the decimal and carry flags as first steps
2017-12-25 18:09:10 +00:00
block = self . parsed . find_block ( " main " )
statements = list ( block . statements )
for index , stmt in enumerate ( statements ) :
if isinstance ( stmt , ParseResult . Label ) and stmt . name == " start " :
asmlines = [
" \t \t cld \t \t \t ; clear decimal flag " ,
2017-12-28 03:20:59 +00:00
" \t \t clc \t \t \t ; clear carry flag " ,
" \t \t clv \t \t \t ; clear overflow flag " ,
2017-12-25 18:09:10 +00:00
]
2017-12-27 18:01:14 +00:00
statements . insert ( index + 1 , ParseResult . InlineAsm ( asmlines , 0 ) )
2017-12-25 18:09:10 +00:00
break
block . statements = statements
2017-12-21 13:52:30 +00:00
# generate
for block in sorted ( self . parsed . blocks , key = lambda b : b . address ) :
2017-12-21 22:05:35 +00:00
if block . name in ( " ZP " , " <header> " ) :
continue # these blocks are already processed
2017-12-21 13:52:30 +00:00
self . cur_block = block
self . p ( " \n ; ---- next block: ' {:s} ' ---- \t \t ; src l. {:d} \n " . format ( block . sourceref . file , block . sourceref . line ) )
if block . address :
self . p ( " .cerror * > $ {0:04x} , ' block address overlaps by ' , *-$ {0:04x} , ' bytes ' " . format ( block . address ) )
self . p ( " * = $ {:04x} " . format ( block . address ) )
self . p ( " {:s} \t .proc \n " . format ( block . label ) )
self . generate_block_vars ( block )
2017-12-23 23:58:55 +00:00
subroutines = list ( sub for sub in block . symbols . iter_subroutines ( ) if sub . address is not None )
2017-12-21 13:52:30 +00:00
if subroutines :
self . p ( " \n ; external subroutines " )
for subdef in subroutines :
2017-12-23 23:58:55 +00:00
assert subdef . sub_block is None
2017-12-21 21:16:46 +00:00
self . p ( " \t \t {:s} = {:s} " . format ( subdef . name , Parser . to_hex ( subdef . address ) ) )
2017-12-21 13:52:30 +00:00
self . p ( " ; end external subroutines " )
for stmt in block . statements :
self . generate_statement ( stmt )
2017-12-25 12:20:23 +00:00
subroutines = list ( sub for sub in block . symbols . iter_subroutines ( True ) )
2017-12-23 23:58:55 +00:00
if subroutines :
self . p ( " \n ; block subroutines " )
for subdef in subroutines :
assert subdef . sub_block is not None
self . p ( " {:s} \t \t ; src l. {:d} " . format ( subdef . name , subdef . sourceref . line ) )
params = " , " . join ( " {:s} -> {:s} " . format ( p [ 0 ] or " <unnamed> " , p [ 1 ] ) for p in subdef . parameters )
returns = " , " . join ( sorted ( subdef . return_registers ) )
clobbers = " , " . join ( sorted ( subdef . clobbered_registers ) )
self . p ( " \t \t ; params: {} \n \t \t ; returns: {} clobbers: {} "
. format ( params or " - " , returns or " - " , clobbers or " - " ) )
cur_block = self . cur_block
self . cur_block = subdef . sub_block
for stmt in subdef . sub_block . statements :
self . generate_statement ( stmt )
self . cur_block = cur_block
self . p ( " " )
self . p ( " ; end external subroutines " )
2017-12-21 13:52:30 +00:00
self . p ( " \t .pend \n " )
def generate_block_vars ( self , block : ParseResult . Block ) - > None :
2017-12-23 00:53:48 +00:00
consts = [ c for c in block . symbols . iter_constants ( ) ]
if consts :
self . p ( " ; constants " )
for constdef in consts :
if constdef . type == DataType . FLOAT :
self . p ( " \t \t {:s} = {} " . format ( constdef . name , constdef . value ) )
elif constdef . type in ( DataType . BYTE , DataType . WORD ) :
self . p ( " \t \t {:s} = {:s} " . format ( constdef . name , Parser . to_hex ( constdef . value ) ) ) # type: ignore
2017-12-23 13:36:23 +00:00
elif constdef . type in STRING_DATATYPES :
# a const string is just a string variable in the generated assembly
self . _generate_string_var ( constdef )
2017-12-23 00:53:48 +00:00
else :
raise CodeError ( " invalid const type " , constdef )
2017-12-21 13:52:30 +00:00
mem_vars = [ vi for vi in block . symbols . iter_variables ( ) if not vi . allocate and not vi . register ]
if mem_vars :
self . p ( " ; memory mapped variables " )
for vardef in mem_vars :
# create a definition for variables at a specific place in memory (memory-mapped)
if vardef . type in ( DataType . BYTE , DataType . WORD , DataType . FLOAT ) :
2017-12-21 21:16:46 +00:00
self . p ( " \t \t {:s} = {:s} \t ; {:s} " . format ( vardef . name , Parser . to_hex ( vardef . address ) , vardef . type . name . lower ( ) ) )
2017-12-21 13:52:30 +00:00
elif vardef . type == DataType . BYTEARRAY :
2017-12-21 21:16:46 +00:00
self . p ( " \t \t {:s} = {:s} \t ; array of {:d} bytes " . format ( vardef . name , Parser . to_hex ( vardef . address ) , vardef . length ) )
2017-12-21 13:52:30 +00:00
elif vardef . type == DataType . WORDARRAY :
2017-12-21 21:16:46 +00:00
self . p ( " \t \t {:s} = {:s} \t ; array of {:d} words " . format ( vardef . name , Parser . to_hex ( vardef . address ) , vardef . length ) )
2017-12-21 13:52:30 +00:00
elif vardef . type == DataType . MATRIX :
self . p ( " \t \t {:s} = {:s} \t ; matrix {:d} by {:d} = {:d} bytes "
2017-12-21 21:16:46 +00:00
. format ( vardef . name , Parser . to_hex ( vardef . address ) , vardef . matrixsize [ 0 ] , vardef . matrixsize [ 1 ] , vardef . length ) )
2017-12-21 13:52:30 +00:00
else :
2017-12-27 18:01:14 +00:00
raise CodeError ( " invalid var type " )
2017-12-21 13:52:30 +00:00
non_mem_vars = [ vi for vi in block . symbols . iter_variables ( ) if vi . allocate ]
if non_mem_vars :
self . p ( " ; normal variables " )
for vardef in non_mem_vars :
# create a definition for a variable that takes up space and will be initialized at startup
if vardef . type in ( DataType . BYTE , DataType . WORD , DataType . FLOAT ) :
if vardef . address :
assert block . name == " ZP " , " only ZP-variables can be put on an address "
2017-12-21 21:16:46 +00:00
self . p ( " \t \t {:s} = {:s} " . format ( vardef . name , Parser . to_hex ( vardef . address ) ) )
2017-12-21 13:52:30 +00:00
else :
if vardef . type == DataType . BYTE :
2017-12-21 21:16:46 +00:00
self . p ( " {:s} \t \t .byte {:s} " . format ( vardef . name , Parser . to_hex ( int ( vardef . value ) ) ) )
2017-12-21 13:52:30 +00:00
elif vardef . type == DataType . WORD :
2017-12-21 21:16:46 +00:00
self . p ( " {:s} \t \t .word {:s} " . format ( vardef . name , Parser . to_hex ( int ( vardef . value ) ) ) )
2017-12-21 13:52:30 +00:00
elif vardef . type == DataType . FLOAT :
self . p ( " {:s} \t \t .byte $ {:02x} , $ {:02x} , $ {:02x} , $ {:02x} , $ {:02x} "
. format ( vardef . name , * self . to_mflpt5 ( float ( vardef . value ) ) ) )
else :
2017-12-27 18:01:14 +00:00
raise CodeError ( " weird datatype " )
2017-12-21 13:52:30 +00:00
elif vardef . type in ( DataType . BYTEARRAY , DataType . WORDARRAY ) :
if vardef . address :
raise CodeError ( " array or wordarray vars must not have address; will be allocated by assembler " )
if vardef . type == DataType . BYTEARRAY :
self . p ( " {:s} \t \t .fill {:d} , $ {:02x} " . format ( vardef . name , vardef . length , vardef . value or 0 ) )
elif vardef . type == DataType . WORDARRAY :
f_hi , f_lo = divmod ( vardef . value or 0 , 256 ) # type: ignore
self . p ( " {:s} \t \t .fill {:d} , [$ {:02x} , $ {:02x} ] \t ; {:d} words of $ {:04x} "
. format ( vardef . name , vardef . length * 2 , f_lo , f_hi , vardef . length , vardef . value or 0 ) )
else :
2017-12-27 18:01:14 +00:00
raise CodeError ( " invalid datatype " , vardef . type )
2017-12-21 13:52:30 +00:00
elif vardef . type == DataType . MATRIX :
if vardef . address :
raise CodeError ( " matrix vars must not have address; will be allocated by assembler " )
self . p ( " {:s} \t \t .fill {:d} , $ {:02x} \t \t ; matrix {:d} * {:d} bytes "
. format ( vardef . name ,
vardef . matrixsize [ 0 ] * vardef . matrixsize [ 1 ] ,
vardef . value or 0 ,
vardef . matrixsize [ 0 ] , vardef . matrixsize [ 1 ] ) )
2017-12-23 13:36:23 +00:00
elif vardef . type in STRING_DATATYPES :
self . _generate_string_var ( vardef )
2017-12-21 13:52:30 +00:00
else :
raise CodeError ( " unknown variable type " + str ( vardef . type ) )
2017-12-23 13:36:23 +00:00
def _generate_string_var ( self , vardef : Union [ ConstantDef , VariableDef ] ) - > None :
if vardef . type == DataType . STRING :
# 0-terminated string
self . p ( " {:s} \n \t \t .null {:s} " . format ( vardef . name , self . output_string ( str ( vardef . value ) ) ) )
elif vardef . type == DataType . STRING_P :
# pascal string
self . p ( " {:s} \n \t \t .ptext {:s} " . format ( vardef . name , self . output_string ( str ( vardef . value ) ) ) )
elif vardef . type == DataType . STRING_S :
# 0-terminated string in screencode encoding
self . p ( " .enc ' screen ' " )
self . p ( " {:s} \n \t \t .null {:s} " . format ( vardef . name , self . output_string ( str ( vardef . value ) , True ) ) )
self . p ( " .enc ' none ' " )
elif vardef . type == DataType . STRING_PS :
# 0-terminated pascal string in screencode encoding
self . p ( " .enc ' screen ' " )
self . p ( " {:s} \n \t \t .ptext {:s} " . format ( vardef . name , self . output_string ( str ( vardef . value ) , True ) ) )
self . p ( " .enc ' none ' " )
2017-12-21 21:16:46 +00:00
def generate_statement ( self , stmt : ParseResult . _AstNode ) - > None :
2017-12-21 13:52:30 +00:00
if isinstance ( stmt , ParseResult . ReturnStmt ) :
if stmt . a :
if isinstance ( stmt . a , ParseResult . IntegerValue ) :
self . p ( " \t \t lda # {:d} " . format ( stmt . a . value ) )
else :
raise CodeError ( " can only return immediate values for now " ) # XXX
if stmt . x :
if isinstance ( stmt . x , ParseResult . IntegerValue ) :
self . p ( " \t \t ldx # {:d} " . format ( stmt . x . value ) )
else :
raise CodeError ( " can only return immediate values for now " ) # XXX
if stmt . y :
if isinstance ( stmt . y , ParseResult . IntegerValue ) :
self . p ( " \t \t ldy # {:d} " . format ( stmt . y . value ) )
else :
raise CodeError ( " can only return immediate values for now " ) # XXX
self . p ( " \t \t rts " )
2017-12-28 03:20:59 +00:00
elif isinstance ( stmt , ParseResult . AugmentedAssignmentStmt ) :
self . generate_augmented_assignment ( stmt )
2017-12-21 13:52:30 +00:00
elif isinstance ( stmt , ParseResult . AssignmentStmt ) :
self . generate_assignment ( stmt )
elif isinstance ( stmt , ParseResult . Label ) :
self . p ( " \n {:s} \t \t \t \t ; src l. {:d} " . format ( stmt . name , stmt . lineno ) )
2017-12-28 03:20:59 +00:00
elif isinstance ( stmt , ( ParseResult . InplaceIncrStmt , ParseResult . InplaceDecrStmt ) ) :
self . generate_incr_or_decr ( stmt )
2017-12-21 13:52:30 +00:00
elif isinstance ( stmt , ParseResult . CallStmt ) :
2017-12-23 00:53:48 +00:00
self . generate_call ( stmt )
2017-12-21 13:52:30 +00:00
elif isinstance ( stmt , ParseResult . InlineAsm ) :
self . p ( " \t \t ; inline asm, src l. {:d} " . format ( stmt . lineno ) )
for line in stmt . asmlines :
self . p ( line )
self . p ( " \t \t ; end inline asm, src l. {:d} " . format ( stmt . lineno ) )
2017-12-21 22:05:35 +00:00
elif isinstance ( stmt , ParseResult . Comment ) :
self . p ( stmt . text )
2017-12-27 22:45:22 +00:00
elif isinstance ( stmt , ParseResult . BreakpointStmt ) :
# put a marker in the source so that we can generate a list of breakpoints later
self . p ( " \t \t nop \t ; {:s} src l. {:d} " . format ( self . BREAKPOINT_COMMENT_SIGNATURE , stmt . lineno ) )
2017-12-21 13:52:30 +00:00
else :
raise CodeError ( " unknown statement " + repr ( stmt ) )
self . previous_stmt_was_assignment = isinstance ( stmt , ParseResult . AssignmentStmt )
2017-12-28 03:20:59 +00:00
def generate_incr_or_decr ( self , stmt : Union [ ParseResult . InplaceIncrStmt , ParseResult . InplaceDecrStmt ] ) - > None :
if stmt . what . datatype == DataType . FLOAT :
raise CodeError ( " incr/decr on float not yet supported " ) # @todo support incr/decr on float
else :
assert type ( stmt . howmuch ) is int
assert stmt . howmuch > 0
if stmt . howmuch > 0xff :
raise CodeError ( " only supports incr/decr by up to 255 for now " ) # XXX
is_incr = isinstance ( stmt , ParseResult . InplaceIncrStmt )
if isinstance ( stmt . what , ParseResult . RegisterValue ) :
if is_incr :
if stmt . what . register == ' A ' :
self . p ( " \t \t clc " )
self . p ( " \t \t adc # {:d} " . format ( stmt . howmuch ) )
elif stmt . what . register in REGISTER_BYTES :
if stmt . howmuch == 1 :
2017-12-23 00:53:48 +00:00
self . p ( " \t \t in {:s} " . format ( stmt . what . register . lower ( ) ) )
else :
2017-12-28 03:20:59 +00:00
self . p ( " \t \t pha " )
self . p ( " \t \t t {:s} a " . format ( stmt . what . register . lower ( ) ) )
self . p ( " \t \t clc " )
self . p ( " \t \t adc # {:d} " . format ( stmt . howmuch ) )
self . p ( " \t \t ta {:s} " . format ( stmt . what . register . lower ( ) ) )
self . p ( " \t \t pla " )
else :
raise CodeError ( " invalid incr/decr register " )
else :
if stmt . what . register == ' A ' :
self . p ( " \t \t adc # {:d} " . format ( stmt . howmuch ) )
elif stmt . what . register in REGISTER_BYTES :
if stmt . howmuch == 1 :
2017-12-23 00:53:48 +00:00
self . p ( " \t \t de {:s} " . format ( stmt . what . register . lower ( ) ) )
else :
2017-12-28 03:20:59 +00:00
self . p ( " \t \t pha " )
self . p ( " \t \t t {:s} a " . format ( stmt . what . register . lower ( ) ) )
self . p ( " \t \t sbc # {:d} " . format ( stmt . howmuch ) )
self . p ( " \t \t ta {:s} " . format ( stmt . what . register . lower ( ) ) )
self . p ( " \t \t pla " )
2017-12-23 00:53:48 +00:00
else :
2017-12-28 03:20:59 +00:00
raise CodeError ( " invalid incr/decr register " )
elif isinstance ( stmt . what , ( ParseResult . MemMappedValue , ParseResult . IndirectValue ) ) :
what = stmt . what
if isinstance ( what , ParseResult . IndirectValue ) :
if isinstance ( what . value , ParseResult . IntegerValue ) :
r_str = what . value . name or Parser . to_hex ( what . value . value )
else :
raise CodeError ( " invalid incr indirect type " , what . value )
else :
r_str = what . name or Parser . to_hex ( what . address )
if what . datatype == DataType . BYTE :
if stmt . howmuch == 1 :
self . p ( " \t \t {:s} {:s} " . format ( " inc " if is_incr else " dec " , r_str ) )
else :
self . p ( " \t \t pha " )
self . p ( " \t \t lda " + r_str )
if is_incr :
self . p ( " \t \t clc " )
self . p ( " \t \t adc # {:d} " . format ( stmt . howmuch ) )
2017-12-23 00:53:48 +00:00
else :
2017-12-28 03:20:59 +00:00
self . p ( " \t \t sbc # {:d} " . format ( stmt . howmuch ) )
self . p ( " \t \t sta " + r_str )
self . p ( " \t \t pla " )
elif what . datatype == DataType . WORD :
# @todo verify this 16-bit incr/decr asm code
if stmt . howmuch == 1 :
if is_incr :
2017-12-23 00:53:48 +00:00
self . p ( " \t \t inc " + r_str )
self . p ( " \t \t bne + " )
self . p ( " \t \t inc {:s} +1 " . format ( r_str ) )
self . p ( " + " )
else :
self . p ( " \t \t dec " + r_str )
self . p ( " \t \t bne + " )
self . p ( " \t \t dec {:s} +1 " . format ( r_str ) )
self . p ( " + " )
else :
2017-12-28 03:20:59 +00:00
raise CodeError ( " cannot yet incr/decr 16 bit memory by more than 1 " ) # @todo 16-bit incr/decr
2017-12-23 00:53:48 +00:00
else :
2017-12-28 03:20:59 +00:00
raise CodeError ( " cannot in/decrement memory of type " + str ( what . datatype ) )
else :
raise CodeError ( " cannot in/decrement " + str ( stmt . what ) )
2017-12-23 00:53:48 +00:00
def generate_call ( self , stmt : ParseResult . CallStmt ) - > None :
2017-12-27 18:01:14 +00:00
self . p ( " \t \t \t \t \t ; src l. {:d} " . format ( stmt . lineno ) )
if stmt . condition :
assert stmt . is_goto
if stmt . condition . lvalue :
if stmt . condition . comparison_op :
2017-12-28 18:08:33 +00:00
self . _generate_goto_conditional_comparison ( stmt )
else :
self . _generate_goto_conditional_truthvalue ( stmt )
else :
self . _generate_goto_conditional_if ( stmt )
else :
# unconditional goto or subroutine call.
def branch_emitter ( targetstr : str , is_goto : bool , target_indirect : bool ) - > None :
if is_goto :
if target_indirect :
self . p ( " \t \t jmp ( {:s} ) " . format ( targetstr ) )
2017-12-27 18:01:14 +00:00
else :
2017-12-28 18:08:33 +00:00
self . p ( " \t \t jmp {:s} " . format ( targetstr ) )
2017-12-27 18:01:14 +00:00
else :
2017-12-28 18:08:33 +00:00
assert not target_indirect
self . p ( " \t \t jsr " + targetstr )
self . _generate_call_or_goto ( stmt , branch_emitter )
def _generate_goto_conditional_if ( self , stmt ) :
# a goto with just an if-condition, no condition expression
def branch_emitter ( targetstr : str , is_goto : bool , target_indirect : bool ) - > None :
assert is_goto and not stmt . condition . comparison_op
ifs = stmt . condition . ifstatus
if target_indirect :
if ifs == " true " :
self . p ( " \t \t beq + " )
self . p ( " \t \t jmp ( {:s} ) " . format ( targetstr ) )
self . p ( " + " )
elif ifs in ( " not " , " zero " ) :
self . p ( " \t \t bne + " )
self . p ( " \t \t jmp ( {:s} ) " . format ( targetstr ) )
self . p ( " + " )
elif ifs in ( " cc " , " cs " , " vc " , " vs " , " eq " , " ne " ) :
if ifs == " cc " :
self . p ( " \t \t bcs + " )
elif ifs == " cs " :
self . p ( " \t \t bcc + " )
elif ifs == " vc " :
self . p ( " \t \t bvs + " )
elif ifs == " vs " :
self . p ( " \t \t bvc + " )
elif ifs == " eq " :
self . p ( " \t \t bne + " )
elif ifs == " ne " :
self . p ( " \t \t beq + " )
self . p ( " \t \t jmp ( {:s} ) " . format ( targetstr ) )
self . p ( " + " )
elif ifs == " lt " :
self . p ( " \t \t bcs + " )
self . p ( " \t \t jmp ( {:s} ) " . format ( targetstr ) )
self . p ( " + " )
elif ifs == " gt " :
self . p ( " \t \t bcc + " )
self . p ( " \t \t beq + " )
self . p ( " \t \t jmp ( {:s} ) " . format ( targetstr ) )
self . p ( " + " )
elif ifs == " ge " :
self . p ( " \t \t bcc + " )
self . p ( " \t \t jmp ( {:s} ) " . format ( targetstr ) )
self . p ( " + " )
elif ifs == " le " :
self . p ( " \t \t beq + " )
self . p ( " \t \t bcs ++ " )
self . p ( " + \t \t jmp ( {:s} ) " . format ( targetstr ) )
self . p ( " + " )
else :
raise CodeError ( " invalid if status " + ifs )
else :
if ifs == " true " :
self . p ( " \t \t bne " + targetstr )
elif ifs in ( " not " , " zero " ) :
self . p ( " \t \t beq " + targetstr )
elif ifs in ( " cc " , " cs " , " vc " , " vs " , " eq " , " ne " ) :
self . p ( " \t \t b {:s} {:s} " . format ( ifs , targetstr ) )
elif ifs == " lt " :
self . p ( " \t \t bcc " + targetstr )
elif ifs == " gt " :
self . p ( " \t \t beq + " )
self . p ( " \t \t bcs " + targetstr )
self . p ( " + " )
elif ifs == " ge " :
self . p ( " \t \t bcs " + targetstr )
elif ifs == " le " :
self . p ( " \t \t bcc " + targetstr )
self . p ( " \t \t beq " + targetstr )
else :
raise CodeError ( " invalid if status " + ifs )
self . _generate_call_or_goto ( stmt , branch_emitter )
def _generate_goto_conditional_truthvalue ( self , stmt : ParseResult . 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 , ParseResult . MemMappedValue )
cv_str = cv . name or Parser . to_hex ( cv . address )
if cv . datatype == DataType . BYTE :
self . p ( " \t \t sta " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) # need to save A, because the goto may not be taken
self . p ( " \t \t lda " + cv_str )
self . p ( " \t \t {:s} {:s} " . format ( branch , targetstr ) )
self . p ( " \t \t lda " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) # restore A
elif cv . datatype == DataType . WORD :
self . p ( " \t \t sta " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) # need to save A, because the goto may not be taken
self . p ( " \t \t lda " + cv_str )
if stmt . condition . ifstatus == " true " :
self . p ( " \t \t {:s} {:s} " . format ( branch , targetstr ) )
self . p ( " \t \t lda {:s} +1 " . format ( cv_str ) )
self . p ( " \t \t {:s} {:s} " . format ( branch , targetstr ) )
self . p ( " \t \t lda " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) # restore A
else :
self . p ( " \t \t {:s} + " . format ( inverse_branch , targetstr ) )
self . p ( " \t \t lda {:s} +1 " . format ( cv_str ) )
self . p ( " \t \t {:s} {:s} " . format ( branch , targetstr ) )
self . p ( " + \t \t lda " + 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 ) , self . cur_block . sourceref . file , stmt . lineno )
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 , ParseResult . RegisterValue )
if cv . register == ' A ' :
self . p ( " \t \t cmp #0 " )
elif cv . register == ' X ' :
self . p ( " \t \t cpx #0 " )
elif cv . register == ' Y ' :
self . p ( " \t \t cpy #0 " )
else :
if cv . register == ' AX ' :
line_after_branch = " + "
self . p ( " \t \t cmp #0 " )
self . p ( " \t \t {:s} {:s} " . format ( inverse_branch , line_after_branch ) )
self . p ( " \t \t cpx #0 " )
elif cv . register == ' AY ' :
line_after_branch = " + "
self . p ( " \t \t cmp #0 " )
self . p ( " \t \t {:s} {:s} " . format ( inverse_branch , line_after_branch ) )
self . p ( " \t \t cpy #0 " )
elif cv . register == ' XY ' :
line_after_branch = " + "
self . p ( " \t \t cpx #0 " )
self . p ( " \t \t {:s} {:s} " . format ( inverse_branch , line_after_branch ) )
self . p ( " \t \t cpy #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 :
print ( " EMIT " , stmt . target , stmt . condition , is_goto , target_indirect )
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 " )
cv = stmt . condition . lvalue . value # type: ignore
if isinstance ( cv , ParseResult . RegisterValue ) :
raise CodeError ( " indirect registers not yet supported " ) # @todo indirect reg
elif isinstance ( cv , ParseResult . MemMappedValue ) :
raise CodeError ( " memmapped indirect should not occur, use the variable without indirection " )
elif isinstance ( cv , ParseResult . 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 \t sta " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) # need to save A, because the goto may not be taken
self . p ( " \t \t lda " + cv_str )
self . p ( " \t \t {:s} {:s} " . format ( branch , targetstr ) )
self . p ( " \t \t lda " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) # restore A
elif cv . datatype == DataType . WORD :
self . p ( " \t \t sta " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) # need to save A, because the goto may not be taken
self . p ( " \t \t lda " + cv_str )
if stmt . condition . ifstatus == " true " :
self . p ( " \t \t {:s} {:s} " . format ( branch , targetstr ) )
self . p ( " \t \t lda {:s} +1 " . format ( cv_str ) )
self . p ( " \t \t {:s} {:s} " . format ( branch , targetstr ) )
self . p ( " \t \t lda " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) # restore A
2017-12-27 18:01:14 +00:00
else :
2017-12-28 18:08:33 +00:00
self . p ( " \t \t {:s} + " . format ( inverse_branch , targetstr ) )
self . p ( " \t \t lda {:s} +1 " . format ( cv_str ) )
self . p ( " \t \t {:s} {:s} " . format ( branch , targetstr ) )
self . p ( " + \t \t lda " + 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 ) , self . cur_block . sourceref . file , stmt . lineno )
else :
raise CodeError ( " weird indirect type " , str ( cv ) )
2017-12-27 18:01:14 +00:00
2017-12-28 18:08:33 +00:00
cv = stmt . condition . lvalue
if isinstance ( cv , ParseResult . RegisterValue ) :
self . _generate_call_or_goto ( stmt , branch_emitter_reg )
elif isinstance ( cv , ParseResult . MemMappedValue ) :
self . _generate_call_or_goto ( stmt , branch_emitter_mmap )
elif isinstance ( cv , ParseResult . IndirectValue ) :
if isinstance ( cv . value , ParseResult . RegisterValue ) :
self . _generate_call_or_goto ( stmt , branch_emitter_indirect_cond )
elif isinstance ( cv . value , ParseResult . MemMappedValue ) :
self . _generate_call_or_goto ( stmt , branch_emitter_indirect_cond )
elif isinstance ( cv . value , ParseResult . IntegerValue ) and cv . value . constant :
self . _generate_call_or_goto ( stmt , branch_emitter_indirect_cond )
else :
raise CodeError ( " weird indirect type " , str ( cv ) )
2017-12-27 18:01:14 +00:00
else :
2017-12-28 18:08:33 +00:00
raise CodeError ( " need register, memmapped or indirect value " , str ( cv ) )
2017-12-27 18:01:14 +00:00
2017-12-28 18:08:33 +00:00
def _generate_goto_conditional_comparison ( self , stmt : ParseResult . 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 , ParseResult . 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 , ParseResult . RegisterValue ) :
if isinstance ( rv , ParseResult . RegisterValue ) :
self . p ( " \t \t st {:s} {:s} " . format ( rv . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
if lv . register == " A " :
self . p ( " \t \t cmp " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
elif lv . register == " X " :
self . p ( " \t \t cpx " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
elif lv . register == " Y " :
self . p ( " \t \t cpy " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
else :
raise CodeError ( " wrong lvalue register " )
elif isinstance ( rv , ParseResult . IntegerValue ) :
rvstr = rv . name or Parser . to_hex ( rv . value )
if lv . register == " A " :
self . p ( " \t \t cmp # " + rvstr )
elif lv . register == " X " :
self . p ( " \t \t cpx # " + rvstr )
elif lv . register == " Y " :
self . p ( " \t \t cpy # " + rvstr )
else :
raise CodeError ( " wrong lvalue register " )
elif isinstance ( rv , ParseResult . MemMappedValue ) :
rvstr = rv . name or Parser . to_hex ( rv . address )
if lv . register == " A " :
self . p ( " \t \t cmp " + rvstr )
elif lv . register == " X " :
self . p ( " \t \t cpx # " + rvstr )
elif lv . register == " Y " :
self . p ( " \t \t cpy # " + rvstr )
else :
raise CodeError ( " wrong lvalue register " )
else :
raise CodeError ( " invalid rvalue type in comparison " , rv )
elif isinstance ( lv , ParseResult . MemMappedValue ) :
assert not isinstance ( rv , ParseResult . RegisterValue ) , " registers as rvalue should have been swapped with lvalue "
if isinstance ( rv , ParseResult . IntegerValue ) :
self . p ( " \t \t sta " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) # need to save A, because the goto may not be taken
self . p ( " \t \t lda " + ( lv . name or Parser . to_hex ( lv . address ) ) )
self . p ( " \t \t cmp # " + ( rv . name or Parser . to_hex ( rv . value ) ) )
line_after_goto = " \t \t lda " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) # restore A
elif isinstance ( rv , ParseResult . MemMappedValue ) :
rvstr = rv . name or Parser . to_hex ( rv . address )
self . p ( " \t \t sta " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) # need to save A, because the goto may not be taken
self . p ( " \t \t lda " + ( lv . name or Parser . to_hex ( lv . address ) ) )
self . p ( " \t \t cmp " + rvstr )
line_after_goto = " \t \t lda " + 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 )
2017-12-27 18:01:14 +00:00
2017-12-28 18:08:33 +00:00
def _generate_call_or_goto ( self , stmt : ParseResult . CallStmt , branch_emitter : Callable [ [ str , bool , bool ] , None ] ) - > None :
2017-12-25 18:22:22 +00:00
def generate_param_assignments ( ) - > None :
2017-12-28 18:08:33 +00:00
self . p ( " ; param assignment " ) # XXX
2017-12-25 01:07:17 +00:00
for assign_stmt in stmt . desugared_call_arguments :
self . generate_assignment ( assign_stmt )
2017-12-28 18:08:33 +00:00
self . p ( " ; param assignment done " ) # XXX
2017-12-25 18:22:22 +00:00
2017-12-26 00:30:22 +00:00
def generate_result_assignments ( ) - > None :
2017-12-28 18:08:33 +00:00
self . p ( " ; result assignment " ) # XXX
2017-12-26 00:30:22 +00:00
for assign_stmt in stmt . desugared_output_assignments :
self . generate_assignment ( assign_stmt )
2017-12-28 18:08:33 +00:00
self . p ( " ; result assignment done " ) # XXX
2017-12-26 00:30:22 +00:00
2017-12-25 18:22:22 +00:00
def params_load_a ( ) - > bool :
for assign_stmt in stmt . desugared_call_arguments :
for lv in assign_stmt . leftvalues :
if isinstance ( lv , ParseResult . RegisterValue ) :
if lv . register == ' A ' :
return True
return False
2017-12-26 00:30:22 +00:00
def unclobber_result_registers ( registers : Set [ str ] , output_assignments : List [ ParseResult . AssignmentStmt ] ) - > None :
for a in output_assignments :
for lv in a . leftvalues :
if isinstance ( lv , ParseResult . RegisterValue ) :
if len ( lv . register ) == 1 :
2017-12-28 18:08:33 +00:00
registers . discard ( lv . register )
2017-12-26 00:30:22 +00:00
else :
for r in lv . register :
2017-12-28 18:08:33 +00:00
registers . discard ( r )
2017-12-26 00:30:22 +00:00
2017-12-23 00:53:48 +00:00
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 , ParseResult . MemMappedValue ) :
targetstr = stmt . target . name or Parser . to_hex ( stmt . address )
else :
2017-12-27 18:01:14 +00:00
raise CodeError ( " call sub target should be mmapped " )
2017-12-23 01:41:41 +00:00
if stmt . is_goto :
2017-12-25 02:42:20 +00:00
generate_param_assignments ( )
2017-12-28 18:08:33 +00:00
branch_emitter ( targetstr , True , False )
2017-12-26 00:30:22 +00:00
# no result assignments because it's a goto
2017-12-23 01:41:41 +00:00
return
2017-12-23 00:53:48 +00:00
clobbered = set ( ) # type: Set[str]
if targetdef . clobbered_registers :
2017-12-25 01:07:17 +00:00
if stmt . preserve_regs :
2017-12-23 00:53:48 +00:00
clobbered = targetdef . clobbered_registers
2017-12-26 00:30:22 +00:00
unclobber_result_registers ( clobbered , stmt . desugared_output_assignments )
2017-12-25 18:22:22 +00:00
with self . preserving_registers ( clobbered , loads_a_within = params_load_a ( ) ) :
2017-12-25 01:07:17 +00:00
generate_param_assignments ( )
2017-12-28 18:08:33 +00:00
branch_emitter ( targetstr , False , False )
2017-12-26 00:30:22 +00:00
generate_result_assignments ( )
2017-12-23 00:53:48 +00:00
return
if isinstance ( stmt . target , ParseResult . 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 , ParseResult . RegisterValue ) :
targetstr = stmt . target . value . register
elif isinstance ( stmt . target . value , ParseResult . 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
2017-12-25 01:07:17 +00:00
generate_param_assignments ( )
2017-12-23 00:53:48 +00:00
if targetstr in REGISTER_WORDS :
self . p ( " \t \t st {:s} {:s} " . format ( targetstr [ 0 ] . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t st {:s} {:s} " . format ( targetstr [ 1 ] . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B2 ) ) )
2017-12-28 18:08:33 +00:00
branch_emitter ( Parser . to_hex ( Zeropage . SCRATCH_B1 ) , True , True )
2017-12-23 00:53:48 +00:00
else :
2017-12-28 18:08:33 +00:00
branch_emitter ( targetstr , True , True )
2017-12-26 00:30:22 +00:00
# no result assignments because it's a goto
2017-12-23 00:53:48 +00:00
else :
2017-12-28 18:08:33 +00:00
# indirect call to subroutine
2017-12-23 00:53:48 +00:00
preserve_regs = { ' A ' , ' X ' , ' Y ' } if stmt . preserve_regs else set ( )
2017-12-26 00:30:22 +00:00
unclobber_result_registers ( preserve_regs , stmt . desugared_output_assignments )
2017-12-25 18:22:22 +00:00
with self . preserving_registers ( preserve_regs , loads_a_within = params_load_a ( ) ) :
2017-12-25 01:07:17 +00:00
generate_param_assignments ( )
2017-12-28 03:20:59 +00:00
# @todo optimize this with RTS_trick? https://wiki.nesdev.com/w/index.php/6502_assembly_optimisations#Use_Jump_tables_with_RTS_instruction_instead_of_JMP_indirect_instruction
2017-12-23 00:53:48 +00:00
if targetstr in REGISTER_WORDS :
if stmt . preserve_regs :
# cannot use zp scratch. This is very inefficient code!
print ( " warning: {:s} : {:d} : indirect register pair call, this is very inefficient "
. format ( self . cur_block . sourceref . file , stmt . lineno ) )
self . p ( " \t \t st {:s} ++ " . format ( targetstr [ 0 ] . lower ( ) ) )
self . p ( " \t \t st {:s} +++ " . format ( targetstr [ 1 ] . lower ( ) ) )
self . p ( " \t \t jsr + " )
self . p ( " \t \t jmp ++++ " )
self . p ( " + \t \t jmp (+) " )
self . p ( " + \t \t .byte 0 \t ; lo " )
self . p ( " + \t \t .byte 0 \t ; hi " )
self . p ( " + " )
else :
self . p ( " \t \t st {:s} {:s} " . format ( targetstr [ 0 ] . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t st {:s} {:s} " . format ( targetstr [ 1 ] . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B2 ) ) )
self . p ( " \t \t jsr + " )
self . p ( " \t \t jmp ++ " )
self . p ( " + \t \t jmp ( {:s} ) " . format ( Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " + " )
else :
self . p ( " \t \t jsr + " )
self . p ( " \t \t jmp ++ " )
self . p ( " + \t \t jmp ( {:s} ) " . format ( targetstr ) )
self . p ( " + " )
2017-12-26 00:30:22 +00:00
generate_result_assignments ( )
2017-12-23 00:53:48 +00:00
else :
2017-12-28 18:08:33 +00:00
# call to a label or immediate address
2017-12-23 00:53:48 +00:00
if stmt . target . name :
targetstr = stmt . target . name
elif stmt . address is not None :
targetstr = Parser . to_hex ( stmt . address )
elif isinstance ( stmt . target , ParseResult . IntegerValue ) :
targetstr = stmt . target . name or Parser . to_hex ( stmt . target . value )
else :
raise CodeError ( " missing name " , stmt . target )
if stmt . is_goto :
2017-12-25 01:07:17 +00:00
# no need to preserve registers for a goto
generate_param_assignments ( )
2017-12-28 18:08:33 +00:00
branch_emitter ( targetstr , True , False )
2017-12-26 00:30:22 +00:00
# no result assignments because it's a goto
2017-12-23 00:53:48 +00:00
else :
preserve_regs = { ' A ' , ' X ' , ' Y ' } if stmt . preserve_regs else set ( )
2017-12-26 00:30:22 +00:00
unclobber_result_registers ( preserve_regs , stmt . desugared_output_assignments )
2017-12-25 18:22:22 +00:00
with self . preserving_registers ( preserve_regs , loads_a_within = params_load_a ( ) ) :
2017-12-25 01:07:17 +00:00
generate_param_assignments ( )
2017-12-28 18:08:33 +00:00
branch_emitter ( targetstr , False , False )
2017-12-26 00:30:22 +00:00
generate_result_assignments ( )
2017-12-23 00:53:48 +00:00
2017-12-28 03:20:59 +00:00
def generate_augmented_assignment ( self , stmt : ParseResult . AugmentedAssignmentStmt ) - > None :
# for instance: value += 3
lvalue = stmt . leftvalues [ 0 ]
rvalue = stmt . right
self . p ( " \t \t \t \t \t ; src l. {:d} " . format ( stmt . lineno ) )
if isinstance ( lvalue , ParseResult . RegisterValue ) :
if isinstance ( rvalue , ParseResult . IntegerValue ) :
self . _generate_aug_reg_int ( lvalue , stmt . operator , rvalue )
elif isinstance ( rvalue , ParseResult . RegisterValue ) :
self . _generate_aug_reg_reg ( lvalue , stmt . operator , rvalue )
2017-12-28 18:08:33 +00:00
elif isinstance ( rvalue , ParseResult . MemMappedValue ) :
self . _generate_aug_reg_mem ( lvalue , stmt . operator , rvalue )
2017-12-28 03:20:59 +00:00
else :
2017-12-28 18:08:33 +00:00
raise CodeError ( " invalid rvalue for augmented assignment on register " , str ( rvalue ) )
2017-12-28 03:20:59 +00:00
else :
2017-12-28 18:08:33 +00:00
raise CodeError ( " augmented assignment only implemented for registers for now " ) # XXX
def _generate_aug_reg_mem ( self , lvalue : ParseResult . RegisterValue , operator : str , rvalue : ParseResult . MemMappedValue ) - > None :
r_str = rvalue . name or Parser . to_hex ( rvalue . address )
if operator == " += " :
self . p ( " \t \t clc " )
if lvalue . register == " A " :
self . p ( " \t \t adc " + r_str )
elif lvalue . register == " X " :
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t adc " + r_str )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t adc " + r_str )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported register for aug assign " , str ( lvalue ) ) # @todo +=.word
elif operator == " -= " :
if lvalue . register == " A " :
self . p ( " \t \t sbc " + r_str )
elif lvalue . register == " X " :
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t sbc " + r_str )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t sbc " + r_str )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported register for aug assign " , str ( lvalue ) ) # @todo -=.word
elif operator == " &= " :
if lvalue . register == " A " :
self . p ( " \t \t and " + r_str )
elif lvalue . register == " X " :
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t and " + r_str )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t and " + r_str )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported register for aug assign " , str ( lvalue ) ) # @todo &=.word
elif operator == " |= " :
if lvalue . register == " A " :
self . p ( " \t \t ora " + r_str )
elif lvalue . register == " X " :
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t ora " + r_str )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t ora " + r_str )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported register for aug assign " , str ( lvalue ) ) # @todo |=.word
elif operator == " ^= " :
if lvalue . register == " A " :
self . p ( " \t \t eor " + r_str )
elif lvalue . register == " X " :
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t eor " + r_str )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t eor " + r_str )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported register for aug assign " , str ( lvalue ) ) # @todo ^=.word
elif operator == " >>= " :
raise CodeError ( " can not yet shift a variable amount " ) # XXX
elif operator == " <<= " :
raise CodeError ( " can not yet shift a variable amount " ) # XXX
2017-12-28 03:20:59 +00:00
def _generate_aug_reg_int ( self , lvalue : ParseResult . RegisterValue , operator : str , rvalue : ParseResult . IntegerValue ) - > None :
r_str = rvalue . name or Parser . to_hex ( rvalue . value )
if operator == " += " :
self . p ( " \t \t clc " )
if lvalue . register == " A " :
self . p ( " \t \t adc # " + r_str )
elif lvalue . register == " X " :
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t adc # " + r_str )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t adc # " + r_str )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported register for aug assign " , str ( lvalue ) ) # @todo +=.word
elif operator == " -= " :
if lvalue . register == " A " :
self . p ( " \t \t sbc # " + r_str )
elif lvalue . register == " X " :
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t sbc # " + r_str )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t sbc # " + r_str )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported register for aug assign " , str ( lvalue ) ) # @todo -=.word
elif operator == " &= " :
if lvalue . register == " A " :
self . p ( " \t \t and # " + r_str )
elif lvalue . register == " X " :
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t and # " + r_str )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t and # " + r_str )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported register for aug assign " , str ( lvalue ) ) # @todo &=.word
elif operator == " |= " :
if lvalue . register == " A " :
self . p ( " \t \t ora # " + r_str )
elif lvalue . register == " X " :
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t ora # " + r_str )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t ora # " + r_str )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported register for aug assign " , str ( lvalue ) ) # @todo |=.word
elif operator == " ^= " :
if lvalue . register == " A " :
self . p ( " \t \t eor # " + r_str )
elif lvalue . register == " X " :
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t eor # " + r_str )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t eor # " + r_str )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported register for aug assign " , str ( lvalue ) ) # @todo ^=.word
elif operator == " >>= " :
if rvalue . value != 1 :
raise CodeError ( " can only shift 1 bit for now " ) # XXX
if lvalue . register == " A " :
self . p ( " \t \t lsr a " )
elif lvalue . register == " X " :
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t lsr a " )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t lsr a " )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported register for aug assign " , str ( lvalue ) ) # @todo >>=.word
elif operator == " <<= " :
if rvalue . value != 1 :
raise CodeError ( " can only shift 1 bit for now " ) # XXX
if lvalue . register == " A " :
self . p ( " \t \t asl a " )
elif lvalue . register == " X " :
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t asl a " )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t asl a " )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported register for aug assign " , str ( lvalue ) ) # @todo <<=.word
def _generate_aug_reg_reg ( self , lvalue : ParseResult . RegisterValue , operator : str , rvalue : ParseResult . RegisterValue ) - > None :
if operator == " += " :
self . p ( " \t \t clc " )
if rvalue . register not in REGISTER_BYTES :
raise CodeError ( " unsupported rvalue register for aug assign " , str ( rvalue ) ) # @todo +=.word
if lvalue . register == " A " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t adc " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
elif lvalue . register == " X " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t adc " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t adc " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported lvalue register for aug assign " , str ( lvalue ) ) # @todo +=.word
elif operator == " -= " :
if rvalue . register not in REGISTER_BYTES :
raise CodeError ( " unsupported rvalue register for aug assign " , str ( rvalue ) ) # @todo -=.word
if lvalue . register == " A " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t sbc " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
elif lvalue . register == " X " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t sbc " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t sbc " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported lvalue register for aug assign " , str ( lvalue ) ) # @todo -=.word
elif operator == " &= " :
if rvalue . register not in REGISTER_BYTES :
raise CodeError ( " unsupported rvalue register for aug assign " , str ( rvalue ) ) # @todo &=.word
if lvalue . register == " A " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t and " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
elif lvalue . register == " X " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t and " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t and " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported lvalue register for aug assign " , str ( lvalue ) ) # @todo &=.word
elif operator == " |= " :
if rvalue . register not in REGISTER_BYTES :
raise CodeError ( " unsupported rvalue register for aug assign " , str ( rvalue ) ) # @todo |=.word
if lvalue . register == " A " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t ora " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
elif lvalue . register == " X " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t ora " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t ora " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported lvalue register for aug assign " , str ( lvalue ) ) # @todo |=.word
elif operator == " ^= " :
if rvalue . register not in REGISTER_BYTES :
raise CodeError ( " unsupported rvalue register for aug assign " , str ( rvalue ) ) # @todo ^=.word
if lvalue . register == " A " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t eor " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
elif lvalue . register == " X " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t eor " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t eor " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported lvalue register for aug assign " , str ( lvalue ) ) # @todo ^=.word
elif operator == " >>= " :
if rvalue . register not in REGISTER_BYTES :
raise CodeError ( " unsupported rvalue register for aug assign " , str ( rvalue ) ) # @todo >>=.word
if lvalue . register == " A " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t lsr " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
elif lvalue . register == " X " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t lsr " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t lsr " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported lvalue register for aug assign " , str ( lvalue ) ) # @todo >>=.word
elif operator == " <<= " :
if rvalue . register not in REGISTER_BYTES :
raise CodeError ( " unsupported rvalue register for aug assign " , str ( rvalue ) ) # @todo <<=.word
if lvalue . register == " A " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t asl " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
elif lvalue . register == " X " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t pha " )
self . p ( " \t \t txa " )
self . p ( " \t \t asl " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
self . p ( " \t \t tax " )
self . p ( " \t \t pla " )
elif lvalue . register == " Y " :
self . p ( " \t \t st {:s} {:s} " . format ( rvalue . register . lower ( ) , Parser . to_hex ( Zeropage . SCRATCH_B1 ) ) )
self . p ( " \t \t pha " )
self . p ( " \t \t tya " )
self . p ( " \t \t asl " + Parser . to_hex ( Zeropage . SCRATCH_B1 ) )
self . p ( " \t \t tay " )
self . p ( " \t \t pla " )
else :
raise CodeError ( " unsupported lvalue register for aug assign " , str ( lvalue ) ) # @todo <<=.word
2017-12-21 13:52:30 +00:00
def generate_assignment ( self , stmt : ParseResult . AssignmentStmt ) - > None :
2017-12-23 00:53:48 +00:00
def unwrap_indirect ( iv : ParseResult . IndirectValue ) - > ParseResult . MemMappedValue :
if isinstance ( iv . value , ParseResult . MemMappedValue ) :
return iv . value
elif iv . value . constant and isinstance ( iv . value , ParseResult . IntegerValue ) :
return ParseResult . MemMappedValue ( iv . value . value , iv . datatype , 1 , iv . name )
else :
raise CodeError ( " cannot yet generate code for assignment: non-constant and non-memmapped indirect " ) # XXX
2017-12-25 12:20:23 +00:00
rvalue = stmt . right
2017-12-23 00:53:48 +00:00
if isinstance ( rvalue , ParseResult . IndirectValue ) :
rvalue = unwrap_indirect ( rvalue )
2017-12-21 13:52:30 +00:00
self . p ( " \t \t \t \t \t ; src l. {:d} " . format ( stmt . lineno ) )
2017-12-23 00:53:48 +00:00
if isinstance ( rvalue , ParseResult . IntegerValue ) :
2017-12-21 13:52:30 +00:00
for lv in stmt . leftvalues :
if isinstance ( lv , ParseResult . RegisterValue ) :
2017-12-23 00:53:48 +00:00
self . generate_assign_integer_to_reg ( lv . register , rvalue )
2017-12-21 13:52:30 +00:00
elif isinstance ( lv , ParseResult . MemMappedValue ) :
2017-12-23 00:53:48 +00:00
self . generate_assign_integer_to_mem ( lv , rvalue )
elif isinstance ( lv , ParseResult . IndirectValue ) :
lv = unwrap_indirect ( lv )
self . generate_assign_integer_to_mem ( lv , rvalue )
2017-12-21 13:52:30 +00:00
else :
raise CodeError ( " invalid assignment target (1) " , str ( stmt ) )
2017-12-23 00:53:48 +00:00
elif isinstance ( rvalue , ParseResult . RegisterValue ) :
2017-12-21 13:52:30 +00:00
for lv in stmt . leftvalues :
if isinstance ( lv , ParseResult . RegisterValue ) :
2017-12-23 00:53:48 +00:00
self . generate_assign_reg_to_reg ( lv , rvalue . register )
2017-12-21 13:52:30 +00:00
elif isinstance ( lv , ParseResult . MemMappedValue ) :
2017-12-23 00:53:48 +00:00
self . generate_assign_reg_to_memory ( lv , rvalue . register )
elif isinstance ( lv , ParseResult . IndirectValue ) :
lv = unwrap_indirect ( lv )
self . generate_assign_reg_to_memory ( lv , rvalue . register )
2017-12-21 13:52:30 +00:00
else :
raise CodeError ( " invalid assignment target (2) " , str ( stmt ) )
2017-12-23 00:53:48 +00:00
elif isinstance ( rvalue , ParseResult . StringValue ) :
r_str = self . output_string ( rvalue . value , True )
2017-12-21 13:52:30 +00:00
for lv in stmt . leftvalues :
if isinstance ( lv , ParseResult . RegisterValue ) :
2017-12-23 00:53:48 +00:00
if len ( rvalue . value ) == 1 :
2017-12-21 13:52:30 +00:00
self . generate_assign_char_to_reg ( lv , r_str )
else :
2017-12-23 00:53:48 +00:00
self . generate_assign_string_to_reg ( lv , rvalue )
2017-12-21 13:52:30 +00:00
elif isinstance ( lv , ParseResult . MemMappedValue ) :
2017-12-23 00:53:48 +00:00
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 , ParseResult . IndirectValue ) :
lv = unwrap_indirect ( lv )
if len ( rvalue . value ) == 1 :
2017-12-21 13:52:30 +00:00
self . generate_assign_char_to_memory ( lv , r_str )
else :
2017-12-23 00:53:48 +00:00
self . generate_assign_string_to_memory ( lv , rvalue )
2017-12-21 13:52:30 +00:00
else :
raise CodeError ( " invalid assignment target (2) " , str ( stmt ) )
2017-12-23 00:53:48 +00:00
elif isinstance ( rvalue , ParseResult . MemMappedValue ) :
2017-12-21 13:52:30 +00:00
for lv in stmt . leftvalues :
if isinstance ( lv , ParseResult . RegisterValue ) :
2017-12-23 00:53:48 +00:00
self . generate_assign_mem_to_reg ( lv . register , rvalue )
2017-12-21 13:52:30 +00:00
elif isinstance ( lv , ParseResult . MemMappedValue ) :
2017-12-23 00:53:48 +00:00
self . generate_assign_mem_to_mem ( lv , rvalue )
elif isinstance ( lv , ParseResult . IndirectValue ) :
lv = unwrap_indirect ( lv )
self . generate_assign_mem_to_mem ( lv , rvalue )
2017-12-21 13:52:30 +00:00
else :
raise CodeError ( " invalid assignment target (4) " , str ( stmt ) )
2017-12-23 00:53:48 +00:00
elif isinstance ( rvalue , ParseResult . FloatValue ) :
2017-12-21 13:52:30 +00:00
for lv in stmt . leftvalues :
if isinstance ( lv , ParseResult . MemMappedValue ) and lv . datatype == DataType . FLOAT :
2017-12-23 13:36:23 +00:00
self . generate_assign_float_to_mem ( lv , rvalue )
2017-12-23 00:53:48 +00:00
elif isinstance ( lv , ParseResult . IndirectValue ) :
lv = unwrap_indirect ( lv )
assert lv . datatype == DataType . FLOAT
2017-12-23 13:36:23 +00:00
self . generate_assign_float_to_mem ( lv , rvalue )
2017-12-21 13:52:30 +00:00
else :
raise CodeError ( " cannot assign float to " , str ( lv ) )
else :
raise CodeError ( " invalid assignment value type " , str ( stmt ) )
2017-12-23 13:36:23 +00:00
def generate_assign_float_to_mem ( self , mmv : ParseResult . MemMappedValue ,
rvalue : Union [ ParseResult . FloatValue , ParseResult . IntegerValue ] , save_reg : bool = True ) - > None :
floatvalue = float ( rvalue . value )
mflpt = self . to_mflpt5 ( floatvalue )
2017-12-21 21:16:46 +00:00
target = mmv . name or Parser . to_hex ( mmv . address )
2017-12-23 13:36:23 +00:00
if save_reg :
self . p ( " \t \t pha \t \t \t ; {:s} = {} " . format ( target , rvalue . name or floatvalue ) )
2017-12-21 13:52:30 +00:00
else :
2017-12-23 13:36:23 +00:00
self . p ( " \t \t \t \t \t ; {:s} = {} " . format ( target , rvalue . name or floatvalue ) )
2017-12-21 13:52:30 +00:00
for num in range ( 5 ) :
self . p ( " \t \t lda #$ {:02x} " . format ( mflpt [ num ] ) )
self . p ( " \t \t sta {:s} + {:d} " . format ( target , num ) )
2017-12-23 13:36:23 +00:00
if save_reg :
2017-12-21 13:52:30 +00:00
self . p ( " \t \t pla " )
def generate_assign_reg_to_memory ( self , lv : ParseResult . MemMappedValue , r_register : str ) - > None :
# Memory = Register
2017-12-21 21:16:46 +00:00
lv_string = lv . name or Parser . to_hex ( lv . address )
2017-12-21 13:52:30 +00:00
if lv . datatype == DataType . BYTE :
if len ( r_register ) > 1 :
raise CodeError ( " cannot assign register pair to single byte memory " )
self . p ( " \t \t st {:s} {} " . format ( r_register . lower ( ) , lv_string ) )
elif lv . datatype == DataType . WORD :
if len ( r_register ) == 1 :
self . p ( " \t \t st {:s} {} " . format ( r_register . lower ( ) , lv_string ) ) # lsb
2017-12-25 18:22:22 +00:00
with self . preserving_registers ( { ' A ' } , loads_a_within = True ) :
2017-12-21 13:52:30 +00:00
self . p ( " \t \t lda #0 " )
self . p ( " \t \t sta {:s} +1 " . format ( lv_string ) ) # msb
else :
self . p ( " \t \t st {:s} {} " . format ( r_register [ 0 ] . lower ( ) , lv_string ) )
self . p ( " \t \t st {:s} {} +1 " . format ( r_register [ 1 ] . lower ( ) , lv_string ) )
elif lv . datatype == DataType . FLOAT :
2017-12-25 01:29:14 +00:00
# assigning a register to a float requires c64 ROM routines
2017-12-24 23:15:04 +00:00
if r_register in REGISTER_WORDS :
2017-12-25 01:29:14 +00:00
def do_rom_calls ( ) :
self . p ( " \t \t jsr c64util.GIVUAYF " ) # uword AY -> fac1
self . p ( " \t \t ldx #< " + lv_string )
self . p ( " \t \t ldy #> " + lv_string )
self . p ( " \t \t jsr 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 \t pha \n \t \t txa \n \t \t tay \n \t \t pla " ) # X->Y (so we have AY now)
do_rom_calls ( )
else : # XY
2017-12-25 18:22:22 +00:00
with self . preserving_registers ( { ' A ' , ' X ' , ' Y ' } , loads_a_within = True ) :
2017-12-25 01:29:14 +00:00
self . p ( " \t \t txa " ) # X->A (so we have AY now)
do_rom_calls ( )
2017-12-24 23:15:04 +00:00
elif r_register in " AXY " :
def do_rom_calls ( ) :
self . p ( " \t \t jsr c64.FREADUY " ) # ubyte Y -> fac1
self . p ( " \t \t ldx #< " + lv_string )
self . p ( " \t \t ldy #> " + lv_string )
self . p ( " \t \t jsr c64.FTOMEMXY " ) # fac1 -> memory XY
if r_register == " A " :
with self . preserving_registers ( { ' A ' , ' X ' , ' Y ' } ) :
self . p ( " \t \t tay " )
do_rom_calls ( )
elif r_register == " X " :
2017-12-25 18:22:22 +00:00
with self . preserving_registers ( { ' A ' , ' X ' , ' Y ' } , loads_a_within = True ) :
2017-12-24 23:15:04 +00:00
self . p ( " \t \t txa " )
self . p ( " \t \t tay " )
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 )
2017-12-21 13:52:30 +00:00
else :
raise CodeError ( " invalid lvalue type " , lv . datatype )
def generate_assign_reg_to_reg ( self , lv : ParseResult . RegisterValue , r_register : str ) - > None :
if lv . register != r_register :
if lv . register == ' A ' : # x/y -> a
self . p ( " \t \t t {:s} a " . format ( r_register . lower ( ) ) )
elif lv . register == ' Y ' :
if r_register == ' A ' :
# a -> y
self . p ( " \t \t tay " )
else :
# x -> y, 6502 doesn't have txy
self . p ( " \t \t stx $ {0:02x} \n \t \t ldy $ {0:02x} " . format ( Zeropage . SCRATCH_B1 ) )
elif lv . register == ' X ' :
if r_register == ' A ' :
# a -> x
self . p ( " \t \t tax " )
else :
# y -> x, 6502 doesn't have tyx
self . p ( " \t \t sty $ {0:02x} \n \t \t ldx $ {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 \t ldx #0 " )
elif lv . register == " AX " and r_register == " X " :
self . p ( " \t \t txa \n \t \t ldx #0 " )
elif lv . register == " AX " and r_register == " Y " :
self . p ( " \t \t tya \n \t \t ldx #0 " )
elif lv . register == " AY " and r_register == " A " :
self . p ( " \t \t ldy #0 " )
elif lv . register == " AY " and r_register == " X " :
self . p ( " \t \t txa \n \t \t ldy #0 " )
elif lv . register == " AY " and r_register == " Y " :
self . p ( " \t \t tya \n \t \t ldy #0 " )
elif lv . register == " XY " and r_register == " A " :
self . p ( " \t \t tax \n \t \t ldy #0 " )
elif lv . register == " XY " and r_register == " X " :
self . p ( " \t \t ldy #0 " )
elif lv . register == " XY " and r_register == " Y " :
self . p ( " \t \t tyx \n \t \t ldy #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 \t sty $ {0:02x} \n \t \t ldx $ {0:02x} " . format ( Zeropage . SCRATCH_B1 ) )
elif lv . register == " AX " and r_register == " XY " :
self . p ( " \t \t txa " )
# y -> x, 6502 doesn't have tyx
self . p ( " \t \t sty $ {0:02x} \n \t \t ldx $ {0:02x} " . format ( Zeropage . SCRATCH_B1 ) )
elif lv . register == " AY " and r_register == " AX " :
# x -> y, 6502 doesn't have txy
self . p ( " \t \t stx $ {0:02x} \n \t \t ldy $ {0:02x} " . format ( Zeropage . SCRATCH_B1 ) )
elif lv . register == " AY " and r_register == " XY " :
self . p ( " \t \t txa " )
elif lv . register == " XY " and r_register == " AX " :
self . p ( " \t \t tax " )
# x -> y, 6502 doesn't have txy
self . p ( " \t \t stx $ {0:02x} \n \t \t ldy $ {0:02x} " . format ( Zeropage . SCRATCH_B1 ) )
elif lv . register == " XY " and r_register == " AY " :
self . p ( " \t \t tax " )
else :
raise CodeError ( " invalid register combination " , lv . register , r_register )
else :
raise CodeError ( " invalid register " + lv . register )
@contextlib.contextmanager
2017-12-25 18:22:22 +00:00
def preserving_registers ( self , registers : Set [ str ] , loads_a_within : bool = False ) :
# this clobbers a ZP scratch register and is therefore NOT safe to use in interrupts
2017-12-21 13:52:30 +00:00
# see http://6502.org/tutorials/register_preservation.html
if registers == { ' A ' } :
self . p ( " \t \t pha " )
yield
self . p ( " \t \t pla " )
elif registers :
2017-12-25 18:22:22 +00:00
if not loads_a_within :
self . p ( " \t \t sta $ {:02x} " . format ( Zeropage . SCRATCH_B2 ) )
2017-12-21 13:52:30 +00:00
if ' A ' in registers :
self . p ( " \t \t pha " )
if ' X ' in registers :
self . p ( " \t \t txa \n \t \t pha " )
if ' Y ' in registers :
self . p ( " \t \t tya \n \t \t pha " )
2017-12-25 18:22:22 +00:00
if not loads_a_within :
self . p ( " \t \t lda $ {:02x} " . format ( Zeropage . SCRATCH_B2 ) )
2017-12-21 13:52:30 +00:00
yield
2017-12-28 18:08:33 +00:00
if ' X ' in registers and ' Y ' in registers :
if ' A ' not in registers :
self . p ( " \t \t sta $ {:02x} " . format ( Zeropage . SCRATCH_B2 ) )
self . p ( " \t \t pla \n \t \t tay " )
self . p ( " \t \t pla \n \t \t tax " )
self . p ( " \t \t lda $ {:02x} " . format ( Zeropage . SCRATCH_B2 ) )
else :
self . p ( " \t \t pla \n \t \t tay " )
self . p ( " \t \t pla \n \t \t tax " )
else :
if ' Y ' in registers :
if ' A ' not in registers :
self . p ( " \t \t sta $ {:02x} " . format ( Zeropage . SCRATCH_B2 ) )
self . p ( " \t \t pla \n \t \t tay " )
self . p ( " \t \t lda $ {:02x} " . format ( Zeropage . SCRATCH_B2 ) )
else :
self . p ( " \t \t pla \n \t \t tay " )
if ' X ' in registers :
if ' A ' not in registers :
self . p ( " \t \t sta $ {:02x} " . format ( Zeropage . SCRATCH_B2 ) )
self . p ( " \t \t pla \n \t \t tax " )
self . p ( " \t \t lda $ {:02x} " . format ( Zeropage . SCRATCH_B2 ) )
else :
self . p ( " \t \t pla \n \t \t tax " )
2017-12-21 13:52:30 +00:00
if ' A ' in registers :
self . p ( " \t \t pla " )
else :
yield
def generate_assign_integer_to_mem ( self , lv : ParseResult . MemMappedValue , rvalue : ParseResult . IntegerValue ) - > None :
if lv . name :
symblock , sym = self . cur_block . lookup ( lv . name )
if not isinstance ( sym , VariableDef ) :
2017-12-27 18:01:14 +00:00
raise CodeError ( " invalid lvalue type " + str ( sym ) )
2017-12-23 00:53:48 +00:00
assign_target = symblock . label + ' . ' + sym . name if symblock is not self . cur_block else lv . name
2017-12-21 13:52:30 +00:00
lvdatatype = sym . type
else :
2017-12-21 21:16:46 +00:00
assign_target = Parser . to_hex ( lv . address )
2017-12-21 13:52:30 +00:00
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 " )
2017-12-25 18:22:22 +00:00
with self . preserving_registers ( { ' A ' } , loads_a_within = True ) :
2017-12-21 13:52:30 +00:00
self . p ( " \t \t lda # " + r_str )
self . p ( " \t \t sta " + 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 " )
2017-12-25 18:22:22 +00:00
with self . preserving_registers ( { ' A ' } , loads_a_within = True ) :
2017-12-21 13:52:30 +00:00
self . p ( " \t \t lda #< " + r_str )
self . p ( " \t \t sta " + assign_target )
self . p ( " \t \t lda #> " + r_str )
self . p ( " \t \t sta {} +1 " . format ( assign_target ) )
elif lvdatatype == DataType . FLOAT :
if rvalue . value is not None and not DataType . FLOAT . assignable_from_value ( rvalue . value ) :
2017-12-27 18:01:14 +00:00
raise CodeError ( " value cannot be assigned to a float " )
2017-12-23 13:36:23 +00:00
self . generate_assign_float_to_mem ( lv , rvalue , False )
2017-12-21 13:52:30 +00:00
else :
2017-12-27 18:01:14 +00:00
raise CodeError ( " invalid lvalue type " + str ( lvdatatype ) )
2017-12-21 13:52:30 +00:00
def generate_assign_mem_to_reg ( self , l_register : str , rvalue : ParseResult . 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 \t ld {:s} {:s} " . format ( l_register . lower ( ) , r_str ) )
else :
2017-12-23 13:36:23 +00:00
if rvalue . datatype == DataType . BYTE :
self . p ( " \t \t ld {:s} {:s} " . format ( l_register [ 0 ] . lower ( ) , r_str ) )
self . p ( " \t \t ld {:s} #0 " . format ( l_register [ 1 ] . lower ( ) ) )
elif rvalue . datatype == DataType . WORD :
self . p ( " \t \t ld {:s} {:s} " . format ( l_register [ 0 ] . lower ( ) , r_str ) )
self . p ( " \t \t ld {:s} {:s} +1 " . format ( l_register [ 1 ] . lower ( ) , r_str ) )
else :
raise CodeError ( " can only assign a byte or word to a register pair " )
2017-12-21 13:52:30 +00:00
def generate_assign_mem_to_mem ( self , lv : ParseResult . MemMappedValue , rvalue : ParseResult . MemMappedValue ) - > None :
2017-12-25 11:58:52 +00:00
r_str = rvalue . name or Parser . to_hex ( rvalue . address )
l_str = lv . name or Parser . to_hex ( lv . address )
2017-12-21 13:52:30 +00:00
if lv . datatype == DataType . BYTE :
if rvalue . datatype != DataType . BYTE :
2017-12-23 13:36:23 +00:00
raise CodeError ( " can only assign a byte to a byte " , str ( rvalue ) )
2017-12-25 18:22:22 +00:00
with self . preserving_registers ( { ' A ' } , loads_a_within = True ) :
2017-12-21 13:52:30 +00:00
self . p ( " \t \t lda " + r_str )
2017-12-25 11:58:52 +00:00
self . p ( " \t \t sta " + l_str )
2017-12-21 13:52:30 +00:00
elif lv . datatype == DataType . WORD :
if rvalue . datatype == DataType . BYTE :
2017-12-25 18:22:22 +00:00
with self . preserving_registers ( { ' A ' } , loads_a_within = True ) :
2017-12-21 13:52:30 +00:00
self . p ( " \t \t lda " + r_str )
2017-12-23 13:36:23 +00:00
self . p ( " \t \t sta " + l_str )
self . p ( " \t \t lda #0 " )
2017-12-21 13:52:30 +00:00
self . p ( " \t \t sta {:s} +1 " . format ( l_str ) )
elif rvalue . datatype == DataType . WORD :
2017-12-25 18:22:22 +00:00
with self . preserving_registers ( { ' A ' } , loads_a_within = True ) :
2017-12-21 13:52:30 +00:00
self . p ( " \t \t lda {:s} " . format ( r_str ) )
self . p ( " \t \t sta {:s} " . format ( l_str ) )
self . p ( " \t \t lda {:s} +1 " . format ( r_str ) )
self . p ( " \t \t sta {:s} +1 " . format ( l_str ) )
else :
2017-12-23 13:36:23 +00:00
raise CodeError ( " can only assign a byte or word to a word " , str ( rvalue ) )
2017-12-25 11:58:52 +00:00
elif lv . datatype == DataType . FLOAT :
if rvalue . datatype == DataType . FLOAT :
2017-12-25 18:22:22 +00:00
with self . preserving_registers ( { ' A ' } , loads_a_within = True ) :
2017-12-25 11:58:52 +00:00
self . p ( " \t \t lda " + r_str )
self . p ( " \t \t sta " + l_str )
self . p ( " \t \t lda {:s} +1 " . format ( r_str ) )
self . p ( " \t \t sta {:s} +1 " . format ( l_str ) )
self . p ( " \t \t lda {:s} +2 " . format ( r_str ) )
self . p ( " \t \t sta {:s} +2 " . format ( l_str ) )
self . p ( " \t \t lda {:s} +3 " . format ( r_str ) )
self . p ( " \t \t sta {:s} +3 " . format ( l_str ) )
self . p ( " \t \t lda {:s} +4 " . format ( r_str ) )
self . p ( " \t \t sta {:s} +4 " . format ( l_str ) )
elif rvalue . datatype == DataType . BYTE :
with self . preserving_registers ( { ' A ' , ' X ' , ' Y ' } ) :
self . p ( " \t \t ldy " + r_str )
self . p ( " \t \t jsr c64.FREADUY " ) # ubyte Y -> fac1
self . p ( " \t \t ldx #< " + l_str )
self . p ( " \t \t ldy #> " + l_str )
self . p ( " \t \t jsr c64.FTOMEMXY " ) # fac1 -> memory XY
elif rvalue . datatype == DataType . WORD :
2017-12-25 18:22:22 +00:00
with self . preserving_registers ( { ' A ' , ' X ' , ' Y ' } , loads_a_within = True ) :
2017-12-25 11:58:52 +00:00
self . p ( " \t \t lda " + r_str )
self . p ( " \t \t ldy {:s} +1 " . format ( r_str ) )
self . p ( " \t \t jsr c64util.GIVUAYF " ) # uword AY -> fac1
self . p ( " \t \t ldx #< " + l_str )
self . p ( " \t \t ldy #> " + l_str )
self . p ( " \t \t jsr c64.FTOMEMXY " ) # fac1 -> memory XY
else :
raise CodeError ( " unsupported rvalue to memfloat " , str ( rvalue ) )
2017-12-21 13:52:30 +00:00
else :
2017-12-25 11:58:52 +00:00
raise CodeError ( " invalid lvalue memmapped datatype " , str ( lv ) )
2017-12-21 13:52:30 +00:00
def generate_assign_char_to_memory ( self , lv : ParseResult . MemMappedValue , char_str : str ) - > None :
# Memory = Character
2017-12-25 18:22:22 +00:00
with self . preserving_registers ( { ' A ' } , loads_a_within = True ) :
2017-12-21 13:52:30 +00:00
self . p ( " \t \t lda # " + char_str )
if not lv . name :
2017-12-21 21:16:46 +00:00
self . p ( " \t \t sta " + Parser . to_hex ( lv . address ) )
2017-12-21 13:52:30 +00:00
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 :
2017-12-23 00:53:48 +00:00
assign_target = symblock . label + ' . ' + sym . name
2017-12-21 13:52:30 +00:00
if sym . type == DataType . BYTE :
self . p ( " \t \t sta " + assign_target )
elif sym . type == DataType . WORD :
self . p ( " \t \t sta " + assign_target )
self . p ( " \t \t lda #0 " )
self . p ( " \t \t sta {} +1 " . format ( assign_target ) )
else :
2017-12-27 18:01:14 +00:00
raise CodeError ( " invalid lvalue type " + str ( sym ) )
2017-12-21 13:52:30 +00:00
else :
2017-12-27 18:01:14 +00:00
raise CodeError ( " invalid lvalue type " + str ( sym ) )
2017-12-21 13:52:30 +00:00
def generate_assign_integer_to_reg ( self , l_register : str , rvalue : ParseResult . IntegerValue ) - > None :
r_str = rvalue . name if rvalue . name else " $ {:x} " . format ( rvalue . value )
if l_register in ( ' A ' , ' X ' , ' Y ' ) :
self . p ( " \t \t ld {:s} # {:s} " . format ( l_register . lower ( ) , r_str ) )
elif l_register in REGISTER_WORDS :
self . p ( " \t \t ld {:s} #< {:s} " . format ( l_register [ 0 ] . lower ( ) , r_str ) )
self . p ( " \t \t ld {:s} #> {:s} " . format ( l_register [ 1 ] . lower ( ) , r_str ) )
elif l_register == " SC " :
# set/clear S carry bit
if rvalue . value :
self . p ( " \t \t sec " )
else :
self . p ( " \t \t clc " )
2017-12-25 20:43:06 +00:00
elif l_register == " SI " :
# interrupt disable bit
if rvalue . value :
self . p ( " \t \t sei " )
else :
self . p ( " \t \t cli " )
2017-12-21 13:52:30 +00:00
else :
raise CodeError ( " invalid register in immediate integer assignment " , l_register , rvalue . value )
def generate_assign_char_to_reg ( self , lv : ParseResult . 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 \t ld {:s} # {:s} " . format ( lv . register . lower ( ) , char_str ) )
def generate_assign_string_to_reg ( self , lv : ParseResult . RegisterValue , rvalue : ParseResult . 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 \t ld {:s} #< {:s} " . format ( lv . register [ 0 ] . lower ( ) , rvalue . name ) )
self . p ( " \t \t ld {:s} #> {:s} " . format ( lv . register [ 1 ] . lower ( ) , rvalue . name ) )
else :
raise CodeError ( " cannot assign immediate string, it should be a string variable " )
def generate_assign_string_to_memory ( self , lv : ParseResult . MemMappedValue , rvalue : ParseResult . StringValue ) - > None :
if lv . datatype != DataType . WORD :
raise CodeError ( " need word memory type for string address assignment " )
if rvalue . name :
2017-12-21 21:16:46 +00:00
assign_target = lv . name if lv . name else Parser . to_hex ( lv . address )
2017-12-21 13:52:30 +00:00
self . p ( " \t \t lda #< {:s} " . format ( rvalue . name ) )
self . p ( " \t \t sta " + assign_target )
self . p ( " \t \t lda #> {:s} " . format ( rvalue . name ) )
self . p ( " \t \t sta {} +1 " . format ( assign_target ) )
else :
raise CodeError ( " cannot assign immediate string, it should be a string variable " )
def footer ( self ) - > None :
self . p ( " \n \n .end " )
def output_string ( self , 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 :
2017-12-28 18:08:33 +00:00
if char == ' \f ' :
2017-12-21 13:52:30 +00:00
result + = " {clear} "
2017-12-28 18:08:33 +00:00
elif char == ' \b ' :
2017-12-21 13:52:30 +00:00
result + = " {delete} "
2017-12-28 18:08:33 +00:00
elif char == ' \n ' :
2017-12-21 13:52:30 +00:00
result + = " {cr} "
2017-12-28 18:08:33 +00:00
elif char == ' \r ' :
result + = " {down} "
elif char == ' \t ' :
2017-12-21 13:52:30 +00:00
result + = " {tab} "
else :
result + = ' " , {:d} , " ' . format ( ord ( char ) )
return result + ' " '
class Assembler64Tass :
def __init__ ( self , format : ProgramFormat ) - > None :
self . format = format
def assemble ( self , inputfilename : str , outputfilename : str ) - > None :
2017-12-27 22:45:22 +00:00
args = [ " 64tass " , " --ascii " , " --case-sensitive " , " -Wall " , " -Wno-strict-bool " ,
" --dump-labels " , " --vice-labels " , " -l " , outputfilename + " .vice-mon-list " ,
" -L " , outputfilename + " .final-asm " , " --no-monitor " , " --output " , outputfilename , inputfilename ]
2017-12-21 13:52:30 +00:00
if self . format == ProgramFormat . PRG :
args . append ( " --cbm-prg " )
elif self . format == ProgramFormat . RAW :
args . append ( " --nostart " )
else :
raise ValueError ( " don ' t know how to create format " + str ( self . format ) )
try :
if self . format == ProgramFormat . PRG :
2017-12-28 18:08:33 +00:00
print ( " \n creating C-64 prg " )
2017-12-21 13:52:30 +00:00
elif self . format == ProgramFormat . RAW :
print ( " \n creating raw binary " )
2017-12-25 01:07:17 +00:00
try :
subprocess . check_call ( args )
except FileNotFoundError as x :
raise SystemExit ( " ERROR: cannot run assembler program: " + str ( x ) )
2017-12-21 13:52:30 +00:00
except subprocess . CalledProcessError as x :
2017-12-28 18:08:33 +00:00
raise SystemExit ( " assembler failed with returncode " + str ( x . returncode ) )
2017-12-27 22:45:22 +00:00
def generate_breakpoint_list ( self , program_filename : str ) - > str :
breakpoints = [ ]
with open ( program_filename + " .final-asm " , " rU " ) as f :
for line in f :
match = re . fullmatch ( CodeGenerator . BREAKPOINT_COMMENT_DETECTOR , line , re . DOTALL )
if match :
breakpoints . append ( " $ " + match . group ( " address " ) )
cmdfile = program_filename + " .vice-mon-list "
with open ( cmdfile , " 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 cmdfile