var dimensions check

This commit is contained in:
Irmen de Jong 2018-01-13 02:13:32 +01:00
parent 62dfdace71
commit 1df28c8091
11 changed files with 78 additions and 32 deletions

View File

@ -81,7 +81,8 @@ class PlyParser:
if not module.scope.nodes:
return
zpnode = module.scope.nodes[0]
assert zpnode.name == "ZP", "first node should be the (only) ZP"
if zpnode.name != "ZP":
return
zeropage = Zeropage(module.zp_options)
for vardef in zpnode.scope.filter_nodes(VarDef):
try:
@ -94,9 +95,11 @@ class PlyParser:
# process/simplify all expressions (constant folding etc)
encountered_blocks = set()
for block, parent in module.all_scopes():
if block.name in encountered_blocks:
raise ValueError("block names not unique:", block.name)
encountered_blocks.add(block.name)
parentname = (parent.name + ".") if parent else ""
blockname = parentname + block.name
if blockname in encountered_blocks:
raise ValueError("block names not unique:", blockname)
encountered_blocks.add(blockname)
for node in block.nodes:
try:
node.process_expressions(block.scope)
@ -323,7 +326,7 @@ class PlyParser:
if sub_node.name not in {"asmbinary", "asminclude", "breakpoint", "saveregisters"}:
raise ParseError("invalid directive in " + node.__class__.__name__.lower(), sub_node.sourceref)
if sub_node.name == "saveregisters" and not first_node:
raise ParseError("saveregisters directive should be the first", sub_node.sourceref)
raise ParseError("saveregisters directive must be the first", sub_node.sourceref)
first_node = False
def process_imports(self, module: Module) -> None:

View File

@ -68,10 +68,13 @@ FLOAT_MAX_NEGATIVE = -1.7014118345e+38
def to_hex(number: int) -> str:
# 0..255 -> "$00".."$ff"
# 0..15 -> "0".."15"
# 16..255 -> "$10".."$ff"
# 256..65536 -> "$0100".."$ffff"
if number is None:
raise ValueError("number")
if 0 <= number < 16:
return str(number)
if 0 <= number < 0x100:
return "${:02x}".format(number)
if 0 <= number < 0x10000:

View File

@ -270,6 +270,19 @@ class AssemblyGenerator:
self.p("_init_strings_size = * - _init_strings_start")
self.p("")
def _numeric_value_str(self, value: Any, as_hex: bool=False) -> str:
if isinstance(value, bool):
return "1" if value else "0"
if isinstance(value, int):
if as_hex:
return to_hex(value)
return str(value)
if isinstance(value, (int, float)):
if as_hex:
raise TypeError("cannot output float as hex")
return str(value)
raise TypeError("no numeric representation possible", value)
def generate_block_vars(self, block: Block, zeropage: bool=False) -> None:
# Generate the block variable storage.
# The memory bytes of the allocated variables is set to zero (so it compresses very well),
@ -280,9 +293,9 @@ class AssemblyGenerator:
self.p("; constants")
for vardef in vars_by_vartype.get(VarType.CONST, []):
if vardef.datatype == DataType.FLOAT:
self.p("\v{:s} = {}".format(vardef.name, vardef.value))
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, to_hex(vardef.value)))
self.p("\v{:s} = {:s}".format(vardef.name, self._numeric_value_str(vardef.value, True)))
elif vardef.datatype in STRING_DATATYPES:
# a const string is just a string variable in the generated assembly
self._generate_string_var(vardef)

View File

@ -258,7 +258,6 @@ class CodeGenerator:
self.p("\t.pend\n")
def generate_block_vars(self, block: Block) -> None:
# @todo block vars should be re-initialized when the program is run again, and not depend on statically prefilled data!
consts = [c for c in block.symbols.iter_constants()]
if consts:
self.p("; constants")
@ -928,7 +927,7 @@ class CodeGenerator:
if isinstance(stmt.target, MemMappedValue):
targetstr = stmt.target.name or Parser.to_hex(stmt.address)
else:
raise CodeError("call sub target should be mmapped")
raise CodeError("call sub target must be mmapped")
if stmt.is_goto:
generate_param_assignments()
branch_emitter(targetstr, True, False)
@ -1857,7 +1856,7 @@ class CodeGenerator:
self.p("\t\tld{:s} #<{:s}".format(lv.register[0].lower(), rvalue.name))
self.p("\t\tld{:s} #>{:s}".format(lv.register[1].lower(), rvalue.name))
else:
raise CodeError("cannot assign immediate string, it should be a string variable")
raise CodeError("cannot assign immediate string, it must be a string variable")
def generate_assign_string_to_memory(self, lv: MemMappedValue, rvalue: StringValue) -> None:
if lv.datatype != DataType.WORD:
@ -1869,7 +1868,7 @@ class CodeGenerator:
self.p("\t\tlda #>{:s}".format(rvalue.name))
self.p("\t\tsta {}+1".format(assign_target))
else:
raise CodeError("cannot assign immediate string, it should be a string variable")
raise CodeError("cannot assign immediate string, it must be a string variable")
def footer(self) -> None:
self.p("\n\n.end")

