vars init

This commit is contained in:
Irmen de Jong 2018-01-13 23:49:57 +01:00
parent faa08133a8
commit ee9a5716b0
11 changed files with 152 additions and 128 deletions

View File

@ -16,7 +16,7 @@ from .plyparse import parse_file, ParseError, Module, Directive, Block, Subrouti
SymbolName, Dereference, AddressOf
from .plylex import SourceRef, print_bold
from .optimize import optimize
from .datatypes import DataType, VarType, STRING_DATATYPES
from .datatypes import DataType, VarType
class CompileError(Exception):
@ -42,7 +42,6 @@ class PlyParser:
# these shall only be done on the main module after all imports have been done:
self.apply_directive_options(module)
self.determine_subroutine_usage(module)
# XXX merge zero page from imported modules??? do we still have to do that?
self.allocate_zeropage_vars(module)
except ParseError as x:
self.handle_parse_error(x)
@ -85,6 +84,8 @@ class PlyParser:
return
zeropage = Zeropage(module.zp_options)
for vardef in zpnode.scope.filter_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)
@ -293,7 +294,7 @@ class PlyParser:
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] == '$' or "." not in name:
if name[0] == '$':
continue
try:
symbol = parent_scope[name]
@ -301,8 +302,11 @@ class PlyParser:
pass
else:
if isinstance(symbol, Subroutine):
namespace, name = name.rsplit(".", maxsplit=2)
usages[(namespace, name)].add(str(asmnode.sourceref))
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(self, module: Module) -> None:
for node, parent in module.all_scopes():
@ -460,7 +464,7 @@ class Zeropage:
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 in STRING_DATATYPES:
elif vardef.datatype.isstring():
print_bold("warning: {}: allocating a large datatype in zeropage".format(vardef.sourceref))
size = vardef.size[0]
else:

View File

@ -46,9 +46,19 @@ class DataType(enum.Enum):
return self.value < other.value
return NotImplemented
def isnumeric(self) -> bool:
return self.value in (1, 2, 3)
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"}

View File

