code generation

This commit is contained in:
Irmen de Jong 2018-01-09 23:28:39 +01:00
parent 14e36a8708
commit 0bb5f98768
9 changed files with 567 additions and 126 deletions

View File

@ -126,7 +126,7 @@ class PlyParser:
block.address = 0xc000
elif directive.args[0] == "prg":
block.format = ProgramFormat.PRG
block.address = 0x0801
block.address = 0xc000
elif directive.args[0] == "basic":
block.format = ProgramFormat.BASIC
block.address = 0x0801

240
il65/datatypes.py Normal file
View File

@ -0,0 +1,240 @@
"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
Here are the data type definitions and -conversions.
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
import math
import enum
from typing import Tuple, Union
from functools import total_ordering
from .plylex import print_warning, SourceRef
PrimitiveType = Union[int, float, str]
@total_ordering
class VarType(enum.Enum):
CONST = 1
MEMORY = 2
VAR = 3
def __lt__(self, other):
if self.__class__ == other.__class__:
return self.value < other.value
return NotImplemented
@total_ordering
class DataType(enum.Enum):
"""The possible data types of values"""
BYTE = 1
WORD = 2
FLOAT = 3
BYTEARRAY = 4
WORDARRAY = 5
MATRIX = 6
STRING = 7
STRING_P = 8
STRING_S = 9
STRING_PS = 10
def __lt__(self, other):
if self.__class__ == other.__class__:
return self.value < other.value
return NotImplemented
STRING_DATATYPES = {DataType.STRING, DataType.STRING_P, DataType.STRING_S, DataType.STRING_PS}
# 5-byte cbm MFLPT format limitations:
FLOAT_MAX_POSITIVE = 1.7014118345e+38
FLOAT_MAX_NEGATIVE = -1.7014118345e+38
def to_hex(number: int) -> str:
# 0..255 -> "$00".."$ff"
# 256..65536 -> "$0100".."$ffff"
if number is None:
raise ValueError("number")
if 0 <= number < 0x100:
return "${:02x}".format(number)
if 0 <= number < 0x10000:
return "${:04x}".format(number)
raise OverflowError(number)
def to_mflpt5(number: float) -> bytearray:
# algorithm here https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
number = float(number)
if number < FLOAT_MAX_NEGATIVE or number > FLOAT_MAX_POSITIVE:
raise OverflowError("floating point number out of 5-byte mflpt range", number)
if number == 0.0:
return bytearray([0, 0, 0, 0, 0])
if number < 0.0:
sign = 0x80000000
number = -number
else:
sign = 0x00000000
mant, exp = math.frexp(number)
exp += 128
if exp < 1:
# underflow, use zero instead
return bytearray([0, 0, 0, 0, 0])
if exp > 255:
raise OverflowError("floating point number out of 5-byte mflpt range", number)
mant = sign | int(mant * 0x100000000) & 0x7fffffff
return bytearray([exp]) + int.to_bytes(mant, 4, "big")
def mflpt5_to_float(mflpt: bytearray) -> float:
# algorithm here https://sourceforge.net/p/acme-crossass/code-0/62/tree/trunk/ACME_Lib/cbm/mflpt.a
if mflpt == bytearray([0, 0, 0, 0, 0]):
return 0.0
exp = mflpt[0] - 128
sign = mflpt[1] & 0x80
number = 0x80000000 | int.from_bytes(mflpt[1:], "big")
number = float(number) * 2**exp / 0x100000000
return -number if sign else number
def coerce_value(datatype: DataType, value: PrimitiveType, sourceref: SourceRef=None) -> Tuple[bool, PrimitiveType]:
# if we're a BYTE type, and the value is a single character, convert it to the numeric value
def verify_bounds(value: PrimitiveType) -> None:
# if the value is out of bounds, raise an overflow exception
if datatype == DataType.BYTE and not (0 <= value <= 0xff): # type: ignore
raise OverflowError("value out of range for byte")
if datatype == DataType.WORD and not (0 <= value <= 0xffff): # type: ignore
raise OverflowError("value out of range for word")
if datatype == DataType.FLOAT and not (FLOAT_MAX_NEGATIVE <= value <= FLOAT_MAX_POSITIVE): # type: ignore
raise OverflowError("value out of range for float")
if datatype in (DataType.BYTE, DataType.BYTEARRAY, DataType.MATRIX) and isinstance(value, str):
if len(value) == 1:
return True, char_to_bytevalue(value)
# if we're an integer value and the passed value is float, truncate it (and give a warning)
if datatype in (DataType.BYTE, DataType.WORD, DataType.MATRIX) and isinstance(value, float):
frac = math.modf(value)
if frac != 0:
print_warning("float value truncated ({} to datatype {})".format(value, datatype.name), sourceref=sourceref)
value = int(value)
verify_bounds(value)
return True, value
verify_bounds(value)
return False, value
def char_to_bytevalue(character: str, petscii: bool=True) -> int:
assert len(character) == 1
if petscii:
return ord(character.translate(ascii_to_petscii_trans))
else:
raise NotImplementedError("screencode conversion not yet implemented for chars")
# ASCII/UNICODE-to-PETSCII translation table
# Unicode symbols supported that map to a PETSCII character: £ ↑ ← ♠ ♥ ♦ ♣ π ● ○ and various others
ascii_to_petscii_trans = str.maketrans({
'\f': 147, # form feed becomes ClearScreen "{clear}"
'\n': 13, # line feed becomes a RETURN "{cr}" (not a line feed)
'\r': 17, # CR becomes CursorDown "{down}"
'a': 65,
'b': 66,
'c': 67,
'd': 68,
'e': 69,
'f': 70,
'g': 71,
'h': 72,
'i': 73,
'j': 74,
'k': 75,
'l': 76,
'm': 77,
'n': 78,
'o': 79,
'p': 80,
'q': 81,
'r': 82,
's': 83,
't': 84,
'u': 85,
'v': 86,
'w': 87,
'x': 88,
'y': 89,
'z': 90,
'A': 97,
'B': 98,
'C': 99,
'D': 100,
'E': 101,
'F': 102,
'G': 103,
'H': 104,
'I': 105,
'J': 106,
'K': 107,
'L': 108,
'M': 109,
'N': 110,
'O': 111,
'P': 112,
'Q': 113,
'R': 114,
'S': 115,
'T': 116,
'U': 117,
'V': 118,
'W': 119,
'X': 120,
'Y': 121,
'Z': 122,
'{': 179, # left squiggle
'}': 235, # right squiggle
'£': 92, # pound currency sign
'^': 94, # up arrow
'~': 126, # pi math symbol
'π': 126, # pi symbol
'`': 39, # single quote
'': 250, # check mark
'|': 221, # vertical bar
'': 221, # vertical bar
'': 96, # horizontal bar
'': 123, # vertical and horizontal bar
'': 94, # up arrow
'': 95, # left arrow
'': 163, # upper bar
'_': 164, # lower bar (underscore)
'': 164, # lower bar
'': 165, # left bar
'': 97, # spades
'': 113, # circle
'': 115, # hearts
'': 119, # open circle
'': 120, # clubs
'': 122, # diamonds
'': 171, # vertical and right
'': 179, # vertical and left
'': 177, # horiz and up
'': 178, # horiz and down
'': 173, # up right
'': 174, # down left
'': 175, # down right
'': 189, # up left
'': 172, # block lr
'': 187, # block ll
'': 188, # block ur
'': 190, # block ul
'': 191, # block ul and lr
'': 161, # left half
'': 162, # lower half
'': 230, # raster
})