View File

@ -202,7 +202,7 @@ class Parser:
for sub in block.symbols.iter_subroutines(True):
self._check_return_statement(sub.sub_block, "subroutine '{:s}'".format(sub.name))
if not main_found:
raise self.PError("a block 'main' should be defined and contain the program's entry point label 'start'")
raise self.PError("a block 'main' must be defined and contain the program's entry point label 'start'")
def _check_return_statement(self, block: Block, message: str) -> None:
# find last statement that isn't a comment
@ -560,7 +560,7 @@ class Parser:
arg = block_args.pop(0)
if arg.isidentifier():
if arg.lower() == "zeropage" or arg in ("zp", "zP", "Zp"):
raise self.PError("zero page block should be named 'ZP'")
raise self.PError("zero page block must be named 'ZP'")
is_zp_block = arg == "ZP"
if arg in set(b.name for b in self.result.blocks):
orig = [b for b in self.result.blocks if b.name == arg][0]
@ -980,7 +980,7 @@ class Parser:
return CallStmt(self.sourceref, target, address=address, arguments=arguments,
outputs=outputvars, preserve_regs=preserve_regs)
else:
raise TypeError("target should be a Value", target)
raise TypeError("target must be a Value", target)
def parse_integer(self, text: str) -> int:
text = text.strip()

View File

@ -848,7 +848,7 @@ class IntegerValue(Value):
self.value = value
elif value is None:
if not name:
raise ValueError("when integer value is not given, the name symbol should be speicified")
raise ValueError("when integer value is not given, the name symbol must be specified")
super().__init__(datatype, sourceref, name, True)
self.value = None
else:

View File