@ -223,40 +223,55 @@ class AssemblyGenerator:
# @todo add a block initializer subroutine that can contain custom reset/init code? (static initializer)
def _memset(varname: str, value: int, size: int) -> None:
value = value or 0
self.p("\vlda #<" + varname)
self.p("\vsta il65_lib.SCRATCH_ZPWORD1")
self.p("\vlda #>" + varname)
self.p("\vsta il65_lib.SCRATCH_ZPWORD1+1")
self.p("\vlda #" + to_hex(value))
self.p("\vldx #" + to_hex(size))
self.p("\vjsr il65_lib.memset")
if size > 6:
self.p("\vlda #<" + varname)
self.p("\vsta il65_lib.SCRATCH_ZPWORD1")
self.p("\vlda #>" + varname)
self.p("\vsta il65_lib.SCRATCH_ZPWORD1+1")
self.p("\vlda #" + to_hex(value))
self.p("\vldx #<" + to_hex(size))
self.p("\vldy #>" + to_hex(size))
self.p("\vjsr il65_lib.memset")
else:
self.p("\vlda #" + to_hex(value))
for i in range(size):
self.p("\vsta {:s}+{:d}".format(varname, i))
def _memsetw(varname: str, value: int, size: int) -> None:
value = value or 0
self.p("\vlda #<" + varname)
self.p("\vsta il65_lib.SCRATCH_ZPWORD1")
self.p("\vlda #>" + varname)
self.p("\vsta il65_lib.SCRATCH_ZPWORD1+1")
self.p("\vlda #<" + to_hex(value))
self.p("\vldy #>" + to_hex(value))
self.p("\vldx #" + to_hex(size))
self.p("\vjsr il65_lib.memsetw")
if size > 4:
self.p("\vlda #<" + varname)
self.p("\vsta il65_lib.SCRATCH_ZPWORD1")
self.p("\vlda #>" + varname)
self.p("\vsta il65_lib.SCRATCH_ZPWORD1+1")
self.p("\vlda #<" + to_hex(size))
self.p("\vsta il65_lib.SCRATCH_ZPWORD2")
self.p("\vlda #>" + to_hex(size))
self.p("\vsta il65_lib.SCRATCH_ZPWORD2+1")
self.p("\vlda #<" + to_hex(value))
self.p("\vldx #>" + to_hex(value))
self.p("\vjsr il65_lib.memsetw")
else:
self.p("\vlda #<" + to_hex(value))
self.p("\vldy #>" + to_hex(value))
for i in range(size):
self.p("\vsta {:s}+{:d}".format(varname, i*2))
self.p("\vsty {:s}+{:d}".format(varname, i*2+1))
self.p("_il65_init_block\v; (re)set vars to initial values")
float_inits = {}
string_inits = [] # type: List[VarDef]
prev_value_a, prev_value_x = None, None
vars_by_datatype = defaultdict(list) # type: Dict[DataType, List[VarDef]]
for vardef in block.scope.filter_nodes(VarDef):
if vardef.vartype == VarType.VAR:
vars_by_datatype[vardef.datatype].append(vardef)
for bytevar in sorted(vars_by_datatype[DataType.BYTE], key=lambda vd: vd.value):
assert isinstance(bytevar.value, int)
if bytevar.value != prev_value_a:
self.p("\vlda #${:02x}".format(bytevar.value))
prev_value_a = bytevar.value
self.p("\vsta {:s}".format(bytevar.name))
for wordvar in sorted(vars_by_datatype[DataType.WORD], key=lambda vd: vd.value):
assert isinstance(wordvar.value, int)
v_hi, v_lo = divmod(wordvar.value, 256)
if v_hi != prev_value_a:
self.p("\vlda #${:02x}".format(v_hi))
@ -267,15 +282,18 @@ class AssemblyGenerator:
self.p("\vsta {:s}".format(wordvar.name))
self.p("\vstx {:s}+1".format(wordvar.name))
for floatvar in vars_by_datatype[DataType.FLOAT]:
assert isinstance(floatvar.value, (int, float))
fpbytes = to_mflpt5(floatvar.value) # type: ignore
float_inits[floatvar.name] = (floatvar.name, fpbytes, floatvar.value)
for arrayvar in vars_by_datatype[DataType.BYTEARRAY]:
assert isinstance(arrayvar.value, int)
_memset(arrayvar.name, arrayvar.value, arrayvar.size[0])
for arrayvar in vars_by_datatype[DataType.WORDARRAY]:
assert isinstance(arrayvar.value, int)
_memsetw(arrayvar.name, arrayvar.value, arrayvar.size[0])
for arrayvar in vars_by_datatype[DataType.MATRIX]:
assert isinstance(arrayvar.value, int)
_memset(arrayvar.name, arrayvar.value, arrayvar.size[0] * arrayvar.size[1])
# @todo string datatype inits with 1 memcopy
if float_inits:
self.p("\vldx #4")
self.p("-")
@ -287,11 +305,12 @@ class AssemblyGenerator:
self.p("\vrts\n")
for varname, (vname, fpbytes, fpvalue) in sorted(float_inits.items()):
self.p("_init_float_{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(varname, *fpbytes, fpvalue))
if string_inits:
self.p("_init_strings_start")
for svar in sorted(string_inits, key=lambda v: v.name):
self._generate_string_var(svar, init=True)
self.p("_init_strings_size = * - _init_strings_start")
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.
self._generate_string_var(strvar)
self.p("")
def _numeric_value_str(self, value: Any, as_hex: bool=False) -> str:
@ -320,7 +339,7 @@ class AssemblyGenerator:
self.p("\v{:s} = {}".format(vardef.name, self._numeric_value_str(vardef.value)))
elif vardef.datatype in (DataType.BYTE, DataType.WORD):
self.p("\v{:s} = {:s}".format(vardef.name, self._numeric_value_str(vardef.value, True)))
elif vardef.datatype in STRING_DATATYPES:
elif vardef.datatype.isstring():
# a const string is just a string variable in the generated assembly
self._generate_string_var(vardef)
else:
@ -328,7 +347,7 @@ class AssemblyGenerator:
self.p("; 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)
if vardef.datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
if vardef.datatype.isnumeric():
assert vardef.size == [1]
self.p("\v{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value), vardef.datatype.name.lower()))
elif vardef.datatype == DataType.BYTEARRAY:
@ -353,7 +372,9 @@ class AssemblyGenerator:
# 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 in (DataType.WORDARRAY, DataType.BYTEARRAY, DataType.MATRIX):
if vardef.datatype.isstring():
raise CodeError("cannot put strings in the zeropage", vardef.sourceref)
if vardef.datatype.isarray():
size_str = "size " + str(vardef.size)
else:
size_str = ""
@ -363,7 +384,7 @@ class AssemblyGenerator:
# 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 in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
if vardef.datatype.isnumeric():
assert vardef.size == [1]
if vardef.datatype == DataType.BYTE:
self.p("{:s}\v.byte ?".format(vardef.name))
@ -385,33 +406,29 @@ class AssemblyGenerator:
assert len(vardef.size) == 2
self.p("{: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 in STRING_DATATYPES:
elif vardef.datatype.isstring():
string_vars.append(vardef)
else:
raise CodeError("unknown variable type " + str(vardef.datatype))
if string_vars:
self.p("il65_string_vars_start")
for svar in sorted(string_vars, key=lambda v: v.name): # must be the same order as in the init routine!!!
self.p("{:s}\v.fill {:d}+1\t\t; {}".format(svar.name, len(svar.value), svar.datatype.name.lower()))
# string vars are considered to be a constant, and are not re-initialized.
self.p("")
def _generate_string_var(self, vardef: VarDef, init: bool=False) -> None:
prefix = "_init_str_" if init else ""
def _generate_string_var(self, vardef: VarDef) -> None:
if vardef.datatype == DataType.STRING:
# 0-terminated string
self.p("{:s}{:s}\n\v.null {:s}".format(prefix, vardef.name, self.output_string(str(vardef.value))))
self.p("{:s}\n\v.null {:s}".format(vardef.name, self.output_string(str(vardef.value))))
elif vardef.datatype == DataType.STRING_P:
# pascal string
self.p("{:s}{:s}\n\v.ptext {:s}".format(prefix, vardef.name, self.output_string(str(vardef.value))))
self.p("{:s}\n\v.ptext {:s}".format(vardef.name, self.output_string(str(vardef.value))))
elif vardef.datatype == DataType.STRING_S:
# 0-terminated string in screencode encoding
self.p(".enc 'screen'")
self.p("{:s}{:s}\n\v.null {:s}".format(prefix, vardef.name, self.output_string(str(vardef.value), True)))
self.p("{:s}\n\v.null {:s}".format(vardef.name, self.output_string(str(vardef.value), True)))
self.p(".enc 'none'")
elif vardef.datatype == DataType.STRING_PS:
# 0-terminated pascal string in screencode encoding
self.p(".enc 'screen'")
self.p("{:s}{:s}n\v.ptext {:s}".format(prefix, vardef.name, self.output_string(str(vardef.value), True)))
self.p("{:s}n\v.ptext {:s}".format(vardef.name, self.output_string(str(vardef.value), True)))
self.p(".enc 'none'")
def generate_statement(self, stmt: AstNode) -> None:

View File

@ -1035,7 +1035,7 @@ sub input_chars (buffer: AX) -> (A?, Y) {
;sub memcopy_basic () -> (?) {
; ; ---- copy a memory block by using a BASIC ROM routine @todo fix code
; ; ---- copy a memory block by using a BASIC ROM routine
; ; it calls a function from the basic interpreter, so:
; ; - BASIC ROM must be banked in
; ; - the source block must be readable (so no RAM hidden under BASIC, Kernal, or I/O)
@ -1043,7 +1043,6 @@ sub input_chars (buffer: AX) -> (A?, Y) {
; ; higher addresses are copied first, so:
; ; - moving data to higher addresses works even if areas overlap
; ; - moving data to lower addresses only works if areas do not overlap
; ; @todo fix this
; %asm {
; lda #<src_start
; ldx #>src_start
@ -1061,7 +1060,7 @@ sub input_chars (buffer: AX) -> (A?, Y) {
; }
;}
; macro version of the above memcopy_basic routine: @todo macro support?
; macro version of the above memcopy_basic routine:
; MACRO PARAMS src_start, src_end, target_start
; lda #<src_start
; ldx #>src_start

View File

@ -431,7 +431,7 @@ class VarDef(AstNode):
assert self.size is None
self.size = self.datatype.dimensions or [1]
self.datatype = self.datatype.to_enum()
if self.datatype in {DataType.BYTEARRAY, DataType.WORDARRAY, DataType.MATRIX} and sum(self.size) in (0, 1):
if self.datatype.isarray() and sum(self.size) in (0, 1):
print("warning: {}: array/matrix with size 1, use normal byte/word instead for efficiency".format(self.sourceref))
if self.vartype == VarType.CONST and self.value is None:
raise ParseError("constant value assignment is missing",
@ -439,7 +439,7 @@ class VarDef(AstNode):
# if the value is an expression, mark it as a *constant* expression here
if isinstance(self.value, AstNode):
self.value.processed_expr_must_be_constant = True
elif self.value is None and self.datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
elif self.value is None and self.datatype.isnumeric():
self.value = 0
# if it's a matrix with interleave, it must be memory mapped
if self.datatype == DataType.MATRIX and len(self.size) == 3:
@ -688,9 +688,9 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
else:
raise ExpressionEvaluationError("can only use math- or builtin function", expr.sourceref)
elif isinstance(target, Dereference): # '[...](1,2,3)'
raise NotImplementedError("dereferenced call") # XXX
raise ExpressionEvaluationError("dereferenced value call is not a constant value", expr.sourceref)
elif isinstance(target, int): # '64738()'
raise NotImplementedError("immediate address call") # XXX
raise ExpressionEvaluationError("immediate address call is not a constant value", expr.sourceref)
else:
raise NotImplementedError("weird call target", target)
else:

View File

@ -247,13 +247,18 @@ logic and several utility routines that do I/O, such as ``print_string``.
The ``%asm {`` and ``}`` start and end markers each have to be on their own unique line.
### Program Entry Point
### Program Start and Entry Point
Every program has to have one entry point where code execution begins.
The compiler looks for the ``start`` label in the ``main`` block for this.
For proper program termination, this block has to end with a ``return`` statement (or a ``goto`` call).
Blocks and other details are described below.
The initial values of your variables will be restored automatically when the program is (re)started,
*except for string variables*. It is assumed they are unchanged by your program.
If you do modify them in-place, you should take care yourself that they work as
expected when the program is restarted.
### Blocks

View File

@ -6,6 +6,14 @@ from il65.plylex import SourceRef
def test_datatypes():
assert all(isinstance(s, datatypes.DataType) for s in datatypes.STRING_DATATYPES)
assert all(s.isstring() for s in datatypes.STRING_DATATYPES)
assert not any(s.isarray() or s.isnumeric() for s in datatypes.STRING_DATATYPES)
assert datatypes.DataType.WORDARRAY.isarray()
assert not datatypes.DataType.WORDARRAY.isnumeric()
assert not datatypes.DataType.WORDARRAY.isstring()
assert not datatypes.DataType.WORD.isarray()
assert datatypes.DataType.WORD.isnumeric()
assert not datatypes.DataType.WORD.isstring()
def test_sourceref():

View File

@ -34,7 +34,6 @@ bar: goto $c000
; ----
goto sub1
;goto sub2 (1 ) ; @todo error, must be return sub2(1) -> optimized in 'tail call'
return sub2 ( )
return sub2 ()
return sub2 (1 )
@ -47,13 +46,12 @@ bar: goto $c000
return sub4 (string="hello", other = 42)
return bar ()
goto [AX]
; goto [AX()] % ; @todo error, must be return()
goto [var1]
goto [mem1] ; comment
goto $c000
goto 64738
64738(1,2) ; @todo jsr $64738 ??
return 9999() ; @todo jsr 9999 ??
64738(1,2) ; @todo should be jsr $64738
return 9999() ; @todo should be jmp 9999 ?
return [AX]()
return [var1] () ; comment
return [mem1] ()
@ -63,13 +61,12 @@ bar: goto $c000
return $c2()
goto [$c2.word]
return 33
return [$c2.word] ; @todo this as rvalue
; [ $c2.word (4) ] ;@ todo parse precedence
return [$c2.word]
return [$c2.word] (4)
return [$c2.word] (4)
return [$c2.word] (4)
return [$c2.word] (4)
; return [$c2dd.word] ( ) ;@ todo parse precedence
return [$c2dd.word] ( )
goto [$c2dd.word]
%asm {
@ -128,9 +125,8 @@ bar: goto $c000
[AX]()
[var1] ( )
[mem1] ()
A= [$c2.word(4)]
;A= [$c2.word() ] ; @todo Precedence?
;A= [$c2dd.word() ] ; @todo Precedence?
A= [$c2.word]
A= [$c2dd.word ]
$c000()
$c2()

View File

@ -78,7 +78,7 @@
var .stext stext = 'screencodes-null'
var .pstext pstext = "screencodes-pascal"
var .matrix( 2, 400 ) uninitmatrix
var .matrix( 2, 128 ) uninitmatrix
var .matrix(10, 20) initmatrix1 = $12
var .matrix(10, 20) initmatrix1b = true
var .matrix(10, 20) initmatrix1c = '@'
@ -305,7 +305,7 @@ start:
AY = "text-immediate" ; reuses existing
AX = "another"
AX = ""
;*$c000.word = "another" ; reuse @ todo precedence?
;*$c100.word = "text-immediate" ; reuse @ todo precedence?
;*$c200.word = "" ; reuse @ todo precedence?
[$c000.word] = "another" ; must reuse string
[$c100.word] = "text-immediate" ; must reuse string
[$c200.word] = "" ; must reuse string
}

View File

@ -13,8 +13,9 @@ start:
c64.init_system()
A = c64.VMCSB
A |= 2 ; @todo c64.VMCSB |= 2
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: ")
@ -58,7 +59,8 @@ ask_guess:
[$22.word] = guess
c64.FREADSTR(A)
AY = c64flt.GETADRAY()
A -= secretnumber ; @todo condition so we can do if guess > secretnumber....
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 ")
@ -87,9 +89,12 @@ game_over:
return
sub goodbye ()->() {
;var x ; @todo vars in sub
;memory y = $c000 ; @todo vars in sub
;const q = 22 ; @todo const in sub
var xxxxxx ; @todo vars in sub
memory y = $c000 ; @todo vars in sub
const q = 22 ; @todo const in sub
xxxxxx = q *4
xxxxxx = qqqqq *44 ;@todo symbol error
c64scr.print_string("\nThanks for playing. Bye!\n")
return

View File

@ -1,67 +1,47 @@
%output basic
%zp clobber,restore
~ ZP {
var zp1_1 = 200
var zp1_2 = 200
var .word zp1_3 = $ff99
var .word zp1_4 = $ee88
const zpc1_1 = 55
const .word zpc1_2 = 2333.54566
const .float zpc1_3 = 6.54566
}
~ ZP {
var zp2_1 = 100
var zp2_2 = 100
var .word zp2_3 = $55aa
var .word zp2_4 = $66bb
var .word zp2_5 = $66bc
var .word zp2_6 = $66bd
var .word zp2_7 = $66be
var .word zp2_8 = $67be
var .word zp2_9 = $68be
var .word zp2_10 = $69be
var .word zp2_11 = $69be
var .array(4) array1
var .wordarray(4) warray1
var .matrix(3,3) matrix1
var .text string = "bye"
const .text zpc2_1 = "hello"
const zpc2_2 = 0
}
%import c64lib
~ main {
var .float float1 = 3.14
var .float float2 = 3.14
var .float float3 = 3.14
var .float float4 = 3.14
var .float float5 = 3.14
var .array(10) array1
var .wordarray(10) warray1
var .matrix(4,4) matrix1
var b1 = 10
var b2 = 20
var b3 = 10
var b4 = 20
var b5 = 10
var b6 = 20
var b7 = 10
var b8 = 30
var b9 = 30
var b10 = 40
var b11 = 40
var b12 = 30
var b13 = 40
var b14 = 0
var b15 = 0
var b16 = 0
var zp1_1 = 200
var zp1_2 = 200
var .text zp_s1 = "hello\n"
var .ptext zp_s2 = "goodbye\n"
var .stext zp_s3 = "welcome\n"
var .pstext zp_s4 = "go away\n"
const .text ctext = "constant\n"
var .array(20) arr1 = $ea
var .wordarray(20) arr2 = $ea
start:
%asm {
return 1.22, 46
lda zp1_1
jsr c64scr.print_byte_decimal0
inc zp1_1
lda zp1_1
jsr c64scr.print_byte_decimal0
inc zp1_1
lda zp1_1
jsr c64scr.print_byte_decimal0
inc zp1_1
lda zp1_1
jsr c64scr.print_byte_decimal0
inc zp1_1
;ldx #<zp_s1
;ldy #>zp_s1
;jsr c64scr.print_string
;ldx #<zp_s2
;ldy #>zp_s2
;jsr c64scr.print_pstring
;ldx #<ctext
;ldy #>ctext
;jsr c64scr.print_string
}
return
}