View File

@ -5,13 +5,13 @@ This is the assembly code generator (from the parse tree)
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
import io
import re
import subprocess
import datetime
from typing import Union
from .plyparse import Module, ProgramFormat, Block, Directive, VarDef, Label, ZpOptions, DataType
from .symbols import to_hex
import itertools
from typing import Union, TextIO, List, Tuple, Iterator
from .plyparse import Module, ProgramFormat, Block, Directive, VarDef, Label, Subroutine, AstNode, ZpOptions
from .datatypes import VarType, DataType, to_hex, mflpt5_to_float, to_mflpt5, STRING_DATATYPES
class CodeError(Exception):
@ -24,17 +24,20 @@ class AssemblyGenerator:
def __init__(self, module: Module) -> None:
self.module = module
self.generated_code = io.StringIO()
self.cur_block = None
self.output = None # type: TextIO
def p(self, text, *args, **vargs):
# replace '\v' (vertical tab) char by the actual line indent (2 tabs) and write to the stringIo
print(text.replace("\v", "\t\t"), *args, file=self.generated_code, **vargs)
print(text.replace("\v", "\t\t"), *args, file=self.output, **vargs)
def generate(self, filename: str) -> None:
self._generate()
with open(filename, "wt") as out:
out.write(self.generated_code.getvalue())
self.generated_code.close()
with open(filename, "wt") as self.output:
try:
self._generate()
except Exception as x:
self.output.write(".error \"****** ABORTED DUE TO ERROR: " + str(x) + "\"\n")
raise
def _generate(self) -> None:
self.sanitycheck()
@ -93,7 +96,7 @@ class AssemblyGenerator:
zpblock = self.module.zeropage()
if zpblock:
vars_to_init = [v for v in zpblock.scope.filter_nodes(VarDef)
if v.allocate and v.type in (DataType.BYTE, DataType.WORD, DataType.FLOAT)]
if v.vartype == VarType.VAR and v.vartype 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]
@ -131,6 +134,7 @@ class AssemblyGenerator:
self.p("\v; there are no zp vars to initialize")
else:
self.p("\v; there is no zp block to initialize")
# @todo all block vars should be (re)initialized here as well!
if self.module.zp_options == ZpOptions.CLOBBER_RESTORE:
self.p("\vjsr {:s}.start\v; call user code".format(self.module.main().label))
self.p("\vcld")
@ -142,13 +146,190 @@ class AssemblyGenerator:
self.p("_float_bytes_{:s}\v.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}\t; {}".format(varname, *bytes, fpvalue))
self.p("\n")
def blocks(self):
self.p("; @todo") # @todo
pass
def blocks(self) -> None:
zpblock = self.module.zeropage()
if zpblock:
# if there's a Zeropage block, it always goes first
self.cur_block = zpblock # type: ignore
self.p("\n; ---- zero page block: '{:s}' ----\t\t; src l. {:d}\n".format(zpblock.sourceref.file, zpblock.sourceref.line))
self.p("{:s}\t.proc\n".format(zpblock.label))
self.generate_block_vars(zpblock)
self.p("\v.pend\n")
for block in sorted(self.module.scope.filter_nodes(Block), key=lambda b: b.address or 0):
if block.name == "ZP":
continue # already processed
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)
subroutines = list(sub for sub in block.scope.filter_nodes(Subroutine) if sub.address is not None)
if subroutines:
# these are (external) subroutines that are defined by address instead of a scope/code
self.p("; external subroutines")
for subdef in subroutines:
assert subdef.scope is None
self.p("\v{:s} = {:s}".format(subdef.name, to_hex(subdef.address)))
self.p("; end external subroutines\n")
for stmt in block.scope.nodes:
if isinstance(stmt, Directive):
continue # should have been handled already
self.generate_statement(stmt)
if block.name == "main" and isinstance(stmt, Label) and stmt.name == "start":
# make sure the main.start routine clears the decimal and carry flags as first steps
self.p("\vcld\t\t\t; clear decimal flag")
self.p("\vclc\t\t\t; clear carry flag")
self.p("\vclv\t\t\t; clear overflow flag")
subroutines = list(sub for sub in block.scope.filter_nodes(Subroutine) if sub.address is None)
if subroutines:
# these are subroutines that are defined by a scope/code
self.p("; -- block subroutines")
for subdef in subroutines:
assert subdef.scope is not None
self.p("{:s}\v; src l. {:d}".format(subdef.name, subdef.sourceref.line))
params = ", ".join("{:s} -> {:s}".format(name or "<unnamed>", registers) for name, registers in subdef.param_spec)
returns = ",".join(sorted(register for register in subdef.result_spec if register[-1] != '?'))
clobbers = ",".join(sorted(register for register in subdef.result_spec if register[-1] == '?'))
self.p("\v; params: {}\n\v; returns: {} clobbers: {}"
.format(params or "-", returns or "-", clobbers or "-"))
cur_block = self.cur_block
self.cur_block = subdef.scope
for stmt in subdef.scope.nodes:
if isinstance(stmt, Directive):
continue # should have been handled already
self.generate_statement(stmt)
self.cur_block = cur_block
self.p("")
self.p("; -- end block subroutines")
self.p("\n\v.pend\n")
def footer(self):
self.p("; @todo") # @todo
pass
def footer(self) -> None:
self.p("\t.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:
if char == '\f':
result += "{clear}"
elif char == '\b':
result += "{delete}"
elif char == '\n':
result += "{cr}"
elif char == '\r':
result += "{down}"
elif char == '\t':
result += "{tab}"
else:
result += '", {:d}, "'.format(ord(char))
return result + '"'
def 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!
vars_by_vartype = itertools.groupby(block.scope.filter_nodes(VarDef), lambda c: c.vartype)
variable_definitions = sorted(vars_by_vartype, key=lambda gt: gt[0]) # type: List[Tuple[VarType, Iterator[VarDef]]]
for vartype, varnodes in variable_definitions:
if vartype == VarType.CONST:
self.p("; constants")
for vardef in varnodes:
if vardef.datatype == DataType.FLOAT:
self.p("\t\t{:s} = {}".format(vardef.name, vardef.value))
elif vardef.datatype in (DataType.BYTE, DataType.WORD):
self.p("\t\t{:s} = {:s}".format(vardef.name, to_hex(vardef.value)))
elif vardef.datatype in STRING_DATATYPES:
# a const string is just a string variable in the generated assembly
self._generate_string_var(vardef)
else:
raise CodeError("invalid const type", vardef)
elif vartype == VarType.MEMORY:
self.p("; memory mapped variables")
for vardef in varnodes:
# create a definition for variables at a specific place in memory (memory-mapped)
if vardef.datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
assert vardef.size == [1]
self.p("\t\t{:s} = {:s}\t; {:s}".format(vardef.name, to_hex(vardef.value), vardef.datatype.name.lower()))
elif vardef.datatype == DataType.BYTEARRAY:
assert len(vardef.size) == 1
self.p("\t\t{:s} = {:s}\t; array of {:d} bytes".format(vardef.name, to_hex(vardef.value), vardef.size[0]))
elif vardef.datatype == DataType.WORDARRAY:
assert len(vardef.size) == 1
self.p("\t\t{:s} = {:s}\t; array of {:d} words".format(vardef.name, to_hex(vardef.value), vardef.size[0]))
elif vardef.datatype == DataType.MATRIX:
assert len(vardef.size) == 2
self.p("\t\t{:s} = {:s}\t; matrix {:d} by {:d} = {:d} bytes"
.format(vardef.name, to_hex(vardef.value), vardef.size[0], vardef.size[1], vardef.size[0]*vardef.size[1]))
else:
raise CodeError("invalid var type")
elif vartype == VarType.VAR:
self.p("; normal variables")
for vardef in varnodes:
# create a definition for a variable that takes up space and will be initialized at startup
if vardef.datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
assert vardef.size == [1]
if vardef.datatype == DataType.BYTE:
self.p("{:s}\t\t.byte {:s}".format(vardef.name, to_hex(int(vardef.value or 0))))
elif vardef.datatype == DataType.WORD:
self.p("{:s}\t\t.word {:s}".format(vardef.name, to_hex(int(vardef.value or 0))))
elif vardef.datatype == DataType.FLOAT:
self.p("{:s}\t\t.byte ${:02x}, ${:02x}, ${:02x}, ${:02x}, ${:02x}{:s}"
.format(vardef.name, *to_mflpt5(float(vardef.value or 0.0))))
else:
raise CodeError("weird datatype")
elif vardef.datatype in (DataType.BYTEARRAY, DataType.WORDARRAY):
assert len(vardef.size) == 1
if vardef.datatype == DataType.BYTEARRAY:
self.p("{:s}\t\t.fill {:d}, ${:02x}".format(vardef.name, vardef.size[0], vardef.value or 0))
elif vardef.datatype == 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.size[0] * 2, f_lo, f_hi, vardef.size[0], vardef.value or 0))
else:
raise CodeError("invalid datatype", vardef.datatype)
elif vardef.datatype == DataType.MATRIX:
assert len(vardef.size) == 2
self.p("{:s}\t\t.fill {:d}, ${:02x}\t\t; matrix {:d}*{:d} bytes"
.format(vardef.name, vardef.size[0] * vardef.size[1], vardef.value or 0, vardef.size[0], vardef.size[1]))
elif vardef.datatype in STRING_DATATYPES:
self._generate_string_var(vardef)
else:
raise CodeError("unknown variable type " + str(vardef.datatype))
def _generate_string_var(self, vardef: VarDef) -> None:
if vardef.datatype == DataType.STRING:
# 0-terminated string
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}\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}\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}\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:
if isinstance(stmt, Label):
self.p("\n{:s}\v\t\t; {:s}".format(stmt.name, stmt.lineref))
# @todo rest of the statement nodes
class Assembler64Tass:
@ -164,7 +345,7 @@ class Assembler64Tass:
elif self.format == ProgramFormat.RAW:
args.append("--nostart")
else:
raise ValueError("don't know how to create format "+str(self.format))
raise CodeError("don't know how to create format "+str(self.format))
try:
if self.format == ProgramFormat.PRG:
print("\nCreating C-64 prg.")