@ -481,7 +481,7 @@ _loop lda #0
sub scroll_left_full (alsocolors: SC) -> (A?, X?, Y?) {
; ---- scroll the whole screen 1 character to the left
; contents of the rightmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data should be scrolled too
; Carry flag determines if screen color data must be scrolled too
%asm {
bcs +
jmp _scroll_screen
@ -540,7 +540,7 @@ _scroll_screen
sub scroll_right_full (alsocolors: SC) -> (A?, X?) {
; ---- scroll the whole screen 1 character to the right
; contents of the leftmost column are unchanged, you should clear/refill this yourself
; Carry flag determines if screen color data should be scrolled too
; Carry flag determines if screen color data must be scrolled too
%asm {
bcs +
jmp _scroll_screen
@ -591,7 +591,7 @@ _scroll_screen
sub scroll_up_full (alsocolors: SC) -> (A?, X?) {
; ---- scroll the whole screen 1 character up
; contents of the bottom row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data should be scrolled too
; Carry flag determines if screen color data must be scrolled too
%asm {
bcs +
jmp _scroll_screen
@ -642,7 +642,7 @@ _scroll_screen
sub scroll_down_full (alsocolors: SC) -> (A?, X?) {
; ---- scroll the whole screen 1 character down
; contents of the top row are unchanged, you should refill/clear this yourself
; Carry flag determines if screen color data should be scrolled too
; Carry flag determines if screen color data must be scrolled too
%asm {
bcs +
jmp _scroll_screen

View File

@ -50,6 +50,8 @@ def main() -> None:
mon_command_file = assembler.generate_breakpoint_list(program_filename)
duration_total = time.perf_counter() - start
print("Compile duration: {:.2f} seconds".format(duration_total))
size = os.path.getsize(program_filename)
print("Output size: {:d} bytes".format(size))
print_bold("Output file: " + program_filename)
print()
if args.startvice:

View File

@ -181,7 +181,7 @@ class Scope(AstNode):
self._populate_symboltable(newnode)
def validate_address(obj: AstNode, attrib: attr.Attribute, value: Optional[int]):
def validate_address(obj: AstNode, attrib: attr.Attribute, value: Optional[int]) -> None:
if value is None:
return
if isinstance(obj, Block) and obj.name == "ZP":
@ -190,6 +190,27 @@ def validate_address(obj: AstNode, attrib: attr.Attribute, value: Optional[int])
raise ParseError("invalid {:s} (must be from $0200 to $ffff)".format(attrib.name), obj.sourceref)
def dimensions_validator(obj: 'DatatypeNode', attrib: attr.Attribute, value: List[int]) -> None:
if not value:
return
dt = obj.to_enum()
if value and dt not in (DataType.MATRIX, DataType.WORDARRAY, DataType.BYTEARRAY):
raise ParseError("cannot use a dimension for this datatype", obj.sourceref)
if dt == DataType.WORDARRAY or dt == DataType.BYTEARRAY:
if len(value) == 1:
if value[0] <= 0 or value[0] > 256:
raise ParseError("array length must be 1..256", obj.sourceref)
else:
raise ParseError("array must have only one dimension", obj.sourceref)
if dt == DataType.MATRIX:
if len(value) == 2:
size = value[0] * value[1]
if size <= 0 or size > 0x8000:
raise ParseError("matrix size columns * rows must be 1..32768", obj.sourceref)
else:
raise ParseError("matrix must have two dimensions", obj.sourceref)
@attr.s(cmp=False, repr=False)
class Block(AstNode):
scope = attr.ib(type=Scope)
@ -432,7 +453,7 @@ class VarDef(AstNode):
@attr.s(cmp=False, slots=True, repr=False)
class DatatypeNode(AstNode):
name = attr.ib(type=str)
dimensions = attr.ib(type=list, default=None) # if set, 1 or more dimensions (ints)
dimensions = attr.ib(type=list, default=None, validator=dimensions_validator) # if set, 1 or more dimensions (ints)
def to_enum(self):
return {
@ -556,7 +577,7 @@ class Expression(AstNode):
assert self.operator not in ("++", "--"), "incr/decr should not be an expression"
def process_expressions(self, scope: Scope) -> None:
raise RuntimeError("should be done via parent node's process_expressions")
raise RuntimeError("must be done via parent node's process_expressions")
def evaluate_primitive_constants(self, scope: Scope) -> Union[int, float, str, bool]:
# make sure the lvalue and rvalue are primitives, and the operator is allowed
@ -628,9 +649,11 @@ def process_constant_expression(expr: Any, sourceref: SourceRef, symbolscope: Sc
return value.value
if value.vartype == VarType.CONST:
raise ExpressionEvaluationError("can't take the address of a constant", expr.name.sourceref)
raise ExpressionEvaluationError("address-of this {} isn't a compile-time constant".format(value.__class__.__name__), expr.name.sourceref)
raise ExpressionEvaluationError("address-of this {} isn't a compile-time constant"
.format(value.__class__.__name__), expr.name.sourceref)
else:
raise ExpressionEvaluationError("constant address required, not {}".format(value.__class__.__name__), expr.name.sourceref)
raise ExpressionEvaluationError("constant address required, not {}"
.format(value.__class__.__name__), expr.name.sourceref)
except LookupError as x:
raise ParseError(str(x), expr.sourceref) from None
elif isinstance(expr, SubCall):
@ -656,9 +679,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)'
return None # XXX
return None # XXX
elif isinstance(target, int): # '64738()'
return None # XXX
return None # XXX
else:
raise NotImplementedError("weird call target", target) # XXX
else:

View File

@ -22,8 +22,11 @@ def test_parseerror():
def test_to_hex():
assert datatypes.to_hex(0) == "$00"
assert datatypes.to_hex(1) == "$01"
assert datatypes.to_hex(0) == "0"
assert datatypes.to_hex(1) == "1"
assert datatypes.to_hex(10) == "10"
assert datatypes.to_hex(15) == "15"
assert datatypes.to_hex(16) == "$10"
assert datatypes.to_hex(255) == "$ff"
assert datatypes.to_hex(256) == "$0100"
assert datatypes.to_hex(20060) == "$4e5c"

View File

@ -65,10 +65,10 @@
var .float initfloat5 = -1.70141183e+38
var .float initfloat6 = 1.234
var .wordarray( 10 ) uninit_wordarray
var .wordarray( 256 ) uninit_wordarray
var .wordarray(10) init_wordarray = $1234
var .wordarray(10) init_wordarrayb = true
var .array( 10) uninit_bytearray
var .array( 256) uninit_bytearray
var .array(10 ) init_bytearray =$12
var .array(10 ) init_bytearrayb =true
var .array(10 ) init_bytearrayc ='@'
@ -78,7 +78,7 @@
var .stext stext = 'screencodes-null'
var .pstext pstext = "screencodes-pascal"
var .matrix( 10, 20 ) uninitmatrix
var .matrix( 2, 400 ) uninitmatrix
var .matrix(10, 20) initmatrix1 = $12
var .matrix(10, 20) initmatrix1b = true
var .matrix(10, 20) initmatrix1c = '@'