Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ build-backend = "poetry.masonry.api"

[tool.black]
line-length = 120
target-version = ['py38']
target-version = ['py310']

[tool.poe.tasks]
[[tool.poe.tasks.lint]]
help = "Check code style and typing..."
shell = """
flake8 src tests &&
isort --check stc tests
isort --check stc tests &&
black --check src tests
"""

Expand Down
2 changes: 1 addition & 1 deletion src/arch/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

def set_target_arch(target_arch: str):
global target
assert target_arch in AVAILABLE_ARCHITECTURES
assert target_arch in AVAILABLE_ARCHITECTURES, f"Invalid target architecture '{target_arch}'"
target = importlib.import_module(f".{target_arch}", "src.arch")
__DEBUG__(f"Target architecture set to {target_arch}")

Expand Down
9 changes: 5 additions & 4 deletions src/arch/z80/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
import src.api.global_
from src.api.constants import TYPE
from src.arch.z80 import beep
from src.arch.z80.translator import * # noqa

__all__ = [
"beep",
]
from .visitor.function_translator import FunctionTranslator
from .visitor.translator import Translator
from .visitor.var_translator import VarTranslator

__all__ = "beep", "FunctionTranslator", "Translator", "VarTranslator"


# -----------------------------------------
Expand Down
2 changes: 2 additions & 0 deletions src/arch/z80/backend/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,6 @@
"TMP_STORAGES",
"Backend",
"engine",
"Quad",
"ICInfo",
)
1 change: 1 addition & 0 deletions src/arch/z80/backend/icinstruction.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

__all__ = ("ICInstruction",)


# HINT: Do not use Enums here. They cannot be subclassed
class ICInstruction:
ADDU8 = "addu8"
Expand Down
1 change: 0 additions & 1 deletion src/arch/z80/backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import re
from collections import defaultdict
from typing import Iterable

from src.api.config import OPTIONS
from src.api.options import Action
Expand Down
Empty file.
157 changes: 157 additions & 0 deletions src/arch/z80/visitor/builtin_translator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
from src.api import global_ as gl
from src.api.constants import SCOPE, TYPE
from src.api.global_ import optemps
from src.arch.z80 import backend
from src.arch.z80.backend.runtime import Labels as RuntimeLabel
from src.arch.z80.visitor.translator_visitor import TranslatorVisitor
from src.symbols.type_ import Type


class BuiltinTranslator(TranslatorVisitor):
"""BUILTIN functions visitor. Eg. LEN(a$) or SIN(x)"""

REQUIRES = backend.REQUIRES

# region STRING Functions
def visit_INKEY(self, node):
self.runtime_call(RuntimeLabel.INKEY, Type.string.size)

def visit_IN(self, node):
self.ic_in(node.children[0].t)

def visit_CODE(self, node):
self.ic_fparam(gl.PTR_TYPE, node.operand.t)
if node.operand.token not in ("STRING", "VAR") and node.operand.t != "_":
self.ic_fparam(TYPE.ubyte, 1) # If the argument is not a variable, it must be freed
else:
self.ic_fparam(TYPE.ubyte, 0)

self.runtime_call(RuntimeLabel.ASC, Type.ubyte.size) # Expect a char code

def visit_CHR(self, node):
self.ic_fparam(gl.STR_INDEX_TYPE, len(node.operand)) # Number of args
self.runtime_call(RuntimeLabel.CHR, node.size)

def visit_STR(self, node):
self.ic_fparam(TYPE.float, node.children[0].t)
self.runtime_call(RuntimeLabel.STR_FAST, node.type_.size)

def visit_LEN(self, node):
self.ic_lenstr(node.t, node.operand.t)

def visit_VAL(self, node):
self.ic_fparam(gl.PTR_TYPE, node.operand.t)
if node.operand.token not in ("STRING", "VAR") and node.operand.t != "_":
self.ic_fparam(TYPE.ubyte, 1) # If the argument is not a variable, it must be freed
else:
self.ic_fparam(TYPE.ubyte, 0)

self.runtime_call(RuntimeLabel.VAL, node.type_.size)

# endregion

def visit_ABS(self, node):
self.ic_abs(node.children[0].type_, node.t, node.children[0].t)

def visit_RND(self, node): # A special "ZEROARY" function with no parameters
self.runtime_call(RuntimeLabel.RND, Type.float_.size)

def visit_PEEK(self, node):
self.ic_load(node.type_, node.t, "*" + str(node.children[0].t))

# region MATH Functions
def visit_SIN(self, node):
self.ic_fparam(node.operand.type_, node.operand.t)
self.runtime_call(RuntimeLabel.SIN, node.size)

def visit_COS(self, node):
self.ic_fparam(node.operand.type_, node.operand.t)
self.runtime_call(RuntimeLabel.COS, node.size)

def visit_TAN(self, node):
self.ic_fparam(node.operand.type_, node.operand.t)
self.runtime_call(RuntimeLabel.TAN, node.size)

def visit_ASN(self, node):
self.ic_fparam(node.operand.type_, node.operand.t)
self.runtime_call(RuntimeLabel.ASN, node.size)

def visit_ACS(self, node):
self.ic_fparam(node.operand.type_, node.operand.t)
self.runtime_call(RuntimeLabel.ACS, node.size)

def visit_ATN(self, node):
self.ic_fparam(node.operand.type_, node.operand.t)
self.runtime_call(RuntimeLabel.ATN, node.size)

def visit_EXP(self, node):
self.ic_fparam(node.operand.type_, node.operand.t)
self.runtime_call(RuntimeLabel.EXP, node.size)

def visit_LN(self, node):
self.ic_fparam(node.operand.type_, node.operand.t)
self.runtime_call(RuntimeLabel.LN, node.size)

def visit_SGN(self, node):
s = self.TSUFFIX(node.operand.type_)
self.ic_fparam(node.operand.type_, node.operand.t)

label = {
"i8": RuntimeLabel.SGNI8,
"u8": RuntimeLabel.SGNU8,
"i16": RuntimeLabel.SGNI16,
"u16": RuntimeLabel.SGNU16,
"i32": RuntimeLabel.SGNI32,
"u32": RuntimeLabel.SGNU32,
"f16": RuntimeLabel.SGNF16,
"f": RuntimeLabel.SGNF,
}[s]
self.runtime_call(label, node.size)

def visit_SQR(self, node):
self.ic_fparam(node.operand.type_, node.operand.t)
self.runtime_call(RuntimeLabel.SQR, node.size)

# endregion

def visit_LBOUND(self, node):
yield node.operands[1]
self.ic_param(gl.BOUND_TYPE, node.operands[1].t)
entry = node.operands[0]
if entry.scope == SCOPE.global_:
self.ic_fparam(gl.PTR_TYPE, "#{}".format(entry.mangled))
elif entry.scope == SCOPE.parameter:
self.ic_pload(gl.PTR_TYPE, entry.t, entry.offset)
t1 = optemps.new_t()
self.ic_fparam(gl.PTR_TYPE, t1)
elif entry.scope == SCOPE.local:
self.ic_paddr(-entry.offset, entry.t)
t1 = optemps.new_t()
self.ic_fparam(gl.PTR_TYPE, t1)
self.runtime_call(RuntimeLabel.LBOUND, self.TYPE(gl.BOUND_TYPE).size)

def visit_UBOUND(self, node):
yield node.operands[1]
self.ic_param(gl.BOUND_TYPE, node.operands[1].t)
entry = node.operands[0]
if entry.scope == SCOPE.global_:
self.ic_fparam(gl.PTR_TYPE, "#{}".format(entry.mangled))
elif entry.scope == SCOPE.parameter:
self.ic_pload(gl.PTR_TYPE, entry.t, entry.offset)
t1 = optemps.new_t()
self.ic_fparam(gl.PTR_TYPE, t1)
elif entry.scope == SCOPE.local:
self.ic_paddr(-entry.offset, entry.t)
t1 = optemps.new_t()
self.ic_fparam(gl.PTR_TYPE, t1)
self.runtime_call(RuntimeLabel.UBOUND, self.TYPE(gl.BOUND_TYPE).size)

def visit_USR_STR(self, node):
# USR ADDR
self.ic_fparam(TYPE.string, node.children[0].t)
self.runtime_call(RuntimeLabel.USR_STR, node.type_.size)

def visit_USR(self, node):
"""Machine code call from basic"""
self.ic_fparam(gl.PTR_TYPE, node.children[0].t)
self.runtime_call(RuntimeLabel.USR, node.type_.size)
167 changes: 167 additions & 0 deletions src/arch/z80/visitor/function_translator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import src.api
from src.api import global_ as gl
from src.api.config import OPTIONS
from src.api.constants import CLASS, CONVENTION, SCOPE, TYPE
from src.api.debug import __DEBUG__
from src.api.global_ import optemps
from src.arch.z80 import backend
from src.arch.z80.backend.runtime import Labels as RuntimeLabel
from src.arch.z80.visitor.translator import LabelledData
from src.arch.zx48k.backend import Backend
from src.symbols import sym as symbols

from .translator import Translator


class FunctionTranslator(Translator):
REQUIRES = backend.REQUIRES

def __init__(self, backend: Backend, function_list: list[symbols.ID]):
if function_list is None:
function_list = []
super().__init__(backend)

assert isinstance(function_list, list)
assert all(x.token == "FUNCTION" for x in function_list)
self.functions = function_list

def _local_array_load(self, scope, local_var):
t2 = optemps.new_t()
if scope == SCOPE.parameter:
self.ic_pload(gl.PTR_TYPE, t2, "%i" % (local_var.offset - self.TYPE(gl.PTR_TYPE).size))
elif scope == SCOPE.local:
self.ic_pload(gl.PTR_TYPE, t2, "%i" % -(local_var.offset - self.TYPE(gl.PTR_TYPE).size))
self.ic_fparam(gl.PTR_TYPE, t2)

def start(self):
while self.functions:
f = self.functions.pop(0)
__DEBUG__("Translating function " + f.__repr__())
self.visit(f)

def visit_FUNCTION(self, node):
bound_tables = []

self.ic_label(node.mangled)
if node.convention == CONVENTION.fastcall:
self.ic_enter("__fastcall__")
else:
self.ic_enter(node.locals_size)

for local_var in node.local_symbol_table.values():
if not local_var.accessed: # HINT: This should never happen as values() is already filtered
src.api.errmsg.warning_not_used(local_var.lineno, local_var.name)
# HINT: Cannot optimize local variables now, since the offsets are already calculated
# if self.O_LEVEL > 1:
# return

if local_var.class_ == CLASS.array and local_var.scope == SCOPE.local:
bound_ptrs = [] # Bound tables pointers (empty if not used)
lbound_label = local_var.mangled + ".__LBOUND__"
ubound_label = local_var.mangled + ".__UBOUND__"

if local_var.lbound_used or local_var.ubound_used:
bound_ptrs = ["0", "0"] # NULL by default
if local_var.lbound_used:
bound_ptrs[0] = lbound_label
if local_var.ubound_used:
bound_ptrs[1] = ubound_label

if bound_ptrs:
OPTIONS["__DEFINES"].value["__ZXB_USE_LOCAL_ARRAY_WITH_BOUNDS__"] = ""

if local_var.lbound_used:
l = ["%04X" % bound.lower for bound in local_var.bounds]
bound_tables.append(LabelledData(lbound_label, l))

if local_var.ubound_used:
l = ["%04X" % bound.upper for bound in local_var.bounds]
bound_tables.append(LabelledData(ubound_label, l))

l = [len(local_var.bounds) - 1] + [x.count for x in local_var.bounds[1:]] # TODO Check this
q = []
for x in l:
q.append("%02X" % (x & 0xFF))
q.append("%02X" % ((x & 0xFF) >> 8))

q.append("%02X" % local_var.type_.size)
r = []
if local_var.default_value is not None:
r.extend(self.array_default_value(local_var.type_, local_var.default_value))
self.ic_larrd(local_var.offset, q, local_var.size, r, bound_ptrs) # Initializes array bounds
elif local_var.class_ == CLASS.const or local_var.scope == SCOPE.parameter:
continue
else: # Local vars always defaults to 0, so if 0 we do nothing
if (
local_var.token != "FUNCTION"
and local_var.default_value is not None
and local_var.default_value != 0
):
if (
isinstance(local_var.default_value, symbols.CONSTEXPR)
and local_var.default_value.token == "CONSTEXPR"
):
self.ic_lvarx(local_var.type_, local_var.offset, [self.traverse_const(local_var.default_value)])
else:
q = self.default_value(local_var.type_, local_var.default_value)
self.ic_lvard(local_var.offset, q)

for i in node.ref.body:
yield i

self.ic_label("%s__leave" % node.mangled)

# Now free any local string from memory.
preserve_hl = False
if node.convention == CONVENTION.stdcall:
for local_var in node.local_symbol_table.values():
scope = local_var.scope
if local_var.type_ == self.TYPE(TYPE.string):
if local_var.class_ == CLASS.const:
continue
# Only if it's string we free it
if local_var.class_ != CLASS.array: # Ok just free it
if scope == SCOPE.local or (scope == SCOPE.parameter and not local_var.byref):
if not preserve_hl:
preserve_hl = True
self.ic_exchg()

offset = -local_var.offset if scope == SCOPE.local else local_var.offset
self.ic_fpload(TYPE.string, local_var.t, offset)
self.runtime_call(RuntimeLabel.MEM_FREE, 0)
else: # This is an array of strings, we must free it unless it's a by_ref array
if scope == SCOPE.local or (scope == SCOPE.parameter and not local_var.byref):
if not preserve_hl:
preserve_hl = True
self.ic_exchg()

self.ic_param(gl.BOUND_TYPE, local_var.count)
self._local_array_load(scope, local_var)
self.runtime_call(RuntimeLabel.ARRAYSTR_FREE_MEM, 0)

if (
local_var.class_ == CLASS.array
and local_var.type_ != self.TYPE(TYPE.string)
and (scope == SCOPE.local or (scope == SCOPE.parameter and not local_var.byref))
):
if not preserve_hl:
preserve_hl = True
self.ic_exchg()

self._local_array_load(scope, local_var)
self.runtime_call(RuntimeLabel.MEM_FREE, 0)

if preserve_hl:
self.ic_exchg()

if node.convention == CONVENTION.fastcall:
self.ic_leave(CONVENTION.to_string(node.convention))
else:
self.ic_leave(node.ref.params.size)

for bound_table in bound_tables:
self.ic_vard(bound_table.label, bound_table.data)

def visit_FUNCDECL(self, node):
"""Nested scope functions"""
self.functions.append(node.entry)
Loading