View File

@ -11,7 +11,7 @@ from typing import Union, Generator, Tuple, List, Optional, Dict
import attr
from ply.yacc import yacc
from .plylex import SourceRef, tokens, lexer, find_tok_column
from .symbols import DataType
from .datatypes import DataType, VarType, coerce_value
class ProgramFormat(enum.Enum):
@ -71,7 +71,7 @@ class AstNode:
def process_expressions(self) -> None:
# process/simplify all expressions (constant folding etc) @todo
# override in node types that have expression(s)
# @todo override in node types that have expression(s)
pass
@ -319,21 +319,45 @@ class VarDef(AstNode):
vartype = attr.ib()
datatype = attr.ib()
value = attr.ib(default=None)
size = attr.ib(type=int, default=None)
size = attr.ib(type=list, default=None)
def __attrs_post_init__(self):
# convert vartype to enum
if self.vartype == "const":
self.vartype = VarType.CONST
elif self.vartype == "var":
self.vartype = VarType.VAR
elif self.vartype == "memory":
self.vartype = VarType.MEMORY
else:
raise ValueError("invalid vartype", self.vartype)
# convert datatype node to enum + size
if self.datatype is None:
assert self.size is None
self.size = 1
self.size = [1]
self.datatype = DataType.BYTE
elif isinstance(self.datatype, DatatypeNode):
assert self.size is None
self.size = self.datatype.dimensions
self.size = self.datatype.dimensions or [1]
self.datatype = self.datatype.to_enum()
# if the value is an expression, mark it as a *constant* expression here
if isinstance(self.value, Expression):
self.value.processed_must_be_constant = True
elif self.value is None and self.datatype in (DataType.BYTE, DataType.WORD, DataType.FLOAT):
self.value = 0
# note: value coercion is done later, when all expressions are evaluated
def process_expressions(self) -> None:
if isinstance(self.value, Expression):
# process/simplify all expressions (constant folding etc) # @todo
# verify that the expression yields a single constant value, replace value by that value # @todo
self.value = 123 # XXX
assert not isinstance(self.value, Expression)
if self.vartype in (VarType.CONST, VarType.VAR):
try:
_, self.value = coerce_value(self.datatype, self.value, self.sourceref)
except OverflowError as x:
raise ParseError(str(x), self.sourceref) from None
@attr.s(cmp=False, slots=True, repr=False)
@ -359,8 +383,8 @@ class DatatypeNode(AstNode):
@attr.s(cmp=False, repr=False)
class Subroutine(AstNode):
name = attr.ib(type=str)
param_spec = attr.ib()
result_spec = attr.ib()
param_spec = attr.ib(type=list)
result_spec = attr.ib(type=list)
scope = attr.ib(type=Scope, default=None)
address = attr.ib(type=int, default=None, validator=validate_address)
@ -638,9 +662,9 @@ def p_subroutine(p):
"""
body = p[10]
if isinstance(body, Scope):
p[0] = Subroutine(name=p[2], param_spec=p[4], result_spec=p[8], scope=body, sourceref=_token_sref(p, 1))
p[0] = Subroutine(name=p[2], param_spec=p[4] or [], result_spec=p[8] or [], scope=body, sourceref=_token_sref(p, 1))
elif isinstance(body, int):
p[0] = Subroutine(name=p[2], param_spec=p[4], result_spec=p[8], address=body, sourceref=_token_sref(p, 1))
p[0] = Subroutine(name=p[2], param_spec=p[4] or [], result_spec=p[8] or [], address=body, sourceref=_token_sref(p, 1))
else:
raise TypeError("subroutine_body", p.slice)

View File

@ -1,38 +0,0 @@
"""
Programming Language for 6502/6510 microprocessors, codename 'Sick'
Here are the symbol (name) operations such as lookups, datatype definitions.
Written by Irmen de Jong (irmen@razorvine.net) - license: GNU GPL 3.0
"""
import enum
class DataType(enum.Enum):
"""The possible data types of values"""
BYTE = 1
WORD = 2
FLOAT = 3
BYTEARRAY = 4
WORDARRAY = 5
MATRIX = 6
STRING = 7
STRING_P = 8
STRING_S = 9
STRING_PS = 10
STRING_DATATYPES = {DataType.STRING, DataType.STRING_P, DataType.STRING_S, DataType.STRING_PS}
def to_hex(number: int) -> str:
# 0..255 -> "$00".."$ff"
# 256..65536 -> "$0100".."$ffff"
if number is None:
raise ValueError("number")
if 0 <= number < 0x100:
return "${:02x}".format(number)
if 0 <= number < 0x10000:
return "${:04x}".format(number)
raise OverflowError(number)

View File

@ -1,11 +1,11 @@
import pytest
from il65.symbols import DataType, STRING_DATATYPES, to_hex
from il65 import datatypes
from il65.compile import ParseError
from il65.plylex import SourceRef
def test_datatypes():
assert all(isinstance(s, DataType) for s in STRING_DATATYPES)
assert all(isinstance(s, datatypes.DataType) for s in datatypes.STRING_DATATYPES)
def test_sourceref():
@ -22,13 +22,93 @@ def test_parseerror():
def test_to_hex():
assert to_hex(0) == "$00"
assert to_hex(1) == "$01"
assert to_hex(255) == "$ff"
assert to_hex(256) == "$0100"
assert to_hex(20060) == "$4e5c"
assert to_hex(65535) == "$ffff"
assert datatypes.to_hex(0) == "$00"
assert datatypes.to_hex(1) == "$01"
assert datatypes.to_hex(255) == "$ff"
assert datatypes.to_hex(256) == "$0100"
assert datatypes.to_hex(20060) == "$4e5c"
assert datatypes.to_hex(65535) == "$ffff"
with pytest.raises(OverflowError):
to_hex(-1)
datatypes.to_hex(-1)
with pytest.raises(OverflowError):
to_hex(65536)
datatypes.to_hex(65536)
def test_float_to_mflpt5():
mflpt = datatypes.to_mflpt5(1.0)
assert type(mflpt) is bytearray
assert b"\x00\x00\x00\x00\x00" == datatypes.to_mflpt5(0)
assert b"\x82\x49\x0F\xDA\xA1" == datatypes.to_mflpt5(3.141592653)
assert b"\x82\x49\x0F\xDA\xA2" == datatypes.to_mflpt5(3.141592653589793)
assert b"\x90\x80\x00\x00\x00" == datatypes.to_mflpt5(-32768)
assert b"\x81\x00\x00\x00\x00" == datatypes.to_mflpt5(1)
assert b"\x80\x35\x04\xF3\x34" == datatypes.to_mflpt5(0.7071067812)
assert b"\x80\x35\x04\xF3\x33" == datatypes.to_mflpt5(0.7071067811865476)
assert b"\x81\x35\x04\xF3\x34" == datatypes.to_mflpt5(1.4142135624)
assert b"\x81\x35\x04\xF3\x33" == datatypes.to_mflpt5(1.4142135623730951)
assert b"\x80\x80\x00\x00\x00" == datatypes.to_mflpt5(-.5)
assert b"\x80\x31\x72\x17\xF8" == datatypes.to_mflpt5(0.69314718061)
assert b"\x80\x31\x72\x17\xF7" == datatypes.to_mflpt5(0.6931471805599453)
assert b"\x84\x20\x00\x00\x00" == datatypes.to_mflpt5(10)
assert b"\x9E\x6E\x6B\x28\x00" == datatypes.to_mflpt5(1000000000)
assert b"\x80\x00\x00\x00\x00" == datatypes.to_mflpt5(.5)
assert b"\x81\x38\xAA\x3B\x29" == datatypes.to_mflpt5(1.4426950408889634)
assert b"\x81\x49\x0F\xDA\xA2" == datatypes.to_mflpt5(1.5707963267948966)
assert b"\x83\x49\x0F\xDA\xA2" == datatypes.to_mflpt5(6.283185307179586)
assert b"\x7F\x00\x00\x00\x00" == datatypes.to_mflpt5(.25)
def test_float_range():
assert b"\xff\x7f\xff\xff\xff" == datatypes.to_mflpt5(datatypes.FLOAT_MAX_POSITIVE)
assert b"\xff\xff\xff\xff\xff" == datatypes.to_mflpt5(datatypes.FLOAT_MAX_NEGATIVE)
with pytest.raises(OverflowError):
datatypes.to_mflpt5(1.7014118346e+38)
with pytest.raises(OverflowError):
datatypes.to_mflpt5(-1.7014118346e+38)
with pytest.raises(OverflowError):
datatypes.to_mflpt5(1.7014118347e+38)
with pytest.raises(OverflowError):
datatypes.to_mflpt5(-1.7014118347e+38)
assert b"\x03\x39\x1d\x15\x63" == datatypes.to_mflpt5(1.7e-38)
assert b"\x00\x00\x00\x00\x00" == datatypes.to_mflpt5(1.7e-39)
assert b"\x03\xb9\x1d\x15\x63" == datatypes.to_mflpt5(-1.7e-38)
assert b"\x00\x00\x00\x00\x00" == datatypes.to_mflpt5(-1.7e-39)
def test_char_to_bytevalue():
assert datatypes.char_to_bytevalue('a') == 65
assert datatypes.char_to_bytevalue('\n') == 13
assert datatypes.char_to_bytevalue('π') == 126
assert datatypes.char_to_bytevalue('') == 230
assert datatypes.char_to_bytevalue('\x00') == 0
assert datatypes.char_to_bytevalue('\xff') == 255
with pytest.raises(AssertionError):
datatypes.char_to_bytevalue('<undefined>')
# screencodes not yet implemented: assert datatypes.char_to_bytevalue('a', False) == 65
def test_coerce_value():
assert datatypes.coerce_value(datatypes.DataType.BYTE, 0) == (False, 0)
assert datatypes.coerce_value(datatypes.DataType.BYTE, 255) == (False, 255)
assert datatypes.coerce_value(datatypes.DataType.WORD, 0) == (False, 0)
assert datatypes.coerce_value(datatypes.DataType.WORD, 65535) == (False, 65535)
assert datatypes.coerce_value(datatypes.DataType.FLOAT, -999.22) == (False, -999.22)
assert datatypes.coerce_value(datatypes.DataType.FLOAT, 123.45) == (False, 123.45)
assert datatypes.coerce_value(datatypes.DataType.BYTE, 5.678) == (True, 5)
assert datatypes.coerce_value(datatypes.DataType.WORD, 5.678) == (True, 5)
with pytest.raises(OverflowError):
datatypes.coerce_value(datatypes.DataType.BYTE, -1)
with pytest.raises(OverflowError):
datatypes.coerce_value(datatypes.DataType.BYTE, 256)
with pytest.raises(OverflowError):
datatypes.coerce_value(datatypes.DataType.BYTE, 256.12345)
with pytest.raises(OverflowError):
datatypes.coerce_value(datatypes.DataType.WORD, -1)
with pytest.raises(OverflowError):
datatypes.coerce_value(datatypes.DataType.WORD, 65536)
with pytest.raises(OverflowError):
datatypes.coerce_value(datatypes.DataType.WORD, 65536.12345)
with pytest.raises(OverflowError):
datatypes.coerce_value(datatypes.DataType.FLOAT, -1.7014118346e+38)
with pytest.raises(OverflowError):
datatypes.coerce_value(datatypes.DataType.FLOAT, 1.7014118347e+38)

View File

@ -1,45 +0,0 @@
import pytest
from il65.handwritten import symbols, codegen
def test_float_to_mflpt5():
mflpt = codegen.CodeGenerator.to_mflpt5(1.0)
assert type(mflpt) is bytearray
assert b"\x00\x00\x00\x00\x00" == codegen.CodeGenerator.to_mflpt5(0)
assert b"\x82\x49\x0F\xDA\xA1" == codegen.CodeGenerator.to_mflpt5(3.141592653)
assert b"\x82\x49\x0F\xDA\xA2" == codegen.CodeGenerator.to_mflpt5(3.141592653589793)
assert b"\x90\x80\x00\x00\x00" == codegen.CodeGenerator.to_mflpt5(-32768)
assert b"\x81\x00\x00\x00\x00" == codegen.CodeGenerator.to_mflpt5(1)
assert b"\x80\x35\x04\xF3\x34" == codegen.CodeGenerator.to_mflpt5(0.7071067812)
assert b"\x80\x35\x04\xF3\x33" == codegen.CodeGenerator.to_mflpt5(0.7071067811865476)
assert b"\x81\x35\x04\xF3\x34" == codegen.CodeGenerator.to_mflpt5(1.4142135624)
assert b"\x81\x35\x04\xF3\x33" == codegen.CodeGenerator.to_mflpt5(1.4142135623730951)
assert b"\x80\x80\x00\x00\x00" == codegen.CodeGenerator.to_mflpt5(-.5)
assert b"\x80\x31\x72\x17\xF8" == codegen.CodeGenerator.to_mflpt5(0.69314718061)
assert b"\x80\x31\x72\x17\xF7" == codegen.CodeGenerator.to_mflpt5(0.6931471805599453)
assert b"\x84\x20\x00\x00\x00" == codegen.CodeGenerator.to_mflpt5(10)
assert b"\x9E\x6E\x6B\x28\x00" == codegen.CodeGenerator.to_mflpt5(1000000000)
assert b"\x80\x00\x00\x00\x00" == codegen.CodeGenerator.to_mflpt5(.5)
assert b"\x81\x38\xAA\x3B\x29" == codegen.CodeGenerator.to_mflpt5(1.4426950408889634)
assert b"\x81\x49\x0F\xDA\xA2" == codegen.CodeGenerator.to_mflpt5(1.5707963267948966)
assert b"\x83\x49\x0F\xDA\xA2" == codegen.CodeGenerator.to_mflpt5(6.283185307179586)
assert b"\x7F\x00\x00\x00\x00" == codegen.CodeGenerator.to_mflpt5(.25)
def test_float_range():
assert b"\xff\x7f\xff\xff\xff" == codegen.CodeGenerator.to_mflpt5(symbols.FLOAT_MAX_POSITIVE)
assert b"\xff\xff\xff\xff\xff" == codegen.CodeGenerator.to_mflpt5(symbols.FLOAT_MAX_NEGATIVE)
with pytest.raises(OverflowError):
codegen.CodeGenerator.to_mflpt5(1.7014118346e+38)
with pytest.raises(OverflowError):
codegen.CodeGenerator.to_mflpt5(-1.7014118346e+38)
with pytest.raises(OverflowError):
codegen.CodeGenerator.to_mflpt5(1.7014118347e+38)
with pytest.raises(OverflowError):
codegen.CodeGenerator.to_mflpt5(-1.7014118347e+38)
assert b"\x03\x39\x1d\x15\x63" == codegen.CodeGenerator.to_mflpt5(1.7e-38)
assert b"\x00\x00\x00\x00\x00" == codegen.CodeGenerator.to_mflpt5(1.7e-39)
assert b"\x03\xb9\x1d\x15\x63" == codegen.CodeGenerator.to_mflpt5(-1.7e-38)
assert b"\x00\x00\x00\x00\x00" == codegen.CodeGenerator.to_mflpt5(-1.7e-39)

View File

@ -1,5 +1,5 @@
import pytest
from il65.handwritten.symbols import Zeropage, SymbolError, DataType
from il65.handwritten.symbols import Zeropage, SymbolError, DataType # @todo
def test_zp_configure_onlyonce():

View File

@ -1,19 +1,18 @@
%output prg
%output basic
%saveregisters
%import c64lib
%import mathlib
%address 22222
~ main $4444 {
~ main {
%saveregisters true
const num = 2
var var1 =2
var .word wvar1 = 2 + foo() ; @todo constant
var .word wvar1 = 2 + foo() ; @todo constant check error
start: