diff --git a/src/api/optimize.py b/src/api/optimize.py
index ff744fa7d..41bc2442f 100644
--- a/src/api/optimize.py
+++ b/src/api/optimize.py
@@ -6,7 +6,7 @@
# --------------------------------------------------------------------
import symtable
-from collections.abc import Generator
+from collections.abc import Callable, Generator
from typing import Any, NamedTuple
import src.api.check as chk
@@ -17,28 +17,17 @@
from src.api import errmsg
from src.api.config import OPTIONS
from src.api.constants import CLASS, CONVENTION, SCOPE, TYPE
-from src.api.debug import __DEBUG__
from src.api.errmsg import warning_not_used
from src.ast import Ast, NodeVisitor
from src.symbols import sym as symbols
from src.symbols.id_ import ref
-class ToVisit(NamedTuple):
- """Used just to signal an object to be
- traversed.
- """
-
- obj: symbols.SYMBOL
-
-
class GenericVisitor(NodeVisitor):
"""A slightly different visitor, that just traverses an AST, but does not return
a translation of it. Used to examine the AST or do transformations
"""
- node_type = ToVisit
-
@property
def O_LEVEL(self):
return OPTIONS.optimization_level
@@ -58,36 +47,42 @@ def TYPE(type_):
assert TYPE.is_valid(type_)
return gl.SYMBOL_TABLE.basic_types[type_]
- def visit(self, node):
- return super().visit(ToVisit(node))
-
- def _visit(self, node: ToVisit):
- if node.obj is None:
- return None
-
- __DEBUG__(f"Optimizer: Visiting node {node.obj!s}[{node.obj.token}]", 1)
- meth = getattr(self, f"visit_{node.obj.token}", self.generic_visit)
- return meth(node.obj)
-
- def generic_visit(self, node: Ast) -> Generator[Ast | None, Any, None]:
- for i, child in enumerate(node.children):
- node.children[i] = yield self.visit(child)
-
- yield node
-
class UniqueVisitor(GenericVisitor):
def __init__(self):
super().__init__()
self.visited = set()
- def _visit(self, node: ToVisit):
- if node.obj in self.visited:
- return node.obj
+ def _visit(self, node: Ast):
+ if node in self.visited:
+ return node
- self.visited.add(node.obj)
+ self.visited.add(node)
return super()._visit(node)
+ def filter_inorder(
+ self,
+ node,
+ filter_func: Callable[[Any], bool],
+ child_selector: Callable[[Ast], bool] = lambda x: True,
+ ) -> Generator[Ast, None, None]:
+ """Visit the tree inorder, but only those that return true for filter_func and visiting children which
+ return true for child_selector.
+ """
+ visited = set()
+ stack = [node]
+ while stack:
+ node = stack.pop()
+ if node in visited:
+ continue
+
+ visited.add(node)
+ if filter_func(node):
+ yield self.visit(node)
+
+ if isinstance(node, Ast) and child_selector(node):
+ stack.extend(node.children[::-1])
+
class UnreachableCodeVisitor(UniqueVisitor):
"""Visitor to optimize unreachable code (and prune it)."""
@@ -107,7 +102,7 @@ def visit_FUNCTION(self, node: symbols.ID):
if type_ is not None and type_ == self.TYPE(TYPE.string):
node.body.append(symbols.ASM("\nld hl, 0\n", lineno, node.filename, is_sentinel=True))
- yield (yield self.generic_visit(node))
+ yield self.generic_visit(node)
def visit_BLOCK(self, node):
# Remove CHKBREAK after labels
@@ -155,7 +150,7 @@ def visit_BLOCK(self, node):
yield self.NOP
return
- yield (yield self.generic_visit(node))
+ yield self.generic_visit(node)
class FunctionGraphVisitor(UniqueVisitor):
@@ -165,6 +160,7 @@ def _get_calls_from_children(self, node: symtable.Symbol):
return list(self.filter_inorder(node, lambda x: x.token in ("CALL", "FUNCCALL")))
def _set_children_as_accessed(self, node: symbols.SYMBOL):
+ """ "Traverse only those"""
parent = node.get_parent(symbols.FUNCDECL)
if parent is None: # Global scope?
for symbol in self._get_calls_from_children(node):
@@ -314,7 +310,7 @@ def visit_FUNCDECL(self, node):
if self.O_LEVEL > 1 and node.params_size == node.locals_size == 0:
node.entry.ref.convention = CONVENTION.fastcall
- node.children[1] = yield ToVisit(node.entry)
+ node.children[1] = yield self.visit(node.entry)
yield node
def visit_LET(self, node):
@@ -370,19 +366,20 @@ def visit_RETURN(self, node):
might cause infinite recursion.
"""
if len(node.children) == 2:
- node.children[1] = yield ToVisit(node.children[1])
+ node.children[1] = yield self.visit(node.children[1])
+
yield node
def visit_UNARY(self, node):
if node.operator == "ADDRESS":
- yield (yield self.visit_ADDRESS(node))
+ yield self.visit_ADDRESS(node)
else:
- yield (yield self.generic_visit(node))
+ yield self.generic_visit(node)
def visit_IF(self, node):
- expr_ = yield ToVisit(node.children[0])
- then_ = yield ToVisit(node.children[1])
- else_ = (yield ToVisit(node.children[2])) if len(node.children) == 3 else self.NOP
+ expr_ = yield self.visit(node.children[0])
+ then_ = yield self.visit(node.children[1])
+ else_ = (yield self.visit(node.children[2])) if len(node.children) == 3 else self.NOP
if self.O_LEVEL >= 1:
if chk.is_null(then_, else_):
@@ -405,6 +402,7 @@ def visit_IF(self, node):
for i in range(len(node.children)):
node.children[i] = (expr_, then_, else_)[i]
+
yield node
def visit_WHILE(self, node):
@@ -419,6 +417,7 @@ def visit_WHILE(self, node):
for i, child in enumerate((expr_, body_)):
node.children[i] = child
+
yield node
def visit_FOR(self, node):
@@ -433,6 +432,7 @@ def visit_FOR(self, node):
if from_.value > to_.value and step_.value > 0:
yield self.NOP
return
+
if from_.value < to_.value and step_.value < 0:
yield self.NOP
return
@@ -446,12 +446,6 @@ def _visit_LABEL(self, node):
else:
yield node
- def generic_visit(self, node: Ast):
- for i, child in enumerate(node.children):
- node.children[i] = yield ToVisit(child)
-
- yield node
-
def _check_if_any_arg_is_an_array_and_needs_lbound_or_ubound(
self, params: symbols.PARAMLIST, args: symbols.ARGLIST
):
@@ -502,10 +496,7 @@ class VariableVisitor(GenericVisitor):
def generic_visit(self, node: Ast):
if node not in VariableVisitor._visited:
VariableVisitor._visited.add(node)
- for i in range(len(node.children)):
- node.children[i] = yield ToVisit(node.children[i])
-
- yield node
+ yield super().generic_visit(node)
def has_circular_dependency(self, var_dependency: VarDependency) -> bool:
if var_dependency.dependency == VariableVisitor._original_variable:
@@ -532,7 +523,7 @@ def visit_var(entry):
if entry.token != "VAR":
for child in entry.children:
visit_var(child)
- if child.token in ("FUNCTION", "LABEL", "VAR", "VARARRAY"):
+ if child.token in {"FUNCTION", "LABEL", "VAR", "VARARRAY"}:
result.add(VarDependency(parent=VariableVisitor._parent_variable, dependency=child))
return
diff --git a/src/arch/z80/visitor/builtin_translator.py b/src/arch/z80/visitor/builtin_translator.py
index ed8da017b..85e47f0d9 100644
--- a/src/arch/z80/visitor/builtin_translator.py
+++ b/src/arch/z80/visitor/builtin_translator.py
@@ -19,6 +19,13 @@ class BuiltinTranslator(TranslatorVisitor):
REQUIRES = backend.REQUIRES
+ def __init__(self, backend: backend.Backend, parent_visitor: TranslatorVisitor):
+ super().__init__(backend)
+ self.parent_visitor = parent_visitor
+
+ def visit(self, node):
+ return self.parent_visitor.visit(node)
+
# region STRING Functions
def visit_INKEY(self, node):
self.runtime_call(RuntimeLabel.INKEY, Type.string.size)
@@ -125,7 +132,7 @@ def visit_SQR(self, node):
# endregion
def visit_LBOUND(self, node):
- yield node.operands[1]
+ yield self.visit(node.operands[1])
self.ic_param(gl.BOUND_TYPE, node.operands[1].t)
entry = node.operands[0]
if entry.scope == SCOPE.global_:
@@ -141,7 +148,7 @@ def visit_LBOUND(self, node):
self.runtime_call(RuntimeLabel.LBOUND, self.TYPE(gl.BOUND_TYPE).size)
def visit_UBOUND(self, node):
- yield node.operands[1]
+ yield self.visit(node.operands[1])
self.ic_param(gl.BOUND_TYPE, node.operands[1].t)
entry = node.operands[0]
if entry.scope == SCOPE.global_:
diff --git a/src/arch/z80/visitor/function_translator.py b/src/arch/z80/visitor/function_translator.py
index 143a77d4e..fc790f522 100644
--- a/src/arch/z80/visitor/function_translator.py
+++ b/src/arch/z80/visitor/function_translator.py
@@ -24,9 +24,9 @@ class FunctionTranslator(Translator):
REQUIRES = backend.REQUIRES
def __init__(self, backend: Backend, function_list: list[symbols.ID]):
+ super().__init__(backend)
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)
@@ -115,7 +115,7 @@ def visit_FUNCTION(self, node):
self.ic_lvard(local_var.offset, q)
for i in node.ref.body:
- yield i
+ yield self.visit(i)
self.ic_label("%s__leave" % node.mangled)
diff --git a/src/arch/z80/visitor/translator.py b/src/arch/z80/visitor/translator.py
index e027a57f8..41841be81 100644
--- a/src/arch/z80/visitor/translator.py
+++ b/src/arch/z80/visitor/translator.py
@@ -55,27 +55,27 @@ def visit_CLS(self, node):
def visit_NUMBER(self, node):
__DEBUG__("NUMBER " + str(node))
- yield node.value
+ yield node
def visit_STRING(self, node):
__DEBUG__("STRING " + str(node))
node.t = "#" + self.add_string_label(node.value)
- yield node.t
+ yield node
def visit_END(self, node):
- yield node.children[0]
+ yield self.visit(node.children[0])
__DEBUG__("END")
self.ic_end(node.children[0].t)
def visit_ERROR(self, node):
# Raises an error
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_fparam(TYPE.ubyte, node.children[0].t)
self.runtime_call(RuntimeLabel.ERROR, 0)
def visit_STOP(self, node):
"""Returns to BASIC with an error code"""
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_fparam(TYPE.ubyte, node.children[0].t)
self.runtime_call(RuntimeLabel.STOP, 0)
self.ic_end(0)
@@ -83,15 +83,16 @@ def visit_STOP(self, node):
def visit_LET(self, node):
assert node.children[0].token == "VAR"
if self.O_LEVEL < 2 or node.children[0].accessed or node.children[1].token == "CONSTEXPR":
- yield node.children[1]
+ yield self.visit(node.children[1])
+
__DEBUG__("LET")
self.emit_let_left_part(node)
def visit_POKE(self, node):
ch0 = node.children[0]
ch1 = node.children[1]
- yield ch0
- yield ch1
+ yield self.visit(ch0)
+ yield self.visit(ch1)
if ch0.token == "VAR" and ch0.class_ != CLASS.const and ch0.scope == SCOPE.global_:
self.ic_store(ch1.type_, "*" + str(ch0.t), ch1.t)
@@ -99,7 +100,7 @@ def visit_POKE(self, node):
self.ic_store(ch1.type_, str(ch0.t), ch1.t)
def visit_RANDOMIZE(self, node):
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_fparam(node.children[0].type_, node.children[0].t)
self.runtime_call(RuntimeLabel.RANDOMIZE, 0)
@@ -107,7 +108,7 @@ def visit_LABEL(self, node):
self.ic_label(node.mangled)
def visit_CONST(self, node):
- yield node.symbol
+ yield self.visit(node.symbol)
def visit_VAR(self, node):
__DEBUG__(
@@ -117,7 +118,7 @@ def visit_VAR(self, node):
scope = node.scope
if node.t == node.mangled and scope == SCOPE.global_:
- return
+ return node
p = "*" if node.byref else "" # Indirection prefix
@@ -127,6 +128,8 @@ def visit_VAR(self, node):
offset = node.offset
self.ic_pload(node.type_, node.t, p + str(-offset))
+ return node
+
def visit_CONSTEXPR(self, node):
yield node.t
@@ -138,7 +141,7 @@ def visit_PARAMDECL(self, node):
self.visit_VAR(node)
def visit_UNARY(self, node):
- uvisitor = UnaryOpTranslator(self.backend)
+ uvisitor = UnaryOpTranslator(self.backend, self)
att = f"visit_{node.operator}"
if hasattr(uvisitor, att):
yield getattr(uvisitor, att)(node)
@@ -147,8 +150,8 @@ def visit_UNARY(self, node):
raise InvalidOperatorError(node.operator)
def visit_BUILTIN(self, node):
- yield node.operand
- bvisitor = BuiltinTranslator(self.backend)
+ yield self.visit(node.operand)
+ bvisitor = BuiltinTranslator(self.backend, self)
att = f"visit_{node.fname}"
if hasattr(bvisitor, att):
yield getattr(bvisitor, att)(node)
@@ -159,8 +162,8 @@ def visit_BUILTIN(self, node):
raise InvalidBuiltinFunctionError(node.fname)
def visit_BINARY(self, node):
- yield node.left
- yield node.right
+ yield self.visit(node.left)
+ yield self.visit(node.right)
ins = {"PLUS": "add", "MINUS": "sub"}.get(node.operator, node.operator.lower())
ins_t: dict[str, Callable[[TYPE | symbols.BASICTYPE, Any, Any, Any], None]] = {
@@ -187,18 +190,12 @@ def visit_BINARY(self, node):
}
ins_t[ins](node.left.type_, node.t, str(node.left.t), str(node.right.t))
- def visit_TYPECAST(self, node):
- yield node.operand
- assert node.operand.type_.is_basic
- assert node.type_.is_basic
- self.ic_cast(node.t, node.operand.type_, node.type_, node.operand.t)
-
def visit_FUNCDECL(self, node):
# Delay emission of functions until the end of the main code
gl.FUNCTIONS.append(node.entry)
def visit_CALL(self, node: symbols.CALL):
- yield node.args # arglist
+ yield self.visit(node.args) # arglist
if node.entry.convention == CONVENTION.fastcall:
if len(node.args) > 0: # At least 1 parameter
self.ic_fparam(node.args[0].type_, optemps.new_t())
@@ -209,7 +206,7 @@ def visit_CALL(self, node: symbols.CALL):
def visit_ARGLIST(self, node):
for i in range(len(node) - 1, -1, -1): # visit in reverse order
- yield node[i]
+ yield self.visit(node[i])
def visit_ARGUMENT(self, node):
if not node.byref:
@@ -221,7 +218,7 @@ def visit_ARGUMENT(self, node):
else: # PARAMETER
self.ic_pload(node.type_, node.t, str(node.value.offset))
else:
- yield node.value
+ yield self.visit(node.value)
self.ic_param(node.type_, node.t)
return
@@ -260,13 +257,13 @@ def visit_ARGUMENT(self, node):
node.value = SymbolARRAYACCESS.copy_from(node.value)
node.value = symbols.UNARY("ADDRESS", node.value, node.lineno, type_=self.TYPE(gl.PTR_TYPE))
- yield node.value
+ yield self.visit(node.value)
def visit_ARRAYLOAD(self, node):
scope = node.entry.scope
if node.offset is None:
- yield node.args
+ yield self.visit(node.args)
if scope == SCOPE.global_:
self.ic_aload(node.type_, node.entry.t, node.entry.mangled)
@@ -334,8 +331,8 @@ def visit_LETARRAY(self, node):
scope = arr.scope
if arr.offset is None:
- yield node.children[1] # Right expression
- yield arr
+ yield self.visit(node.children[1]) # Right expression
+ yield self.visit(arr)
if scope == SCOPE.global_:
self.ic_astore(arr.type_, arr.entry.mangled, node.children[1].t)
@@ -347,14 +344,14 @@ def visit_LETARRAY(self, node):
else:
name = arr.entry.data_label
if scope == SCOPE.global_:
- yield node.children[1] # Right expression
+ yield self.visit(node.children[1]) # Right expression
self.ic_store(arr.type_, "%s + %i" % (name, arr.offset), node.children[1].t)
elif scope == SCOPE.local:
t1 = optemps.new_t()
t2 = optemps.new_t()
self.ic_pload(gl.PTR_TYPE, t1, -(arr.entry.offset - self.TYPE(gl.PTR_TYPE).size))
self.ic_add(gl.PTR_TYPE, t2, t1, arr.offset)
- yield node.children[1] # Right expression
+ yield self.visit(node.children[1]) # Right expression
if arr.type_ == Type.string:
self.ic_store(arr.type_, f"*{t2}", node.children[1].t)
@@ -366,7 +363,7 @@ def visit_LETARRAY(self, node):
def visit_LETSUBSTR(self, node):
"""LET X$(a TO b) = Y$"""
# load Y$
- yield node.children[3]
+ self.visit(node.children[3])
if check.is_temporary_value(node.children[3]):
self.ic_param(TYPE.string, node.children[3].t)
@@ -376,10 +373,10 @@ def visit_LETSUBSTR(self, node):
self.ic_param(TYPE.ubyte, 0)
# Load a
- yield node.children[1]
+ yield self.visit(node.children[1])
self.ic_param(gl.PTR_TYPE, node.children[1].t)
# Load b
- yield node.children[2]
+ yield self.visit(node.children[2])
self.ic_param(gl.PTR_TYPE, node.children[2].t)
# Load x$
str_var = node.children[0]
@@ -405,8 +402,8 @@ def visit_LETARRAYSUBSTR(self, node):
if self.O_LEVEL > 1 and not node.children[0].entry.accessed:
return
- expr = node.children[3] # right expression
- yield expr
+ expr = node.children[3]
+ yield self.visit(node.children[3]) # right expression
if check.is_temporary_value(expr):
self.ic_param(TYPE.string, expr.t)
@@ -415,9 +412,9 @@ def visit_LETARRAYSUBSTR(self, node):
self.ic_param(gl.PTR_TYPE, expr.t)
self.ic_param(TYPE.ubyte, 0)
- yield node.children[1]
+ yield self.visit(node.children[1])
self.ic_param(gl.PTR_TYPE, node.children[1].t)
- yield node.children[2]
+ yield self.visit(node.children[2])
self.ic_param(gl.PTR_TYPE, node.children[2].t)
node_ = node.children[0]
@@ -426,7 +423,7 @@ def visit_LETARRAYSUBSTR(self, node):
# Address of an array element.
if node_.offset is None:
- yield node_
+ yield self.visit(node_)
if scope == SCOPE.global_:
self.ic_aload(gl.PTR_TYPE, node_.t, entry.mangled)
elif scope == SCOPE.parameter: # TODO: These 2 are never used!??
@@ -446,18 +443,18 @@ def visit_LETARRAYSUBSTR(self, node):
self.runtime_call(RuntimeLabel.LETSUBSTR, 0)
def visit_ARRAYACCESS(self, node):
- yield node.arglist
+ yield self.visit(node.arglist)
def visit_STRSLICE(self, node):
- yield node.string
+ yield self.visit(node.string)
if node.string.token == "STRING" or node.string.token == "VAR" and node.string.scope == SCOPE.global_:
self.ic_param(gl.PTR_TYPE, node.string.t)
# Now emit the slicing indexes
- yield node.lower
+ yield self.visit(node.lower)
self.ic_param(node.lower.type_, node.lower.t)
- yield node.upper
+ yield self.visit(node.upper)
self.ic_param(node.upper.type_, node.upper.t)
if node.string.token == "VAR" and node.string.mangled[0] == "_" or node.string.token == "STRING":
@@ -468,7 +465,7 @@ def visit_STRSLICE(self, node):
self.runtime_call(RuntimeLabel.STRSLICE, self.TYPE(gl.PTR_TYPE).size)
def visit_FUNCCALL(self, node):
- yield node.args
+ yield self.visit(node.args)
if node.entry.convention == CONVENTION.fastcall:
if len(node.args) > 0: # At least 1
@@ -509,7 +506,7 @@ def visit_READ(self, node):
scope = arr.scope
if arr.offset is None:
- yield arr
+ yield self.visit(arr)
if scope == SCOPE.global_:
self.ic_astore(arr.type_, arr.entry.mangled, t)
@@ -539,7 +536,7 @@ def visit_DO_LOOP(self, node):
self.ic_label(loop_label)
if node.children:
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_jump(loop_label)
self.ic_label(end_loop)
@@ -561,10 +558,10 @@ def visit_DO_WHILE(self, node):
self.LOOPS.append(("DO", end_loop, continue_loop)) # Saves which labels to jump upon EXIT or CONTINUE
if len(node.children) > 1:
- yield node.children[1]
+ yield self.visit(node.children[1])
self.ic_label(continue_loop)
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_jnzero(node.children[0].type_, node.children[0].t, loop_label)
self.ic_label(end_loop)
self.LOOPS.pop()
@@ -598,20 +595,20 @@ def visit_FOR(self, node):
self.LOOPS.append(("FOR", end_loop, loop_continue)) # Saves which label to jump upon EXIT FOR and CONTINUE FOR
- yield node.children[1] # Gets starting value (lower limit)
+ yield self.visit(node.children[1]) # Gets starting value (lower limit)
self.emit_let_left_part(node) # Stores it in the iterator variable
self.ic_jump(loop_label_start)
# FOR body statements
self.ic_label(loop_body)
- yield node.children[4]
+ yield self.visit(node.children[4])
# Jump here to continue next iteration
self.ic_label(loop_continue)
# VAR = VAR + STEP
- yield node.children[0] # Iterator Var
- yield node.children[3] # Step
+ yield self.visit(node.children[0]) # Iterator Var
+ yield self.visit(node.children[3]) # Step
t = optemps.new_t()
self.ic_add(type_, t, node.children[0].t, node.children[3].t)
self.emit_let_left_part(node, t)
@@ -624,13 +621,13 @@ def visit_FOR(self, node):
direct = True
else:
direct = False
- yield node.children[3] # Step
+ yield self.visit(node.children[3]) # Step
self.ic_jgezero(type_, node.children[3].t, loop_label_gt)
if not direct or node.children[3].value < 0: # Here for negative steps
# Compares if var < limit2
- yield node.children[0] # Value of var
- yield node.children[2] # Value of limit2
+ yield self.visit(node.children[0]) # Value of var
+ yield self.visit(node.children[2]) # Value of limit2
self.ic_lt(type_, node.t, node.children[0].t, node.children[2].t)
self.ic_jzero(TYPE.ubyte, node.t, loop_body)
@@ -640,8 +637,8 @@ def visit_FOR(self, node):
if not direct or node.children[3].value >= 0: # Here for positive steps
# Compares if var > limit2
- yield node.children[0] # Value of var
- yield node.children[2] # Value of limit2
+ yield self.visit(node.children[0]) # Value of var
+ yield self.visit(node.children[2]) # Value of limit2
self.ic_gt(type_, node.t, node.children[0].t, node.children[2].t)
self.ic_jzero(TYPE.ubyte, node.t, loop_body)
@@ -657,7 +654,7 @@ def visit_GOSUB(self, node):
def visit_ON_GOTO(self, node):
table_label = src.api.tmp_labels.tmp_label()
self.ic_param(gl.PTR_TYPE, "#" + table_label)
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_fparam(node.children[0].type_, node.children[0].t)
self.runtime_call(RuntimeLabel.ON_GOTO, 0)
self.JUMP_TABLES.append(JumpTable(table_label, node.children[1:]))
@@ -665,7 +662,7 @@ def visit_ON_GOTO(self, node):
def visit_ON_GOSUB(self, node):
table_label = src.api.tmp_labels.tmp_label()
self.ic_param(gl.PTR_TYPE, "#" + table_label)
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_fparam(node.children[0].type_, node.children[0].t)
self.runtime_call(RuntimeLabel.ON_GOSUB, 0)
self.JUMP_TABLES.append(JumpTable(table_label, node.children[1:]))
@@ -678,7 +675,7 @@ def visit_CHKBREAK(self, node):
def visit_IF(self, node):
assert 1 < len(node.children) < 4, "IF nodes: %i" % len(node.children)
- yield node.children[0]
+ yield self.visit(node.children[0])
if_label_else = src.api.tmp_labels.tmp_label()
if_label_endif = src.api.tmp_labels.tmp_label()
@@ -687,18 +684,18 @@ def visit_IF(self, node):
else:
self.ic_jzero(node.children[0].type_, node.children[0].t, if_label_endif)
- yield node.children[1] # THEN...
+ yield self.visit(node.children[1]) # THEN...
if len(node.children) == 3: # Has else?
self.ic_jump(if_label_endif)
self.ic_label(if_label_else)
- yield node.children[2]
+ yield self.visit(node.children[2])
self.ic_label(if_label_endif)
def visit_RETURN(self, node):
if len(node.children) == 2: # Something to return?
- yield node.children[1]
+ yield self.visit(node.children[1])
self.ic_ret(node.children[1].type_, node.children[1].t, "%s__leave" % node.children[0].mangled)
elif len(node.children) == 1:
self.ic_return("%s__leave" % node.children[0].mangled)
@@ -717,10 +714,10 @@ def visit_UNTIL_DO(self, node):
self.LOOPS.append(("DO", end_loop, continue_loop)) # Saves which labels to jump upon EXIT or CONTINUE
if len(node.children) > 1:
- yield node.children[1]
+ yield self.visit(node.children[1])
self.ic_label(continue_loop)
- yield node.children[0] # Condition
+ yield self.visit(node.children[0]) # Condition
self.ic_jzero(node.children[0].type_, node.children[0].t, loop_label)
self.ic_label(end_loop)
self.LOOPS.pop()
@@ -732,11 +729,11 @@ def visit_WHILE(self, node):
self.LOOPS.append(("WHILE", end_loop, loop_label)) # Saves which labels to jump upon EXIT or CONTINUE
self.ic_label(loop_label)
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_jzero(node.children[0].type_, node.children[0].t, end_loop)
if len(node.children) > 1:
- yield node.children[1]
+ yield self.visit(node.children[1])
self.ic_jump(loop_label)
self.ic_label(end_loop)
@@ -754,10 +751,10 @@ def visit_WHILE_DO(self, node):
def visit_PLOT(self, node):
self.norm_attr()
TMP_HAS_ATTR = self.check_attr(node, 2)
- yield TMP_HAS_ATTR
- yield node.children[0]
+ yield self.visit(TMP_HAS_ATTR)
+ yield self.visit(node.children[0])
self.ic_param(node.children[0].type_, node.children[0].t)
- yield node.children[1]
+ yield self.visit(node.children[1])
self.ic_fparam(node.children[1].type_, node.children[1].t)
self.runtime_call(RuntimeLabel.PLOT, 0)
self.HAS_ATTR = TMP_HAS_ATTR is not None
@@ -765,10 +762,10 @@ def visit_PLOT(self, node):
def visit_DRAW(self, node):
self.norm_attr()
TMP_HAS_ATTR = self.check_attr(node, 2)
- yield TMP_HAS_ATTR
- yield node.children[0]
+ yield self.visit(TMP_HAS_ATTR)
+ yield self.visit(node.children[0])
self.ic_param(node.children[0].type_, node.children[0].t)
- yield node.children[1]
+ yield self.visit(node.children[1])
self.ic_fparam(node.children[1].type_, node.children[1].t)
self.runtime_call(RuntimeLabel.DRAW, 0)
self.HAS_ATTR = TMP_HAS_ATTR is not None
@@ -776,12 +773,12 @@ def visit_DRAW(self, node):
def visit_DRAW3(self, node):
self.norm_attr()
TMP_HAS_ATTR = self.check_attr(node, 3)
- yield TMP_HAS_ATTR
- yield node.children[0]
+ yield self.visit(TMP_HAS_ATTR)
+ yield self.visit(node.children[0])
self.ic_param(node.children[0].type_, node.children[0].t)
- yield node.children[1]
+ yield self.visit(node.children[1])
self.ic_param(node.children[1].type_, node.children[1].t)
- yield node.children[2]
+ yield self.visit(node.children[2])
self.ic_fparam(node.children[2].type_, node.children[2].t)
self.runtime_call(RuntimeLabel.DRAW3, 0)
self.HAS_ATTR = TMP_HAS_ATTR is not None
@@ -789,12 +786,12 @@ def visit_DRAW3(self, node):
def visit_CIRCLE(self, node):
self.norm_attr()
TMP_HAS_ATTR = self.check_attr(node, 3)
- yield TMP_HAS_ATTR
- yield node.children[0]
+ yield self.visit(TMP_HAS_ATTR)
+ yield self.visit(node.children[0])
self.ic_param(node.children[0].type_, node.children[0].t)
- yield node.children[1]
+ yield self.visit(node.children[1])
self.ic_param(node.children[1].type_, node.children[1].t)
- yield node.children[2]
+ yield self.visit(node.children[2])
self.ic_fparam(node.children[2].type_, node.children[2].t)
self.runtime_call(RuntimeLabel.CIRCLE, 0)
self.HAS_ATTR = TMP_HAS_ATTR is not None
@@ -806,25 +803,18 @@ def visit_CIRCLE(self, node):
# PRINT, LOAD, SAVE and I/O statements
# -----------------------------------------------------------------------------------------------------
def visit_OUT(self, node):
- yield node.children[0]
- yield node.children[1]
+ yield self.visit(node.children[0])
+ yield self.visit(node.children[1])
self.ic_out(node.children[0].t, node.children[1].t)
def visit_PRINT(self, node):
self.norm_attr()
+ skip_tokens = set(self.ATTR_TMP) | {"PRINT_TAB", "PRINT_AT", "PRINT_COMMA"}
for i in node.children:
- yield i
+ yield self.visit(i)
# Print subcommands (AT, OVER, INK, etc... must be skipped here)
- if (
- i.token
- in (
- "PRINT_TAB",
- "PRINT_AT",
- "PRINT_COMMA",
- )
- + self.ATTR_TMP
- ):
+ if i.token in skip_tokens:
continue
self.ic_fparam(i.type_, i.t)
@@ -846,14 +836,14 @@ def visit_PRINT(self, node):
self.runtime_call(RuntimeLabel.PRINT_EOL, 0)
def visit_PRINT_AT(self, node):
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_param(TYPE.ubyte, node.children[0].t)
- yield node.children[1]
+ yield self.visit(node.children[1])
self.ic_fparam(TYPE.ubyte, node.children[1].t)
self.runtime_call(RuntimeLabel.PRINT_AT, 0) # Procedure call. Discard return
def visit_PRINT_TAB(self, node):
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_fparam(TYPE.ubyte, node.children[0].t)
self.runtime_call(RuntimeLabel.PRINT_TAB, 0)
@@ -861,22 +851,22 @@ def visit_PRINT_COMMA(self, node):
self.runtime_call(RuntimeLabel.PRINT_COMMA, 0)
def visit_LOAD(self, node):
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_param(TYPE.string, node.children[0].t)
- yield node.children[1]
+ yield self.visit(node.children[1])
self.ic_param(gl.PTR_TYPE, node.children[1].t)
- yield node.children[2]
+ yield self.visit(node.children[2])
self.ic_param(gl.PTR_TYPE, node.children[2].t)
self.ic_param(TYPE.ubyte, int(node.token == "LOAD"))
self.runtime_call(RuntimeLabel.LOAD_CODE, 0)
def visit_SAVE(self, node):
- yield (node.children[0])
+ yield self.visit(node.children[0])
self.ic_param(TYPE.string, node.children[0].t)
- yield (node.children[1])
+ yield self.visit(node.children[1])
self.ic_param(gl.PTR_TYPE, node.children[1].t)
- yield node.children[2]
+ yield self.visit(node.children[2])
self.ic_param(gl.PTR_TYPE, node.children[2].t)
self.runtime_call(RuntimeLabel.SAVE_CODE, 0)
@@ -884,7 +874,7 @@ def visit_VERIFY(self, node):
return self.visit_LOAD(node)
def visit_BORDER(self, node):
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_fparam(TYPE.ubyte, node.children[0].t)
self.runtime_call(RuntimeLabel.BORDER, 0)
@@ -895,14 +885,14 @@ def visit_BEEP(self, node):
self.ic_fparam(TYPE.uinteger, DE)
self.runtime_call(RuntimeLabel.BEEPER, 0) # Procedure call. Discard return
else:
- yield node.children[1]
+ yield self.visit(node.children[1])
self.ic_param(TYPE.float, node.children[1].t)
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_fparam(TYPE.float, node.children[0].t)
self.runtime_call(RuntimeLabel.BEEP, 0)
def visit_PAUSE(self, node):
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_fparam(node.children[0].type_, node.children[0].t)
self.runtime_call(RuntimeLabel.PAUSE, 0)
@@ -913,7 +903,7 @@ def visit_PAUSE(self, node):
# ATTR sentences: INK, PAPER, BRIGHT, FLASH, INVERSE, OVER, ITALIC, BOLD
# -----------------------------------------------------------------------
def visit_ATTR_sentence(self, node):
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_fparam(TYPE.ubyte, node.children[0].t)
label = {
diff --git a/src/arch/z80/visitor/translator_inst_visitor.py b/src/arch/z80/visitor/translator_inst_visitor.py
index cc218d4c9..306c66512 100644
--- a/src/arch/z80/visitor/translator_inst_visitor.py
+++ b/src/arch/z80/visitor/translator_inst_visitor.py
@@ -16,6 +16,7 @@
class TranslatorInstVisitor(NodeVisitor):
def __init__(self, backend: Backend):
+ super().__init__()
self.backend = backend
def emit(self, *args: str) -> None:
diff --git a/src/arch/z80/visitor/translator_visitor.py b/src/arch/z80/visitor/translator_visitor.py
index 7debfdd27..331ec4aa9 100644
--- a/src/arch/z80/visitor/translator_visitor.py
+++ b/src/arch/z80/visitor/translator_visitor.py
@@ -97,11 +97,17 @@ def dumpMemory(MEMORY):
def visit_BLOCK(self, node):
__DEBUG__("BLOCK", 2)
for child in node.children:
- yield child
+ yield self.visit(child)
+
+ def visit_TYPECAST(self, node):
+ yield self.visit(node.operand)
+ assert node.operand.type_.is_basic
+ assert node.type_.is_basic
+ self.ic_cast(node.t, node.operand.type_, node.type_, node.operand.t)
# Visits any temporal attribute
def visit_ATTR_TMP(self, node):
- yield node.children[0]
+ yield self.visit(node.children[0])
self.ic_fparam(node.children[0].type_, node.children[0].t)
label = {
diff --git a/src/arch/z80/visitor/unary_op_translator.py b/src/arch/z80/visitor/unary_op_translator.py
index e3486b7b1..f81499f76 100644
--- a/src/arch/z80/visitor/unary_op_translator.py
+++ b/src/arch/z80/visitor/unary_op_translator.py
@@ -6,28 +6,36 @@
# --------------------------------------------------------------------
from src.api.constants import SCOPE
+from src.arch.z80.backend import Backend
from src.arch.z80.visitor.translator_visitor import TranslatorVisitor
class UnaryOpTranslator(TranslatorVisitor):
"""UNARY sub-visitor. E.g. -a or bNot pi"""
+ def __init__(self, backend: Backend, parent_visitor: TranslatorVisitor):
+ super().__init__(backend)
+ self.parent_visitor = parent_visitor # Will use this visitor visit() function
+
+ def visit(self, node):
+ return self.parent_visitor.visit(node)
+
def visit_MINUS(self, node):
- yield node.operand
+ yield self.visit(node.operand)
self.ic_neg(node.type_, node.t, node.operand.t)
def visit_NOT(self, node):
- yield node.operand
+ yield self.visit(node.operand)
self.ic_not(node.operand.type_, node.t, node.operand.t)
def visit_BNOT(self, node):
- yield node.operand
+ yield self.visit(node.operand)
self.ic_bnot(node.operand.type_, node.t, node.operand.t)
def visit_ADDRESS(self, node):
scope = node.operand.scope
if node.operand.token == "ARRAYACCESS":
- yield node.operand
+ yield self.visit(node.operand)
# Address of an array element.
if scope == SCOPE.global_:
self.ic_aaddr(node.t, node.operand.entry.mangled)
diff --git a/src/ast/__init__.py b/src/ast/__init__.py
index 9edeeea6b..221568150 100644
--- a/src/ast/__init__.py
+++ b/src/ast/__init__.py
@@ -5,12 +5,11 @@
# See https://www.gnu.org/licenses/agpl-3.0.html for details.
# --------------------------------------------------------------------
-from .ast import Ast, NodeVisitor, types
+from .ast import Ast, NodeVisitor
from .tree import Tree
__all__ = (
"Ast",
"NodeVisitor",
"Tree",
- "types",
)
diff --git a/src/ast/ast.py b/src/ast/ast.py
index 2fb960f46..4c1094afd 100644
--- a/src/ast/ast.py
+++ b/src/ast/ast.py
@@ -4,12 +4,13 @@
# See the file CONTRIBUTORS.md for copyright details.
# See https://www.gnu.org/licenses/agpl-3.0.html for details.
# --------------------------------------------------------------------
-
-import types
-from collections.abc import Callable
-from typing import Any
+from collections.abc import Callable, Generator
+from typing import Any, Final
from .tree import Tree
+from .visitor import GenericNodeVisitor
+
+__all__: Final[tuple[str, ...]] = "Ast", "NodeVisitor"
# ----------------------------------------------------------------------
@@ -25,49 +26,17 @@ def token(self):
return self.__class__
-class NodeVisitor:
- node_type: type = Ast
-
- def visit(self, node):
- stack = [node]
- last_result = None
-
- while stack:
- try:
- last = stack[-1]
- if isinstance(last, types.GeneratorType):
- stack.append(last.send(last_result))
- last_result = None
- elif isinstance(last, self.node_type):
- stack.append(self._visit(stack.pop()))
- else:
- last_result = stack.pop()
- except StopIteration:
- stack.pop()
-
- return last_result
-
- def _visit(self, node):
- meth = getattr(self, f"visit_{node.token}", self.generic_visit)
+class NodeVisitor(GenericNodeVisitor[Ast]):
+ def _visit(self, node: Ast):
+ meth: Callable[[Ast], Generator[Ast | Any, Any, None]] = getattr(
+ self,
+ f"visit_{node.token}",
+ self.generic_visit,
+ )
return meth(node)
- def generic_visit(self, node: Ast):
- raise RuntimeError(f"No visit_{node.token}() method defined")
+ def generic_visit(self, node: Ast) -> Generator[Ast | Any, Any, None]:
+ for i, child in enumerate(node.children):
+ node.children[i] = yield self.visit(child)
- def filter_inorder(
- self, node, filter_func: Callable[[Any], bool], child_selector: Callable[[Ast], bool] = lambda x: True
- ):
- """Visit the tree inorder, but only those that return true for filter_func and visiting children which
- return true for child_selector.
- """
- visited = set()
- stack = [node]
- while stack:
- node = stack.pop()
- if node in visited:
- continue
- visited.add(node)
- if filter_func(node):
- yield self.visit(node)
- if isinstance(node, Ast) and child_selector(node):
- stack.extend(node.children[::-1])
+ yield node
diff --git a/src/ast/exceptions.py b/src/ast/exceptions.py
new file mode 100644
index 000000000..23102a633
--- /dev/null
+++ b/src/ast/exceptions.py
@@ -0,0 +1,18 @@
+from typing import Final
+
+from src.api.exception import Error
+
+__all__: Final[tuple[str, ...]] = ("NotAnAstError",)
+
+
+class NotAnAstError(Error):
+ """Thrown when the "pointer" is not
+ an AST, but another thing.
+ """
+
+ def __init__(self, instance):
+ self.instance = instance
+ self.msg = "Object '%s' is not an Ast instance" % str(instance)
+
+ def __str__(self):
+ return self.msg
diff --git a/src/ast/tree.py b/src/ast/tree.py
index 389f9b648..d65e384e3 100644
--- a/src/ast/tree.py
+++ b/src/ast/tree.py
@@ -11,22 +11,7 @@
from collections.abc import Iterable, Iterator
from typing import Any
-from src.api.exception import Error
-
-__all__ = "ChildrenList", "NotAnAstError", "Tree"
-
-
-class NotAnAstError(Error):
- """Thrown when the "pointer" is not
- an AST, but another thing.
- """
-
- def __init__(self, instance):
- self.instance = instance
- self.msg = "Object '%s' is not an Ast instance" % str(instance)
-
- def __str__(self):
- return self.msg
+__all__ = "ChildrenList", "Tree"
class Tree:
diff --git a/src/ast/visitor.py b/src/ast/visitor.py
new file mode 100644
index 000000000..106adbf56
--- /dev/null
+++ b/src/ast/visitor.py
@@ -0,0 +1,42 @@
+__doc__ = "Implements a generic visitor class for Trees"
+
+from abc import abstractmethod
+from collections.abc import Generator
+from types import GeneratorType
+from typing import Final, Generic, NamedTuple, TypeVar
+
+__all__: Final[tuple[str, ...]] = ("GenericNodeVisitor",)
+
+_T = TypeVar("_T")
+
+
+class ToVisit(NamedTuple, Generic[_T]):
+ obj: _T
+
+
+class GenericNodeVisitor(Generic[_T]):
+ def visit(self, node: _T | None) -> _T | Generator[_T | None, None, None] | None:
+ stack: list[_T | GeneratorType] = [ToVisit[_T](node) if node is not None else None]
+ last_result: _T | None = None
+
+ while stack:
+ try:
+ stack_top = stack[-1]
+ if isinstance(stack_top, GeneratorType):
+ stack.append(stack_top.send(last_result))
+ last_result = None
+ elif isinstance(stack_top, ToVisit):
+ stack.pop()
+ stack.append(self._visit(stack_top.obj))
+ else:
+ last_result = stack.pop()
+ except StopIteration:
+ stack.pop()
+
+ return last_result
+
+ @abstractmethod
+ def _visit(self, node: _T): ...
+
+ @abstractmethod
+ def generic_visit(self, node: _T) -> Generator[_T | None, None, None]: ...
diff --git a/src/symbols/arrayload.py b/src/symbols/arrayload.py
index cbdb6ee55..222413fcc 100644
--- a/src/symbols/arrayload.py
+++ b/src/symbols/arrayload.py
@@ -11,7 +11,6 @@
class SymbolARRAYLOAD(SymbolARRAYACCESS):
"""This class is the same as SymbolARRAYACCESS, we just declare it to make
a distinction. (e.g. the Token is gotten from the class name).
-
"""
pass
diff --git a/src/symbols/symbol_.py b/src/symbols/symbol_.py
index c1932c00b..f495b4977 100644
--- a/src/symbols/symbol_.py
+++ b/src/symbols/symbol_.py
@@ -79,10 +79,9 @@ def t(self) -> str:
def is_needed(self) -> bool:
return len(self.required_by) > 0
- def get_parent(self, type_) -> Tree | None:
- """Traverse parents until finding one
- of type type_ or None if not found.
- If a cycle is detected an undetermined value is returned as parent.
+ def get_parent(self, type_: type[Tree]) -> Tree | None:
+ """Traverse parents until finding one of type type_ or None if not found.
+ If a cycle is detected, None is returned.
"""
visited = set()
parent = self.parent
@@ -90,6 +89,7 @@ def get_parent(self, type_) -> Tree | None:
visited.add(parent)
if isinstance(parent, type_):
return parent
+
parent = parent.parent
- return parent
+ return None
diff --git a/src/zxbasm/expr.py b/src/zxbasm/expr.py
index b0a779f8e..4c9444acc 100644
--- a/src/zxbasm/expr.py
+++ b/src/zxbasm/expr.py
@@ -7,7 +7,7 @@
from src.api.errmsg import error
from src.ast import Ast
-from src.ast.tree import NotAnAstError
+from src.ast.exceptions import NotAnAstError
from src.zxbasm.label import Label
diff --git a/tests/functional/arch/zx48k/print_at2.asm b/tests/functional/arch/zx48k/print_at2.asm
new file mode 100644
index 000000000..8486934b8
--- /dev/null
+++ b/tests/functional/arch/zx48k/print_at2.asm
@@ -0,0 +1,962 @@
+ org 32768
+.core.__START_PROGRAM:
+ di
+ push ix
+ push iy
+ exx
+ push hl
+ exx
+ ld (.core.__CALL_BACK__), sp
+ ei
+ call .core.__PRINT_INIT
+ jp .core.__MAIN_PROGRAM__
+.core.__CALL_BACK__:
+ DEFW 0
+.core.ZXBASIC_USER_DATA:
+ ; Defines USER DATA Length in bytes
+.core.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_END - .core.ZXBASIC_USER_DATA
+ .core.__LABEL__.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_LEN
+ .core.__LABEL__.ZXBASIC_USER_DATA EQU .core.ZXBASIC_USER_DATA
+_row:
+ DEFB 00
+_col:
+ DEFB 00
+.core.ZXBASIC_USER_DATA_END:
+.core.__MAIN_PROGRAM__:
+ call .core.COPY_ATTR
+ ld a, (_row)
+ add a, 2
+ push af
+ ld a, (_col)
+ inc a
+ call .core.PRINT_AT
+ ld hl, 0
+ ld b, h
+ ld c, l
+.core.__END_PROGRAM:
+ di
+ ld hl, (.core.__CALL_BACK__)
+ ld sp, hl
+ exx
+ pop hl
+ exx
+ pop iy
+ pop ix
+ ei
+ ret
+ ;; --- end of user code ---
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/copy_attr.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+; vim:ts=4:sw=4:et:
+ ; PRINT command routine
+ ; Does not print attribute. Use PRINT_STR or PRINT_NUM for that
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/sposn.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/sysvars.asm"
+ ;; -----------------------------------------------------------------------
+ ;; ZX Basic System Vars
+ ;; Some of them will be mapped over Sinclair ROM ones for compatibility
+ ;; -----------------------------------------------------------------------
+ push namespace core
+SCREEN_ADDR: DW 16384 ; Screen address (can be pointed to other place to use a screen buffer)
+SCREEN_ATTR_ADDR: DW 22528 ; Screen attribute address (ditto.)
+ ; These are mapped onto ZX Spectrum ROM VARS
+ CHARS EQU 23606 ; Pointer to ROM/RAM Charset
+ TV_FLAG EQU 23612 ; Flags for controlling output to screen
+ UDG EQU 23675 ; Pointer to UDG Charset
+ COORDS EQU 23677 ; Last PLOT coordinates
+ FLAGS2 EQU 23681 ;
+ ECHO_E EQU 23682 ;
+ DFCC EQU 23684 ; Next screen addr for PRINT
+ DFCCL EQU 23686 ; Next screen attr for PRINT
+ S_POSN EQU 23688
+ ATTR_P EQU 23693 ; Current Permanent ATTRS set with INK, PAPER, etc commands
+ ATTR_T EQU 23695 ; temporary ATTRIBUTES
+ P_FLAG EQU 23697 ;
+ MEM0 EQU 23698 ; Temporary memory buffer used by ROM chars
+ SCR_COLS EQU 33 ; Screen with in columns + 1
+ SCR_ROWS EQU 24 ; Screen height in rows
+ SCR_SIZE EQU (SCR_ROWS << 8) + SCR_COLS
+ pop namespace
+#line 2 "/zxbasic/src/lib/arch/zx48k/runtime/sposn.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/attr.asm"
+ ; Attribute routines
+; vim:ts=4:et:sw:
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/error.asm"
+ ; Simple error control routines
+; vim:ts=4:et:
+ push namespace core
+ ERR_NR EQU 23610 ; Error code system variable
+ ; Error code definitions (as in ZX spectrum manual)
+; Set error code with:
+ ; ld a, ERROR_CODE
+ ; ld (ERR_NR), a
+ ERROR_Ok EQU -1
+ ERROR_SubscriptWrong EQU 2
+ ERROR_OutOfMemory EQU 3
+ ERROR_OutOfScreen EQU 4
+ ERROR_NumberTooBig EQU 5
+ ERROR_InvalidArg EQU 9
+ ERROR_IntOutOfRange EQU 10
+ ERROR_NonsenseInBasic EQU 11
+ ERROR_InvalidFileName EQU 14
+ ERROR_InvalidColour EQU 19
+ ERROR_BreakIntoProgram EQU 20
+ ERROR_TapeLoadingErr EQU 26
+ ; Raises error using RST #8
+__ERROR:
+ ld (__ERROR_CODE), a
+ rst 8
+__ERROR_CODE:
+ nop
+ ret
+ ; Sets the error system variable, but keeps running.
+ ; Usually this instruction if followed by the END intermediate instruction.
+__STOP:
+ ld (ERR_NR), a
+ ret
+ pop namespace
+#line 6 "/zxbasic/src/lib/arch/zx48k/runtime/attr.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/in_screen.asm"
+ push namespace core
+__IN_SCREEN:
+ ; Returns NO carry if current coords (D, E)
+ ; are OUT of the screen limits
+ PROC
+ LOCAL __IN_SCREEN_ERR
+ ld hl, SCR_SIZE
+ ld a, e
+ cp l
+ jr nc, __IN_SCREEN_ERR ; Do nothing and return if out of range
+ ld a, d
+ cp h
+ ret c ; Return if carry (OK)
+__IN_SCREEN_ERR:
+__OUT_OF_SCREEN_ERR:
+ ; Jumps here if out of screen
+ ld a, ERROR_OutOfScreen
+ jp __STOP ; Saves error code and exits
+ ENDP
+ pop namespace
+#line 7 "/zxbasic/src/lib/arch/zx48k/runtime/attr.asm"
+ push namespace core
+__ATTR_ADDR:
+ ; calc start address in DE (as (32 * d) + e)
+ ; Contributed by Santiago Romero at http://www.speccy.org
+ ld h, 0 ; 7 T-States
+ ld a, d ; 4 T-States
+ ld d, h
+ add a, a ; a * 2 ; 4 T-States
+ add a, a ; a * 4 ; 4 T-States
+ ld l, a ; HL = A * 4 ; 4 T-States
+ add hl, hl ; HL = A * 8 ; 15 T-States
+ add hl, hl ; HL = A * 16 ; 15 T-States
+ add hl, hl ; HL = A * 32 ; 15 T-States
+ add hl, de
+ ld de, (SCREEN_ATTR_ADDR) ; Adds the screen address
+ add hl, de
+ ; Return current screen address in HL
+ ret
+ ; Sets the attribute at a given screen coordinate (D, E).
+ ; The attribute is taken from the ATTR_T memory variable
+ ; Used by PRINT routines
+SET_ATTR:
+ ; Checks for valid coords
+ call __IN_SCREEN
+ ret nc
+ call __ATTR_ADDR
+__SET_ATTR:
+ ; Internal __FASTCALL__ Entry used by printing routines
+ ; HL contains the address of the ATTR cell to set
+ PROC
+__SET_ATTR2: ; Sets attr from ATTR_T to (HL) which points to the scr address
+ ld de, (ATTR_T) ; E = ATTR_T, D = MASK_T
+ ld a, d
+ and (hl)
+ ld c, a ; C = current screen color, masked
+ ld a, d
+ cpl ; Negate mask
+ and e ; Mask current attributes
+ or c ; Mix them
+ ld (hl), a ; Store result in screen
+ ret
+ ENDP
+ pop namespace
+#line 3 "/zxbasic/src/lib/arch/zx48k/runtime/sposn.asm"
+ ; Printing positioning library.
+ push namespace core
+ ; Loads into DE current ROW, COL print position from S_POSN mem var.
+__LOAD_S_POSN:
+ PROC
+ ld de, (S_POSN)
+ ld hl, SCR_SIZE
+ or a
+ sbc hl, de
+ ex de, hl
+ ret
+ ENDP
+ ; Saves ROW, COL from DE into S_POSN mem var.
+__SAVE_S_POSN:
+ PROC
+ ld hl, SCR_SIZE
+ or a
+ sbc hl, de
+ ld (S_POSN), hl ; saves it again
+__SET_SCR_PTR: ;; Fast
+ push de
+ call __ATTR_ADDR
+ ld (DFCCL), hl
+ pop de
+ ld a, d
+ ld c, a ; Saves it for later
+ and 0F8h ; Masks 3 lower bit ; zy
+ ld d, a
+ ld a, c ; Recovers it
+ and 07h ; MOD 7 ; y1
+ rrca
+ rrca
+ rrca
+ or e
+ ld e, a
+ ld hl, (SCREEN_ADDR)
+ add hl, de ; HL = Screen address + DE
+ ld (DFCC), hl
+ ret
+ ENDP
+ pop namespace
+#line 6 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/table_jump.asm"
+ push namespace core
+JUMP_HL_PLUS_2A: ; Does JP (HL + A*2) Modifies DE. Modifies A
+ add a, a
+JUMP_HL_PLUS_A: ; Does JP (HL + A) Modifies DE
+ ld e, a
+ ld d, 0
+JUMP_HL_PLUS_DE: ; Does JP (HL + DE)
+ add hl, de
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ ex de, hl
+CALL_HL:
+ jp (hl)
+ pop namespace
+#line 8 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/ink.asm"
+ ; Sets ink color in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+INK:
+ PROC
+ LOCAL __SET_INK
+ LOCAL __SET_INK2
+ ld de, ATTR_P
+__SET_INK:
+ cp 8
+ jr nz, __SET_INK2
+ inc de ; Points DE to MASK_T or MASK_P
+ ld a, (de)
+ or 7 ; Set bits 0,1,2 to enable transparency
+ ld (de), a
+ ret
+__SET_INK2:
+ ; Another entry. This will set the ink color at location pointer by DE
+ and 7 ; # Gets color mod 8
+ ld b, a ; Saves the color
+ ld a, (de)
+ and 0F8h ; Clears previous value
+ or b
+ ld (de), a
+ inc de ; Points DE to MASK_T or MASK_P
+ ld a, (de)
+ and 0F8h ; Reset bits 0,1,2 sign to disable transparency
+ ld (de), a ; Store new attr
+ ret
+ ; Sets the INK color passed in A register in the ATTR_T variable
+INK_TMP:
+ ld de, ATTR_T
+ jp __SET_INK
+ ENDP
+ pop namespace
+#line 9 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/paper.asm"
+ ; Sets paper color in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+PAPER:
+ PROC
+ LOCAL __SET_PAPER
+ LOCAL __SET_PAPER2
+ ld de, ATTR_P
+__SET_PAPER:
+ cp 8
+ jr nz, __SET_PAPER2
+ inc de
+ ld a, (de)
+ or 038h
+ ld (de), a
+ ret
+ ; Another entry. This will set the paper color at location pointer by DE
+__SET_PAPER2:
+ and 7 ; # Remove
+ rlca
+ rlca
+ rlca ; a *= 8
+ ld b, a ; Saves the color
+ ld a, (de)
+ and 0C7h ; Clears previous value
+ or b
+ ld (de), a
+ inc de ; Points to MASK_T or MASK_P accordingly
+ ld a, (de)
+ and 0C7h ; Resets bits 3,4,5
+ ld (de), a
+ ret
+ ; Sets the PAPER color passed in A register in the ATTR_T variable
+PAPER_TMP:
+ ld de, ATTR_T
+ jp __SET_PAPER
+ ENDP
+ pop namespace
+#line 10 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/flash.asm"
+ ; Sets flash flag in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+FLASH:
+ ld hl, ATTR_P
+ PROC
+ LOCAL IS_TR
+ LOCAL IS_ZERO
+__SET_FLASH:
+ ; Another entry. This will set the flash flag at location pointer by DE
+ cp 8
+ jr z, IS_TR
+ ; # Convert to 0/1
+ or a
+ jr z, IS_ZERO
+ ld a, 0x80
+IS_ZERO:
+ ld b, a ; Saves the color
+ ld a, (hl)
+ and 07Fh ; Clears previous value
+ or b
+ ld (hl), a
+ inc hl
+ res 7, (hl) ;Reset bit 7 to disable transparency
+ ret
+IS_TR: ; transparent
+ inc hl ; Points DE to MASK_T or MASK_P
+ set 7, (hl) ;Set bit 7 to enable transparency
+ ret
+ ; Sets the FLASH flag passed in A register in the ATTR_T variable
+FLASH_TMP:
+ ld hl, ATTR_T
+ jr __SET_FLASH
+ ENDP
+ pop namespace
+#line 11 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/bright.asm"
+ ; Sets bright flag in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+BRIGHT:
+ ld hl, ATTR_P
+ PROC
+ LOCAL IS_TR
+ LOCAL IS_ZERO
+__SET_BRIGHT:
+ ; Another entry. This will set the bright flag at location pointer by DE
+ cp 8
+ jr z, IS_TR
+ ; # Convert to 0/1
+ or a
+ jr z, IS_ZERO
+ ld a, 0x40
+IS_ZERO:
+ ld b, a ; Saves the color
+ ld a, (hl)
+ and 0BFh ; Clears previous value
+ or b
+ ld (hl), a
+ inc hl
+ res 6, (hl) ;Reset bit 6 to disable transparency
+ ret
+IS_TR: ; transparent
+ inc hl ; Points DE to MASK_T or MASK_P
+ set 6, (hl) ;Set bit 6 to enable transparency
+ ret
+ ; Sets the BRIGHT flag passed in A register in the ATTR_T variable
+BRIGHT_TMP:
+ ld hl, ATTR_T
+ jr __SET_BRIGHT
+ ENDP
+ pop namespace
+#line 12 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/over.asm"
+ ; Sets OVER flag in P_FLAG permanently
+; Parameter: OVER flag in bit 0 of A register
+ push namespace core
+OVER:
+ PROC
+ ld c, a ; saves it for later
+ and 2
+ ld hl, FLAGS2
+ res 1, (HL)
+ or (hl)
+ ld (hl), a
+ ld a, c ; Recovers previous value
+ and 1 ; # Convert to 0/1
+ add a, a; # Shift left 1 bit for permanent
+ ld hl, P_FLAG
+ res 1, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets OVER flag in P_FLAG temporarily
+OVER_TMP:
+ ld c, a ; saves it for later
+ and 2 ; gets bit 1; clears carry
+ rra
+ ld hl, FLAGS2
+ res 0, (hl)
+ or (hl)
+ ld (hl), a
+ ld a, c ; Recovers previous value
+ and 1
+ ld hl, P_FLAG
+ res 0, (hl)
+ or (hl)
+ ld (hl), a
+ jp __SET_ATTR_MODE
+ ENDP
+ pop namespace
+#line 13 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/inverse.asm"
+ ; Sets INVERSE flag in P_FLAG permanently
+; Parameter: INVERSE flag in bit 0 of A register
+ push namespace core
+INVERSE:
+ PROC
+ and 1 ; # Convert to 0/1
+ add a, a; # Shift left 3 bits for permanent
+ add a, a
+ add a, a
+ ld hl, P_FLAG
+ res 3, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets INVERSE flag in P_FLAG temporarily
+INVERSE_TMP:
+ and 1
+ add a, a
+ add a, a; # Shift left 2 bits for temporary
+ ld hl, P_FLAG
+ res 2, (hl)
+ or (hl)
+ ld (hl), a
+ jp __SET_ATTR_MODE
+ ENDP
+ pop namespace
+#line 14 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/bold.asm"
+ ; Sets BOLD flag in P_FLAG permanently
+; Parameter: BOLD flag in bit 0 of A register
+ push namespace core
+BOLD:
+ PROC
+ and 1
+ rlca
+ rlca
+ rlca
+ ld hl, FLAGS2
+ res 3, (HL)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets BOLD flag in P_FLAG temporarily
+BOLD_TMP:
+ and 1
+ rlca
+ rlca
+ ld hl, FLAGS2
+ res 2, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ENDP
+ pop namespace
+#line 15 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/italic.asm"
+ ; Sets ITALIC flag in P_FLAG permanently
+; Parameter: ITALIC flag in bit 0 of A register
+ push namespace core
+ITALIC:
+ PROC
+ and 1
+ rrca
+ rrca
+ rrca
+ ld hl, FLAGS2
+ res 5, (HL)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets ITALIC flag in P_FLAG temporarily
+ITALIC_TMP:
+ and 1
+ rrca
+ rrca
+ rrca
+ rrca
+ ld hl, FLAGS2
+ res 4, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ENDP
+ pop namespace
+#line 16 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ ; Putting a comment starting with @INIT
+ ; will make the compiler to add a CALL to
+ ; It is useful for initialization routines.
+ push namespace core
+__PRINT_INIT: ; To be called before program starts (initializes library)
+ PROC
+ ld hl, __PRINT_START
+ ld (PRINT_JUMP_STATE), hl
+ ;; Clears ATTR2 flags (OVER 2, etc)
+ xor a
+ ld (FLAGS2), a
+ ld hl, TV_FLAG
+ res 0, (hl)
+ LOCAL SET_SCR_ADDR
+ call __LOAD_S_POSN
+ jp __SET_SCR_PTR
+ ;; Receives HL = future value of S_POSN
+ ;; Stores it at (S_POSN) and refresh screen pointers (ATTR, SCR)
+SET_SCR_ADDR:
+ ld (S_POSN), hl
+ ex de, hl
+ ld hl, SCR_SIZE
+ or a
+ sbc hl, de
+ ex de, hl
+ dec e
+ jp __SET_SCR_PTR
+__PRINTCHAR: ; Print character store in accumulator (A register)
+ ; Modifies H'L', B'C', A'F', D'E', A
+ LOCAL PO_GR_1
+ LOCAL __PRCHAR
+ LOCAL __PRINT_JUMP
+ LOCAL __SRCADDR
+ LOCAL __PRINT_UDG
+ LOCAL __PRGRAPH
+ LOCAL __PRINT_START
+ PRINT_JUMP_STATE EQU __PRINT_JUMP + 2
+__PRINT_JUMP:
+ exx ; Switch to alternative registers
+ jp __PRINT_START ; Where to jump. If we print 22 (AT), next two calls jumps to AT1 and AT2 respectively
+__PRINT_START:
+__PRINT_CHR:
+ cp ' '
+ jr c, __PRINT_SPECIAL ; Characters below ' ' are special ones
+ ex af, af' ; Saves a value (char to print) for later
+ ld hl, (S_POSN)
+ dec l
+ jr nz, 1f
+ ld l, SCR_COLS - 1
+ dec h
+ jr nz, 2f
+ inc h
+ push hl
+ call __SCROLL_SCR
+ pop hl
+#line 94 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+2:
+ call SET_SCR_ADDR
+ jr 4f
+1:
+ ld (S_POSN), hl
+4:
+ ex af, af'
+ cp 80h ; Is it a "normal" (printable) char
+ jr c, __SRCADDR
+ cp 90h ; Is it an UDG?
+ jr nc, __PRINT_UDG
+ ; Print an 8 bit pattern (80h to 8Fh)
+ ld b, a
+ call PO_GR_1 ; This ROM routine will generate the bit pattern at MEM0
+ ld hl, MEM0
+ jp __PRGRAPH
+ PO_GR_1 EQU 0B38h
+__PRINT_UDG:
+ sub 90h ; Sub ASC code
+ ld bc, (UDG)
+ jr __PRGRAPH0
+ __SOURCEADDR EQU (__SRCADDR + 1) ; Address of the pointer to chars source
+__SRCADDR:
+ ld bc, (CHARS)
+__PRGRAPH0:
+ add a, a ; A = a * 2 (since a < 80h) ; Thanks to Metalbrain at http://foro.speccy.org
+ ld l, a
+ ld h, 0 ; HL = a * 2 (accumulator)
+ add hl, hl
+ add hl, hl ; HL = a * 8
+ add hl, bc ; HL = CHARS address
+__PRGRAPH:
+ ex de, hl ; HL = Write Address, DE = CHARS address
+ bit 2, (iy + $47)
+ call nz, __BOLD
+#line 141 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ bit 4, (iy + $47)
+ call nz, __ITALIC
+#line 146 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ ld hl, (DFCC)
+ push hl
+ ld b, 8 ; 8 bytes per char
+__PRCHAR:
+ ld a, (de) ; DE *must* be source, and HL destiny
+PRINT_MODE: ; Which operation is used to write on the screen
+ ; Set it with:
+ ; LD A,
+ ; LD (PRINT_MODE), A
+ ;
+ ; Available operations:
+ ; NORMAL : 0h --> NOP ; OVER 0
+ ; XOR : AEh --> XOR (HL) ; OVER 1
+ ; OR : B6h --> OR (HL) ; PUTSPRITE
+ ; AND : A6h --> AND (HL) ; PUTMASK
+ nop ; Set to one of the values above
+INVERSE_MODE: ; 00 -> NOP -> INVERSE 0
+ nop ; 2F -> CPL -> INVERSE 1
+ ld (hl), a
+ inc de
+ inc h ; Next line
+ djnz __PRCHAR
+ pop hl
+ inc hl
+ ld (DFCC), hl
+ ld hl, (DFCCL) ; current ATTR Pos
+ inc hl
+ ld (DFCCL), hl
+ dec hl
+ call __SET_ATTR
+ exx
+ ret
+ ; ------------- SPECIAL CHARS (< 32) -----------------
+__PRINT_SPECIAL: ; Jumps here if it is a special char
+ ld hl, __PRINT_TABLE
+ jp JUMP_HL_PLUS_2A
+PRINT_EOL: ; Called WHENEVER there is no ";" at end of PRINT sentence
+ exx
+__PRINT_0Dh: ; Called WHEN printing CHR$(13)
+ ld hl, (S_POSN)
+ dec l
+ jr nz, 1f
+ dec h
+ jr nz, 1f
+ inc h
+ push hl
+ call __SCROLL_SCR
+ pop hl
+#line 211 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+1:
+ ld l, 1
+__PRINT_EOL_END:
+ call SET_SCR_ADDR
+ exx
+ ret
+__PRINT_COM:
+ exx
+ push hl
+ push de
+ push bc
+ call PRINT_COMMA
+ pop bc
+ pop de
+ pop hl
+ ret
+__PRINT_TAB:
+ ld hl, __PRINT_TAB1
+ jr __PRINT_SET_STATE
+__PRINT_TAB1:
+ ld (MEM0), a
+ ld hl, __PRINT_TAB2
+ jr __PRINT_SET_STATE
+__PRINT_TAB2:
+ ld a, (MEM0) ; Load tab code (ignore the current one)
+ ld hl, __PRINT_START
+ ld (PRINT_JUMP_STATE), hl
+ exx
+ push hl
+ push bc
+ push de
+ call PRINT_TAB
+ pop de
+ pop bc
+ pop hl
+ ret
+__PRINT_AT:
+ ld hl, __PRINT_AT1
+ jr __PRINT_SET_STATE
+__PRINT_NOP:
+__PRINT_RESTART:
+ ld hl, __PRINT_START
+__PRINT_SET_STATE:
+ ld (PRINT_JUMP_STATE), hl ; Saves next entry call
+ exx
+ ret
+__PRINT_AT1: ; Jumps here if waiting for 1st parameter
+ ld hl, (S_POSN)
+ ld h, a
+ ld a, SCR_ROWS
+ sub h
+ ld (S_POSN + 1), a
+ ld hl, __PRINT_AT2
+ jr __PRINT_SET_STATE
+__PRINT_AT2:
+ call __LOAD_S_POSN
+ ld e, a
+ call __SAVE_S_POSN
+ jr __PRINT_RESTART
+__PRINT_DEL:
+ call __LOAD_S_POSN ; Gets current screen position
+ dec e
+ ld a, -1
+ cp e
+ jr nz, 3f
+ ld e, SCR_COLS - 2
+ dec d
+ cp d
+ jr nz, 3f
+ ld d, SCR_ROWS - 1
+3:
+ call __SAVE_S_POSN
+ exx
+ ret
+__PRINT_INK:
+ ld hl, __PRINT_INK2
+ jr __PRINT_SET_STATE
+__PRINT_INK2:
+ call INK_TMP
+ jr __PRINT_RESTART
+__PRINT_PAP:
+ ld hl, __PRINT_PAP2
+ jr __PRINT_SET_STATE
+__PRINT_PAP2:
+ call PAPER_TMP
+ jr __PRINT_RESTART
+__PRINT_FLA:
+ ld hl, __PRINT_FLA2
+ jr __PRINT_SET_STATE
+__PRINT_FLA2:
+ call FLASH_TMP
+ jr __PRINT_RESTART
+__PRINT_BRI:
+ ld hl, __PRINT_BRI2
+ jr __PRINT_SET_STATE
+__PRINT_BRI2:
+ call BRIGHT_TMP
+ jr __PRINT_RESTART
+__PRINT_INV:
+ ld hl, __PRINT_INV2
+ jr __PRINT_SET_STATE
+__PRINT_INV2:
+ call INVERSE_TMP
+ jr __PRINT_RESTART
+__PRINT_OVR:
+ ld hl, __PRINT_OVR2
+ jr __PRINT_SET_STATE
+__PRINT_OVR2:
+ call OVER_TMP
+ jr __PRINT_RESTART
+__PRINT_BOLD:
+ ld hl, __PRINT_BOLD2
+ jp __PRINT_SET_STATE
+__PRINT_BOLD2:
+ call BOLD_TMP
+ jp __PRINT_RESTART
+#line 355 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+__PRINT_ITA:
+ ld hl, __PRINT_ITA2
+ jp __PRINT_SET_STATE
+__PRINT_ITA2:
+ call ITALIC_TMP
+ jp __PRINT_RESTART
+#line 365 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ LOCAL __BOLD
+__BOLD:
+ push hl
+ ld hl, MEM0
+ ld b, 8
+1:
+ ld a, (de)
+ ld c, a
+ rlca
+ or c
+ ld (hl), a
+ inc hl
+ inc de
+ djnz 1b
+ pop hl
+ ld de, MEM0
+ ret
+#line 386 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ LOCAL __ITALIC
+__ITALIC:
+ push hl
+ ld hl, MEM0
+ ex de, hl
+ ld bc, 8
+ ldir
+ ld hl, MEM0
+ srl (hl)
+ inc hl
+ srl (hl)
+ inc hl
+ srl (hl)
+ inc hl
+ inc hl
+ inc hl
+ sla (hl)
+ inc hl
+ sla (hl)
+ inc hl
+ sla (hl)
+ pop hl
+ ld de, MEM0
+ ret
+#line 414 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ LOCAL __SCROLL_SCR
+#line 488 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ __SCROLL_SCR EQU 0DFEh ; Use ROM SCROLL
+#line 490 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 491 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+PRINT_COMMA:
+ call __LOAD_S_POSN
+ ld a, e
+ and 16
+ add a, 16
+PRINT_TAB:
+ ; Tabulates the number of spaces in A register
+ ; If the current cursor position is already A, does nothing
+ PROC
+ LOCAL LOOP
+ call __LOAD_S_POSN ; e = current row
+ sub e
+ and 31
+ ret z
+ ld b, a
+LOOP:
+ ld a, ' '
+ call __PRINTCHAR
+ djnz LOOP
+ ret
+ ENDP
+PRINT_AT: ; Changes cursor to ROW, COL
+ ; COL in A register
+ ; ROW in stack
+ pop hl ; Ret address
+ ex (sp), hl ; callee H = ROW
+ ld l, a
+ ex de, hl
+ call __IN_SCREEN
+ ret nc ; Return if out of screen
+ jp __SAVE_S_POSN
+ LOCAL __PRINT_COM
+ LOCAL __PRINT_AT1
+ LOCAL __PRINT_AT2
+ LOCAL __PRINT_BOLD
+ LOCAL __PRINT_ITA
+ LOCAL __PRINT_INK
+ LOCAL __PRINT_PAP
+ LOCAL __PRINT_SET_STATE
+ LOCAL __PRINT_TABLE
+ LOCAL __PRINT_TAB, __PRINT_TAB1, __PRINT_TAB2
+ LOCAL __PRINT_ITA2
+#line 547 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ LOCAL __PRINT_BOLD2
+#line 553 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+__PRINT_TABLE: ; Jump table for 0 .. 22 codes
+ DW __PRINT_NOP ; 0
+ DW __PRINT_NOP ; 1
+ DW __PRINT_NOP ; 2
+ DW __PRINT_NOP ; 3
+ DW __PRINT_NOP ; 4
+ DW __PRINT_NOP ; 5
+ DW __PRINT_COM ; 6 COMMA
+ DW __PRINT_NOP ; 7
+ DW __PRINT_DEL ; 8 DEL
+ DW __PRINT_NOP ; 9
+ DW __PRINT_NOP ; 10
+ DW __PRINT_NOP ; 11
+ DW __PRINT_NOP ; 12
+ DW __PRINT_0Dh ; 13
+ DW __PRINT_BOLD ; 14
+ DW __PRINT_ITA ; 15
+ DW __PRINT_INK ; 16
+ DW __PRINT_PAP ; 17
+ DW __PRINT_FLA ; 18
+ DW __PRINT_BRI ; 19
+ DW __PRINT_INV ; 20
+ DW __PRINT_OVR ; 21
+ DW __PRINT_AT ; 22 AT
+ DW __PRINT_TAB ; 23 TAB
+ ENDP
+ pop namespace
+#line 3 "/zxbasic/src/lib/arch/zx48k/runtime/copy_attr.asm"
+#line 4 "/zxbasic/src/lib/arch/zx48k/runtime/copy_attr.asm"
+ push namespace core
+COPY_ATTR:
+ ; Just copies current permanent attribs into temporal attribs
+ ; and sets print mode
+ PROC
+ LOCAL INVERSE1
+ LOCAL __REFRESH_TMP
+ INVERSE1 EQU 02Fh
+ ld hl, (ATTR_P)
+ ld (ATTR_T), hl
+ ld hl, FLAGS2
+ call __REFRESH_TMP
+ ld hl, P_FLAG
+ call __REFRESH_TMP
+__SET_ATTR_MODE: ; Another entry to set print modes. A contains (P_FLAG)
+ LOCAL TABLE
+ LOCAL CONT2
+ rra ; Over bit to carry
+ ld a, (FLAGS2)
+ rla ; Over bit in bit 1, Over2 bit in bit 2
+ and 3 ; Only bit 0 and 1 (OVER flag)
+ ld c, a
+ ld b, 0
+ ld hl, TABLE
+ add hl, bc
+ ld a, (hl)
+ ld (PRINT_MODE), a
+ ld hl, (P_FLAG)
+ xor a ; NOP -> INVERSE0
+ bit 2, l
+ jr z, CONT2
+ ld a, INVERSE1 ; CPL -> INVERSE1
+CONT2:
+ ld (INVERSE_MODE), a
+ ret
+TABLE:
+ nop ; NORMAL MODE
+ xor (hl) ; OVER 1 MODE
+ and (hl) ; OVER 2 MODE
+ or (hl) ; OVER 3 MODE
+#line 67 "/zxbasic/src/lib/arch/zx48k/runtime/copy_attr.asm"
+__REFRESH_TMP:
+ ld a, (hl)
+ and 0b10101010
+ ld c, a
+ rra
+ or c
+ ld (hl), a
+ ret
+ ENDP
+ pop namespace
+#line 24 "arch/zx48k/print_at2.bas"
+ END
diff --git a/tests/functional/arch/zx48k/print_at2.bas b/tests/functional/arch/zx48k/print_at2.bas
new file mode 100644
index 000000000..f2b3d0c14
--- /dev/null
+++ b/tests/functional/arch/zx48k/print_at2.bas
@@ -0,0 +1,3 @@
+DIM row, col as UByte
+PRINT AT row + 2, col + 1;
+
diff --git a/tests/functional/arch/zx48k/print_tab2.asm b/tests/functional/arch/zx48k/print_tab2.asm
new file mode 100644
index 000000000..f219ee8fb
--- /dev/null
+++ b/tests/functional/arch/zx48k/print_tab2.asm
@@ -0,0 +1,957 @@
+ org 32768
+.core.__START_PROGRAM:
+ di
+ push ix
+ push iy
+ exx
+ push hl
+ exx
+ ld (.core.__CALL_BACK__), sp
+ ei
+ call .core.__PRINT_INIT
+ jp .core.__MAIN_PROGRAM__
+.core.__CALL_BACK__:
+ DEFW 0
+.core.ZXBASIC_USER_DATA:
+ ; Defines USER DATA Length in bytes
+.core.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_END - .core.ZXBASIC_USER_DATA
+ .core.__LABEL__.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_LEN
+ .core.__LABEL__.ZXBASIC_USER_DATA EQU .core.ZXBASIC_USER_DATA
+_i:
+ DEFB 00
+.core.ZXBASIC_USER_DATA_END:
+.core.__MAIN_PROGRAM__:
+ call .core.COPY_ATTR
+ ld a, (_i)
+ add a, 2
+ call .core.PRINT_TAB
+ ld hl, 0
+ ld b, h
+ ld c, l
+.core.__END_PROGRAM:
+ di
+ ld hl, (.core.__CALL_BACK__)
+ ld sp, hl
+ exx
+ pop hl
+ exx
+ pop iy
+ pop ix
+ ei
+ ret
+ ;; --- end of user code ---
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/copy_attr.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+; vim:ts=4:sw=4:et:
+ ; PRINT command routine
+ ; Does not print attribute. Use PRINT_STR or PRINT_NUM for that
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/sposn.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/sysvars.asm"
+ ;; -----------------------------------------------------------------------
+ ;; ZX Basic System Vars
+ ;; Some of them will be mapped over Sinclair ROM ones for compatibility
+ ;; -----------------------------------------------------------------------
+ push namespace core
+SCREEN_ADDR: DW 16384 ; Screen address (can be pointed to other place to use a screen buffer)
+SCREEN_ATTR_ADDR: DW 22528 ; Screen attribute address (ditto.)
+ ; These are mapped onto ZX Spectrum ROM VARS
+ CHARS EQU 23606 ; Pointer to ROM/RAM Charset
+ TV_FLAG EQU 23612 ; Flags for controlling output to screen
+ UDG EQU 23675 ; Pointer to UDG Charset
+ COORDS EQU 23677 ; Last PLOT coordinates
+ FLAGS2 EQU 23681 ;
+ ECHO_E EQU 23682 ;
+ DFCC EQU 23684 ; Next screen addr for PRINT
+ DFCCL EQU 23686 ; Next screen attr for PRINT
+ S_POSN EQU 23688
+ ATTR_P EQU 23693 ; Current Permanent ATTRS set with INK, PAPER, etc commands
+ ATTR_T EQU 23695 ; temporary ATTRIBUTES
+ P_FLAG EQU 23697 ;
+ MEM0 EQU 23698 ; Temporary memory buffer used by ROM chars
+ SCR_COLS EQU 33 ; Screen with in columns + 1
+ SCR_ROWS EQU 24 ; Screen height in rows
+ SCR_SIZE EQU (SCR_ROWS << 8) + SCR_COLS
+ pop namespace
+#line 2 "/zxbasic/src/lib/arch/zx48k/runtime/sposn.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/attr.asm"
+ ; Attribute routines
+; vim:ts=4:et:sw:
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/error.asm"
+ ; Simple error control routines
+; vim:ts=4:et:
+ push namespace core
+ ERR_NR EQU 23610 ; Error code system variable
+ ; Error code definitions (as in ZX spectrum manual)
+; Set error code with:
+ ; ld a, ERROR_CODE
+ ; ld (ERR_NR), a
+ ERROR_Ok EQU -1
+ ERROR_SubscriptWrong EQU 2
+ ERROR_OutOfMemory EQU 3
+ ERROR_OutOfScreen EQU 4
+ ERROR_NumberTooBig EQU 5
+ ERROR_InvalidArg EQU 9
+ ERROR_IntOutOfRange EQU 10
+ ERROR_NonsenseInBasic EQU 11
+ ERROR_InvalidFileName EQU 14
+ ERROR_InvalidColour EQU 19
+ ERROR_BreakIntoProgram EQU 20
+ ERROR_TapeLoadingErr EQU 26
+ ; Raises error using RST #8
+__ERROR:
+ ld (__ERROR_CODE), a
+ rst 8
+__ERROR_CODE:
+ nop
+ ret
+ ; Sets the error system variable, but keeps running.
+ ; Usually this instruction if followed by the END intermediate instruction.
+__STOP:
+ ld (ERR_NR), a
+ ret
+ pop namespace
+#line 6 "/zxbasic/src/lib/arch/zx48k/runtime/attr.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/in_screen.asm"
+ push namespace core
+__IN_SCREEN:
+ ; Returns NO carry if current coords (D, E)
+ ; are OUT of the screen limits
+ PROC
+ LOCAL __IN_SCREEN_ERR
+ ld hl, SCR_SIZE
+ ld a, e
+ cp l
+ jr nc, __IN_SCREEN_ERR ; Do nothing and return if out of range
+ ld a, d
+ cp h
+ ret c ; Return if carry (OK)
+__IN_SCREEN_ERR:
+__OUT_OF_SCREEN_ERR:
+ ; Jumps here if out of screen
+ ld a, ERROR_OutOfScreen
+ jp __STOP ; Saves error code and exits
+ ENDP
+ pop namespace
+#line 7 "/zxbasic/src/lib/arch/zx48k/runtime/attr.asm"
+ push namespace core
+__ATTR_ADDR:
+ ; calc start address in DE (as (32 * d) + e)
+ ; Contributed by Santiago Romero at http://www.speccy.org
+ ld h, 0 ; 7 T-States
+ ld a, d ; 4 T-States
+ ld d, h
+ add a, a ; a * 2 ; 4 T-States
+ add a, a ; a * 4 ; 4 T-States
+ ld l, a ; HL = A * 4 ; 4 T-States
+ add hl, hl ; HL = A * 8 ; 15 T-States
+ add hl, hl ; HL = A * 16 ; 15 T-States
+ add hl, hl ; HL = A * 32 ; 15 T-States
+ add hl, de
+ ld de, (SCREEN_ATTR_ADDR) ; Adds the screen address
+ add hl, de
+ ; Return current screen address in HL
+ ret
+ ; Sets the attribute at a given screen coordinate (D, E).
+ ; The attribute is taken from the ATTR_T memory variable
+ ; Used by PRINT routines
+SET_ATTR:
+ ; Checks for valid coords
+ call __IN_SCREEN
+ ret nc
+ call __ATTR_ADDR
+__SET_ATTR:
+ ; Internal __FASTCALL__ Entry used by printing routines
+ ; HL contains the address of the ATTR cell to set
+ PROC
+__SET_ATTR2: ; Sets attr from ATTR_T to (HL) which points to the scr address
+ ld de, (ATTR_T) ; E = ATTR_T, D = MASK_T
+ ld a, d
+ and (hl)
+ ld c, a ; C = current screen color, masked
+ ld a, d
+ cpl ; Negate mask
+ and e ; Mask current attributes
+ or c ; Mix them
+ ld (hl), a ; Store result in screen
+ ret
+ ENDP
+ pop namespace
+#line 3 "/zxbasic/src/lib/arch/zx48k/runtime/sposn.asm"
+ ; Printing positioning library.
+ push namespace core
+ ; Loads into DE current ROW, COL print position from S_POSN mem var.
+__LOAD_S_POSN:
+ PROC
+ ld de, (S_POSN)
+ ld hl, SCR_SIZE
+ or a
+ sbc hl, de
+ ex de, hl
+ ret
+ ENDP
+ ; Saves ROW, COL from DE into S_POSN mem var.
+__SAVE_S_POSN:
+ PROC
+ ld hl, SCR_SIZE
+ or a
+ sbc hl, de
+ ld (S_POSN), hl ; saves it again
+__SET_SCR_PTR: ;; Fast
+ push de
+ call __ATTR_ADDR
+ ld (DFCCL), hl
+ pop de
+ ld a, d
+ ld c, a ; Saves it for later
+ and 0F8h ; Masks 3 lower bit ; zy
+ ld d, a
+ ld a, c ; Recovers it
+ and 07h ; MOD 7 ; y1
+ rrca
+ rrca
+ rrca
+ or e
+ ld e, a
+ ld hl, (SCREEN_ADDR)
+ add hl, de ; HL = Screen address + DE
+ ld (DFCC), hl
+ ret
+ ENDP
+ pop namespace
+#line 6 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/table_jump.asm"
+ push namespace core
+JUMP_HL_PLUS_2A: ; Does JP (HL + A*2) Modifies DE. Modifies A
+ add a, a
+JUMP_HL_PLUS_A: ; Does JP (HL + A) Modifies DE
+ ld e, a
+ ld d, 0
+JUMP_HL_PLUS_DE: ; Does JP (HL + DE)
+ add hl, de
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ ex de, hl
+CALL_HL:
+ jp (hl)
+ pop namespace
+#line 8 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/ink.asm"
+ ; Sets ink color in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+INK:
+ PROC
+ LOCAL __SET_INK
+ LOCAL __SET_INK2
+ ld de, ATTR_P
+__SET_INK:
+ cp 8
+ jr nz, __SET_INK2
+ inc de ; Points DE to MASK_T or MASK_P
+ ld a, (de)
+ or 7 ; Set bits 0,1,2 to enable transparency
+ ld (de), a
+ ret
+__SET_INK2:
+ ; Another entry. This will set the ink color at location pointer by DE
+ and 7 ; # Gets color mod 8
+ ld b, a ; Saves the color
+ ld a, (de)
+ and 0F8h ; Clears previous value
+ or b
+ ld (de), a
+ inc de ; Points DE to MASK_T or MASK_P
+ ld a, (de)
+ and 0F8h ; Reset bits 0,1,2 sign to disable transparency
+ ld (de), a ; Store new attr
+ ret
+ ; Sets the INK color passed in A register in the ATTR_T variable
+INK_TMP:
+ ld de, ATTR_T
+ jp __SET_INK
+ ENDP
+ pop namespace
+#line 9 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/paper.asm"
+ ; Sets paper color in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+PAPER:
+ PROC
+ LOCAL __SET_PAPER
+ LOCAL __SET_PAPER2
+ ld de, ATTR_P
+__SET_PAPER:
+ cp 8
+ jr nz, __SET_PAPER2
+ inc de
+ ld a, (de)
+ or 038h
+ ld (de), a
+ ret
+ ; Another entry. This will set the paper color at location pointer by DE
+__SET_PAPER2:
+ and 7 ; # Remove
+ rlca
+ rlca
+ rlca ; a *= 8
+ ld b, a ; Saves the color
+ ld a, (de)
+ and 0C7h ; Clears previous value
+ or b
+ ld (de), a
+ inc de ; Points to MASK_T or MASK_P accordingly
+ ld a, (de)
+ and 0C7h ; Resets bits 3,4,5
+ ld (de), a
+ ret
+ ; Sets the PAPER color passed in A register in the ATTR_T variable
+PAPER_TMP:
+ ld de, ATTR_T
+ jp __SET_PAPER
+ ENDP
+ pop namespace
+#line 10 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/flash.asm"
+ ; Sets flash flag in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+FLASH:
+ ld hl, ATTR_P
+ PROC
+ LOCAL IS_TR
+ LOCAL IS_ZERO
+__SET_FLASH:
+ ; Another entry. This will set the flash flag at location pointer by DE
+ cp 8
+ jr z, IS_TR
+ ; # Convert to 0/1
+ or a
+ jr z, IS_ZERO
+ ld a, 0x80
+IS_ZERO:
+ ld b, a ; Saves the color
+ ld a, (hl)
+ and 07Fh ; Clears previous value
+ or b
+ ld (hl), a
+ inc hl
+ res 7, (hl) ;Reset bit 7 to disable transparency
+ ret
+IS_TR: ; transparent
+ inc hl ; Points DE to MASK_T or MASK_P
+ set 7, (hl) ;Set bit 7 to enable transparency
+ ret
+ ; Sets the FLASH flag passed in A register in the ATTR_T variable
+FLASH_TMP:
+ ld hl, ATTR_T
+ jr __SET_FLASH
+ ENDP
+ pop namespace
+#line 11 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/bright.asm"
+ ; Sets bright flag in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+BRIGHT:
+ ld hl, ATTR_P
+ PROC
+ LOCAL IS_TR
+ LOCAL IS_ZERO
+__SET_BRIGHT:
+ ; Another entry. This will set the bright flag at location pointer by DE
+ cp 8
+ jr z, IS_TR
+ ; # Convert to 0/1
+ or a
+ jr z, IS_ZERO
+ ld a, 0x40
+IS_ZERO:
+ ld b, a ; Saves the color
+ ld a, (hl)
+ and 0BFh ; Clears previous value
+ or b
+ ld (hl), a
+ inc hl
+ res 6, (hl) ;Reset bit 6 to disable transparency
+ ret
+IS_TR: ; transparent
+ inc hl ; Points DE to MASK_T or MASK_P
+ set 6, (hl) ;Set bit 6 to enable transparency
+ ret
+ ; Sets the BRIGHT flag passed in A register in the ATTR_T variable
+BRIGHT_TMP:
+ ld hl, ATTR_T
+ jr __SET_BRIGHT
+ ENDP
+ pop namespace
+#line 12 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/over.asm"
+ ; Sets OVER flag in P_FLAG permanently
+; Parameter: OVER flag in bit 0 of A register
+ push namespace core
+OVER:
+ PROC
+ ld c, a ; saves it for later
+ and 2
+ ld hl, FLAGS2
+ res 1, (HL)
+ or (hl)
+ ld (hl), a
+ ld a, c ; Recovers previous value
+ and 1 ; # Convert to 0/1
+ add a, a; # Shift left 1 bit for permanent
+ ld hl, P_FLAG
+ res 1, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets OVER flag in P_FLAG temporarily
+OVER_TMP:
+ ld c, a ; saves it for later
+ and 2 ; gets bit 1; clears carry
+ rra
+ ld hl, FLAGS2
+ res 0, (hl)
+ or (hl)
+ ld (hl), a
+ ld a, c ; Recovers previous value
+ and 1
+ ld hl, P_FLAG
+ res 0, (hl)
+ or (hl)
+ ld (hl), a
+ jp __SET_ATTR_MODE
+ ENDP
+ pop namespace
+#line 13 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/inverse.asm"
+ ; Sets INVERSE flag in P_FLAG permanently
+; Parameter: INVERSE flag in bit 0 of A register
+ push namespace core
+INVERSE:
+ PROC
+ and 1 ; # Convert to 0/1
+ add a, a; # Shift left 3 bits for permanent
+ add a, a
+ add a, a
+ ld hl, P_FLAG
+ res 3, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets INVERSE flag in P_FLAG temporarily
+INVERSE_TMP:
+ and 1
+ add a, a
+ add a, a; # Shift left 2 bits for temporary
+ ld hl, P_FLAG
+ res 2, (hl)
+ or (hl)
+ ld (hl), a
+ jp __SET_ATTR_MODE
+ ENDP
+ pop namespace
+#line 14 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/bold.asm"
+ ; Sets BOLD flag in P_FLAG permanently
+; Parameter: BOLD flag in bit 0 of A register
+ push namespace core
+BOLD:
+ PROC
+ and 1
+ rlca
+ rlca
+ rlca
+ ld hl, FLAGS2
+ res 3, (HL)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets BOLD flag in P_FLAG temporarily
+BOLD_TMP:
+ and 1
+ rlca
+ rlca
+ ld hl, FLAGS2
+ res 2, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ENDP
+ pop namespace
+#line 15 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zx48k/runtime/italic.asm"
+ ; Sets ITALIC flag in P_FLAG permanently
+; Parameter: ITALIC flag in bit 0 of A register
+ push namespace core
+ITALIC:
+ PROC
+ and 1
+ rrca
+ rrca
+ rrca
+ ld hl, FLAGS2
+ res 5, (HL)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets ITALIC flag in P_FLAG temporarily
+ITALIC_TMP:
+ and 1
+ rrca
+ rrca
+ rrca
+ rrca
+ ld hl, FLAGS2
+ res 4, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ENDP
+ pop namespace
+#line 16 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ ; Putting a comment starting with @INIT
+ ; will make the compiler to add a CALL to
+ ; It is useful for initialization routines.
+ push namespace core
+__PRINT_INIT: ; To be called before program starts (initializes library)
+ PROC
+ ld hl, __PRINT_START
+ ld (PRINT_JUMP_STATE), hl
+ ;; Clears ATTR2 flags (OVER 2, etc)
+ xor a
+ ld (FLAGS2), a
+ ld hl, TV_FLAG
+ res 0, (hl)
+ LOCAL SET_SCR_ADDR
+ call __LOAD_S_POSN
+ jp __SET_SCR_PTR
+ ;; Receives HL = future value of S_POSN
+ ;; Stores it at (S_POSN) and refresh screen pointers (ATTR, SCR)
+SET_SCR_ADDR:
+ ld (S_POSN), hl
+ ex de, hl
+ ld hl, SCR_SIZE
+ or a
+ sbc hl, de
+ ex de, hl
+ dec e
+ jp __SET_SCR_PTR
+__PRINTCHAR: ; Print character store in accumulator (A register)
+ ; Modifies H'L', B'C', A'F', D'E', A
+ LOCAL PO_GR_1
+ LOCAL __PRCHAR
+ LOCAL __PRINT_JUMP
+ LOCAL __SRCADDR
+ LOCAL __PRINT_UDG
+ LOCAL __PRGRAPH
+ LOCAL __PRINT_START
+ PRINT_JUMP_STATE EQU __PRINT_JUMP + 2
+__PRINT_JUMP:
+ exx ; Switch to alternative registers
+ jp __PRINT_START ; Where to jump. If we print 22 (AT), next two calls jumps to AT1 and AT2 respectively
+__PRINT_START:
+__PRINT_CHR:
+ cp ' '
+ jr c, __PRINT_SPECIAL ; Characters below ' ' are special ones
+ ex af, af' ; Saves a value (char to print) for later
+ ld hl, (S_POSN)
+ dec l
+ jr nz, 1f
+ ld l, SCR_COLS - 1
+ dec h
+ jr nz, 2f
+ inc h
+ push hl
+ call __SCROLL_SCR
+ pop hl
+#line 94 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+2:
+ call SET_SCR_ADDR
+ jr 4f
+1:
+ ld (S_POSN), hl
+4:
+ ex af, af'
+ cp 80h ; Is it a "normal" (printable) char
+ jr c, __SRCADDR
+ cp 90h ; Is it an UDG?
+ jr nc, __PRINT_UDG
+ ; Print an 8 bit pattern (80h to 8Fh)
+ ld b, a
+ call PO_GR_1 ; This ROM routine will generate the bit pattern at MEM0
+ ld hl, MEM0
+ jp __PRGRAPH
+ PO_GR_1 EQU 0B38h
+__PRINT_UDG:
+ sub 90h ; Sub ASC code
+ ld bc, (UDG)
+ jr __PRGRAPH0
+ __SOURCEADDR EQU (__SRCADDR + 1) ; Address of the pointer to chars source
+__SRCADDR:
+ ld bc, (CHARS)
+__PRGRAPH0:
+ add a, a ; A = a * 2 (since a < 80h) ; Thanks to Metalbrain at http://foro.speccy.org
+ ld l, a
+ ld h, 0 ; HL = a * 2 (accumulator)
+ add hl, hl
+ add hl, hl ; HL = a * 8
+ add hl, bc ; HL = CHARS address
+__PRGRAPH:
+ ex de, hl ; HL = Write Address, DE = CHARS address
+ bit 2, (iy + $47)
+ call nz, __BOLD
+#line 141 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ bit 4, (iy + $47)
+ call nz, __ITALIC
+#line 146 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ ld hl, (DFCC)
+ push hl
+ ld b, 8 ; 8 bytes per char
+__PRCHAR:
+ ld a, (de) ; DE *must* be source, and HL destiny
+PRINT_MODE: ; Which operation is used to write on the screen
+ ; Set it with:
+ ; LD A,
+ ; LD (PRINT_MODE), A
+ ;
+ ; Available operations:
+ ; NORMAL : 0h --> NOP ; OVER 0
+ ; XOR : AEh --> XOR (HL) ; OVER 1
+ ; OR : B6h --> OR (HL) ; PUTSPRITE
+ ; AND : A6h --> AND (HL) ; PUTMASK
+ nop ; Set to one of the values above
+INVERSE_MODE: ; 00 -> NOP -> INVERSE 0
+ nop ; 2F -> CPL -> INVERSE 1
+ ld (hl), a
+ inc de
+ inc h ; Next line
+ djnz __PRCHAR
+ pop hl
+ inc hl
+ ld (DFCC), hl
+ ld hl, (DFCCL) ; current ATTR Pos
+ inc hl
+ ld (DFCCL), hl
+ dec hl
+ call __SET_ATTR
+ exx
+ ret
+ ; ------------- SPECIAL CHARS (< 32) -----------------
+__PRINT_SPECIAL: ; Jumps here if it is a special char
+ ld hl, __PRINT_TABLE
+ jp JUMP_HL_PLUS_2A
+PRINT_EOL: ; Called WHENEVER there is no ";" at end of PRINT sentence
+ exx
+__PRINT_0Dh: ; Called WHEN printing CHR$(13)
+ ld hl, (S_POSN)
+ dec l
+ jr nz, 1f
+ dec h
+ jr nz, 1f
+ inc h
+ push hl
+ call __SCROLL_SCR
+ pop hl
+#line 211 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+1:
+ ld l, 1
+__PRINT_EOL_END:
+ call SET_SCR_ADDR
+ exx
+ ret
+__PRINT_COM:
+ exx
+ push hl
+ push de
+ push bc
+ call PRINT_COMMA
+ pop bc
+ pop de
+ pop hl
+ ret
+__PRINT_TAB:
+ ld hl, __PRINT_TAB1
+ jr __PRINT_SET_STATE
+__PRINT_TAB1:
+ ld (MEM0), a
+ ld hl, __PRINT_TAB2
+ jr __PRINT_SET_STATE
+__PRINT_TAB2:
+ ld a, (MEM0) ; Load tab code (ignore the current one)
+ ld hl, __PRINT_START
+ ld (PRINT_JUMP_STATE), hl
+ exx
+ push hl
+ push bc
+ push de
+ call PRINT_TAB
+ pop de
+ pop bc
+ pop hl
+ ret
+__PRINT_AT:
+ ld hl, __PRINT_AT1
+ jr __PRINT_SET_STATE
+__PRINT_NOP:
+__PRINT_RESTART:
+ ld hl, __PRINT_START
+__PRINT_SET_STATE:
+ ld (PRINT_JUMP_STATE), hl ; Saves next entry call
+ exx
+ ret
+__PRINT_AT1: ; Jumps here if waiting for 1st parameter
+ ld hl, (S_POSN)
+ ld h, a
+ ld a, SCR_ROWS
+ sub h
+ ld (S_POSN + 1), a
+ ld hl, __PRINT_AT2
+ jr __PRINT_SET_STATE
+__PRINT_AT2:
+ call __LOAD_S_POSN
+ ld e, a
+ call __SAVE_S_POSN
+ jr __PRINT_RESTART
+__PRINT_DEL:
+ call __LOAD_S_POSN ; Gets current screen position
+ dec e
+ ld a, -1
+ cp e
+ jr nz, 3f
+ ld e, SCR_COLS - 2
+ dec d
+ cp d
+ jr nz, 3f
+ ld d, SCR_ROWS - 1
+3:
+ call __SAVE_S_POSN
+ exx
+ ret
+__PRINT_INK:
+ ld hl, __PRINT_INK2
+ jr __PRINT_SET_STATE
+__PRINT_INK2:
+ call INK_TMP
+ jr __PRINT_RESTART
+__PRINT_PAP:
+ ld hl, __PRINT_PAP2
+ jr __PRINT_SET_STATE
+__PRINT_PAP2:
+ call PAPER_TMP
+ jr __PRINT_RESTART
+__PRINT_FLA:
+ ld hl, __PRINT_FLA2
+ jr __PRINT_SET_STATE
+__PRINT_FLA2:
+ call FLASH_TMP
+ jr __PRINT_RESTART
+__PRINT_BRI:
+ ld hl, __PRINT_BRI2
+ jr __PRINT_SET_STATE
+__PRINT_BRI2:
+ call BRIGHT_TMP
+ jr __PRINT_RESTART
+__PRINT_INV:
+ ld hl, __PRINT_INV2
+ jr __PRINT_SET_STATE
+__PRINT_INV2:
+ call INVERSE_TMP
+ jr __PRINT_RESTART
+__PRINT_OVR:
+ ld hl, __PRINT_OVR2
+ jr __PRINT_SET_STATE
+__PRINT_OVR2:
+ call OVER_TMP
+ jr __PRINT_RESTART
+__PRINT_BOLD:
+ ld hl, __PRINT_BOLD2
+ jp __PRINT_SET_STATE
+__PRINT_BOLD2:
+ call BOLD_TMP
+ jp __PRINT_RESTART
+#line 355 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+__PRINT_ITA:
+ ld hl, __PRINT_ITA2
+ jp __PRINT_SET_STATE
+__PRINT_ITA2:
+ call ITALIC_TMP
+ jp __PRINT_RESTART
+#line 365 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ LOCAL __BOLD
+__BOLD:
+ push hl
+ ld hl, MEM0
+ ld b, 8
+1:
+ ld a, (de)
+ ld c, a
+ rlca
+ or c
+ ld (hl), a
+ inc hl
+ inc de
+ djnz 1b
+ pop hl
+ ld de, MEM0
+ ret
+#line 386 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ LOCAL __ITALIC
+__ITALIC:
+ push hl
+ ld hl, MEM0
+ ex de, hl
+ ld bc, 8
+ ldir
+ ld hl, MEM0
+ srl (hl)
+ inc hl
+ srl (hl)
+ inc hl
+ srl (hl)
+ inc hl
+ inc hl
+ inc hl
+ sla (hl)
+ inc hl
+ sla (hl)
+ inc hl
+ sla (hl)
+ pop hl
+ ld de, MEM0
+ ret
+#line 414 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ LOCAL __SCROLL_SCR
+#line 488 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ __SCROLL_SCR EQU 0DFEh ; Use ROM SCROLL
+#line 490 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+#line 491 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+PRINT_COMMA:
+ call __LOAD_S_POSN
+ ld a, e
+ and 16
+ add a, 16
+PRINT_TAB:
+ ; Tabulates the number of spaces in A register
+ ; If the current cursor position is already A, does nothing
+ PROC
+ LOCAL LOOP
+ call __LOAD_S_POSN ; e = current row
+ sub e
+ and 31
+ ret z
+ ld b, a
+LOOP:
+ ld a, ' '
+ call __PRINTCHAR
+ djnz LOOP
+ ret
+ ENDP
+PRINT_AT: ; Changes cursor to ROW, COL
+ ; COL in A register
+ ; ROW in stack
+ pop hl ; Ret address
+ ex (sp), hl ; callee H = ROW
+ ld l, a
+ ex de, hl
+ call __IN_SCREEN
+ ret nc ; Return if out of screen
+ jp __SAVE_S_POSN
+ LOCAL __PRINT_COM
+ LOCAL __PRINT_AT1
+ LOCAL __PRINT_AT2
+ LOCAL __PRINT_BOLD
+ LOCAL __PRINT_ITA
+ LOCAL __PRINT_INK
+ LOCAL __PRINT_PAP
+ LOCAL __PRINT_SET_STATE
+ LOCAL __PRINT_TABLE
+ LOCAL __PRINT_TAB, __PRINT_TAB1, __PRINT_TAB2
+ LOCAL __PRINT_ITA2
+#line 547 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+ LOCAL __PRINT_BOLD2
+#line 553 "/zxbasic/src/lib/arch/zx48k/runtime/print.asm"
+__PRINT_TABLE: ; Jump table for 0 .. 22 codes
+ DW __PRINT_NOP ; 0
+ DW __PRINT_NOP ; 1
+ DW __PRINT_NOP ; 2
+ DW __PRINT_NOP ; 3
+ DW __PRINT_NOP ; 4
+ DW __PRINT_NOP ; 5
+ DW __PRINT_COM ; 6 COMMA
+ DW __PRINT_NOP ; 7
+ DW __PRINT_DEL ; 8 DEL
+ DW __PRINT_NOP ; 9
+ DW __PRINT_NOP ; 10
+ DW __PRINT_NOP ; 11
+ DW __PRINT_NOP ; 12
+ DW __PRINT_0Dh ; 13
+ DW __PRINT_BOLD ; 14
+ DW __PRINT_ITA ; 15
+ DW __PRINT_INK ; 16
+ DW __PRINT_PAP ; 17
+ DW __PRINT_FLA ; 18
+ DW __PRINT_BRI ; 19
+ DW __PRINT_INV ; 20
+ DW __PRINT_OVR ; 21
+ DW __PRINT_AT ; 22 AT
+ DW __PRINT_TAB ; 23 TAB
+ ENDP
+ pop namespace
+#line 3 "/zxbasic/src/lib/arch/zx48k/runtime/copy_attr.asm"
+#line 4 "/zxbasic/src/lib/arch/zx48k/runtime/copy_attr.asm"
+ push namespace core
+COPY_ATTR:
+ ; Just copies current permanent attribs into temporal attribs
+ ; and sets print mode
+ PROC
+ LOCAL INVERSE1
+ LOCAL __REFRESH_TMP
+ INVERSE1 EQU 02Fh
+ ld hl, (ATTR_P)
+ ld (ATTR_T), hl
+ ld hl, FLAGS2
+ call __REFRESH_TMP
+ ld hl, P_FLAG
+ call __REFRESH_TMP
+__SET_ATTR_MODE: ; Another entry to set print modes. A contains (P_FLAG)
+ LOCAL TABLE
+ LOCAL CONT2
+ rra ; Over bit to carry
+ ld a, (FLAGS2)
+ rla ; Over bit in bit 1, Over2 bit in bit 2
+ and 3 ; Only bit 0 and 1 (OVER flag)
+ ld c, a
+ ld b, 0
+ ld hl, TABLE
+ add hl, bc
+ ld a, (hl)
+ ld (PRINT_MODE), a
+ ld hl, (P_FLAG)
+ xor a ; NOP -> INVERSE0
+ bit 2, l
+ jr z, CONT2
+ ld a, INVERSE1 ; CPL -> INVERSE1
+CONT2:
+ ld (INVERSE_MODE), a
+ ret
+TABLE:
+ nop ; NORMAL MODE
+ xor (hl) ; OVER 1 MODE
+ and (hl) ; OVER 2 MODE
+ or (hl) ; OVER 3 MODE
+#line 67 "/zxbasic/src/lib/arch/zx48k/runtime/copy_attr.asm"
+__REFRESH_TMP:
+ ld a, (hl)
+ and 0b10101010
+ ld c, a
+ rra
+ or c
+ ld (hl), a
+ ret
+ ENDP
+ pop namespace
+#line 21 "arch/zx48k/print_tab2.bas"
+ END
diff --git a/tests/functional/arch/zx48k/print_tab2.bas b/tests/functional/arch/zx48k/print_tab2.bas
new file mode 100644
index 000000000..f49690308
--- /dev/null
+++ b/tests/functional/arch/zx48k/print_tab2.bas
@@ -0,0 +1,4 @@
+DIM i As UByte
+
+PRINT TAB i + 2;
+
diff --git a/tests/functional/arch/zxnext/print_at2.asm b/tests/functional/arch/zxnext/print_at2.asm
new file mode 100644
index 000000000..3cb99fc78
--- /dev/null
+++ b/tests/functional/arch/zxnext/print_at2.asm
@@ -0,0 +1,955 @@
+ org 32768
+.core.__START_PROGRAM:
+ di
+ push iy
+ ld iy, 0x5C3A ; ZX Spectrum ROM variables address
+ ld (.core.__CALL_BACK__), sp
+ ei
+ call .core.__PRINT_INIT
+ jp .core.__MAIN_PROGRAM__
+.core.__CALL_BACK__:
+ DEFW 0
+.core.ZXBASIC_USER_DATA:
+ ; Defines USER DATA Length in bytes
+.core.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_END - .core.ZXBASIC_USER_DATA
+ .core.__LABEL__.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_LEN
+ .core.__LABEL__.ZXBASIC_USER_DATA EQU .core.ZXBASIC_USER_DATA
+_row:
+ DEFB 00
+_col:
+ DEFB 00
+.core.ZXBASIC_USER_DATA_END:
+.core.__MAIN_PROGRAM__:
+ call .core.COPY_ATTR
+ ld a, (_row)
+ add a, 2
+ push af
+ ld a, (_col)
+ inc a
+ call .core.PRINT_AT
+ ld hl, 0
+ ld b, h
+ ld c, l
+.core.__END_PROGRAM:
+ di
+ ld hl, (.core.__CALL_BACK__)
+ ld sp, hl
+ pop iy
+ ei
+ ret
+ ;; --- end of user code ---
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/copy_attr.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+; vim:ts=4:sw=4:et:
+ ; PRINT command routine
+ ; Does not print attribute. Use PRINT_STR or PRINT_NUM for that
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/sposn.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/sysvars.asm"
+ ;; -----------------------------------------------------------------------
+ ;; ZX Basic System Vars
+ ;; Some of them will be mapped over Sinclair ROM ones for compatibility
+ ;; -----------------------------------------------------------------------
+ push namespace core
+SCREEN_ADDR: DW 16384 ; Screen address (can be pointed to other place to use a screen buffer)
+SCREEN_ATTR_ADDR: DW 22528 ; Screen attribute address (ditto.)
+ ; These are mapped onto ZX Spectrum ROM VARS
+ CHARS EQU 23606 ; Pointer to ROM/RAM Charset
+ TV_FLAG EQU 23612 ; TV Flags
+ UDG EQU 23675 ; Pointer to UDG Charset
+ COORDS EQU 23677 ; Last PLOT coordinates
+ FLAGS2 EQU 23681 ;
+ ECHO_E EQU 23682 ;
+ DFCC EQU 23684 ; Next screen addr for PRINT
+ DFCCL EQU 23686 ; Next screen attr for PRINT
+ S_POSN EQU 23688
+ ATTR_P EQU 23693 ; Current Permanent ATTRS set with INK, PAPER, etc commands
+ ATTR_T EQU 23695 ; temporary ATTRIBUTES
+ P_FLAG EQU 23697 ;
+ MEM0 EQU 23698 ; Temporary memory buffer used by ROM chars
+ SCR_COLS EQU 33 ; Screen with in columns + 1
+ SCR_ROWS EQU 24 ; Screen height in rows
+ SCR_SIZE EQU (SCR_ROWS << 8) + SCR_COLS
+ pop namespace
+#line 2 "/zxbasic/src/lib/arch/zxnext/runtime/sposn.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/attr.asm"
+ ; Attribute routines
+; vim:ts=4:et:sw:
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/error.asm"
+ ; Simple error control routines
+; vim:ts=4:et:
+ push namespace core
+ ERR_NR EQU 23610 ; Error code system variable
+ ; Error code definitions (as in ZX spectrum manual)
+; Set error code with:
+ ; ld a, ERROR_CODE
+ ; ld (ERR_NR), a
+ ERROR_Ok EQU -1
+ ERROR_SubscriptWrong EQU 2
+ ERROR_OutOfMemory EQU 3
+ ERROR_OutOfScreen EQU 4
+ ERROR_NumberTooBig EQU 5
+ ERROR_InvalidArg EQU 9
+ ERROR_IntOutOfRange EQU 10
+ ERROR_NonsenseInBasic EQU 11
+ ERROR_InvalidFileName EQU 14
+ ERROR_InvalidColour EQU 19
+ ERROR_BreakIntoProgram EQU 20
+ ERROR_TapeLoadingErr EQU 26
+ ; Raises error using RST #8
+__ERROR:
+ ld (__ERROR_CODE), a
+ rst 8
+__ERROR_CODE:
+ nop
+ ret
+ ; Sets the error system variable, but keeps running.
+ ; Usually this instruction if followed by the END intermediate instruction.
+__STOP:
+ ld (ERR_NR), a
+ ret
+ pop namespace
+#line 6 "/zxbasic/src/lib/arch/zxnext/runtime/attr.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/in_screen.asm"
+ push namespace core
+__IN_SCREEN:
+ ; Returns NO carry if current coords (D, E)
+ ; are OUT of the screen limits
+ PROC
+ LOCAL __IN_SCREEN_ERR
+ ld hl, SCR_SIZE
+ ld a, e
+ cp l
+ jr nc, __IN_SCREEN_ERR ; Do nothing and return if out of range
+ ld a, d
+ cp h
+ ret c ; Return if carry (OK)
+__IN_SCREEN_ERR:
+__OUT_OF_SCREEN_ERR:
+ ; Jumps here if out of screen
+ ld a, ERROR_OutOfScreen
+ jp __STOP ; Saves error code and exits
+ ENDP
+ pop namespace
+#line 7 "/zxbasic/src/lib/arch/zxnext/runtime/attr.asm"
+ push namespace core
+__ATTR_ADDR:
+ ; calc start address in DE (as (32 * d) + e)
+ ; Contributed by Santiago Romero at http://www.speccy.org
+ ld h, 0 ; 7 T-States
+ ld a, d ; 4 T-States
+ ld d, h
+ add a, a ; a * 2 ; 4 T-States
+ add a, a ; a * 4 ; 4 T-States
+ ld l, a ; HL = A * 4 ; 4 T-States
+ add hl, hl ; HL = A * 8 ; 15 T-States
+ add hl, hl ; HL = A * 16 ; 15 T-States
+ add hl, hl ; HL = A * 32 ; 15 T-States
+ add hl, de
+ ld de, (SCREEN_ATTR_ADDR) ; Adds the screen address
+ add hl, de
+ ; Return current screen address in HL
+ ret
+ ; Sets the attribute at a given screen coordinate (D, E).
+ ; The attribute is taken from the ATTR_T memory variable
+ ; Used by PRINT routines
+SET_ATTR:
+ ; Checks for valid coords
+ call __IN_SCREEN
+ ret nc
+ call __ATTR_ADDR
+__SET_ATTR:
+ ; Internal __FASTCALL__ Entry used by printing routines
+ ; HL contains the address of the ATTR cell to set
+ PROC
+__SET_ATTR2: ; Sets attr from ATTR_T to (HL) which points to the scr address
+ ld de, (ATTR_T) ; E = ATTR_T, D = MASK_T
+ ld a, d
+ and (hl)
+ ld c, a ; C = current screen color, masked
+ ld a, d
+ cpl ; Negate mask
+ and e ; Mask current attributes
+ or c ; Mix them
+ ld (hl), a ; Store result in screen
+ ret
+ ENDP
+ pop namespace
+#line 3 "/zxbasic/src/lib/arch/zxnext/runtime/sposn.asm"
+ ; Printing positioning library.
+ push namespace core
+ ; Loads into DE current ROW, COL print position from S_POSN mem var.
+__LOAD_S_POSN:
+ PROC
+ ld de, (S_POSN)
+ ld hl, SCR_SIZE
+ or a
+ sbc hl, de
+ ex de, hl
+ ret
+ ENDP
+ ; Saves ROW, COL from DE into S_POSN mem var.
+__SAVE_S_POSN:
+ PROC
+ ld hl, SCR_SIZE
+ or a
+ sbc hl, de
+ ld (S_POSN), hl ; saves it again
+__SET_SCR_PTR: ;; Fast
+ push de
+ call __ATTR_ADDR
+ ld (DFCCL), hl
+ pop de
+ ld a, d
+ ld c, a ; Saves it for later
+ and 0F8h ; Masks 3 lower bit ; zy
+ ld d, a
+ ld a, c ; Recovers it
+ and 07h ; MOD 7 ; y1
+ rrca
+ rrca
+ rrca
+ or e
+ ld e, a
+ ld hl, (SCREEN_ADDR)
+ add hl, de ; HL = Screen address + DE
+ ld (DFCC), hl
+ ret
+ ENDP
+ pop namespace
+#line 6 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/table_jump.asm"
+ push namespace core
+JUMP_HL_PLUS_2A: ; Does JP (HL + A*2) Modifies DE. Modifies A
+ add a, a
+JUMP_HL_PLUS_A: ; Does JP (HL + A) Modifies DE
+ ld e, a
+ ld d, 0
+JUMP_HL_PLUS_DE: ; Does JP (HL + DE)
+ add hl, de
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ ex de, hl
+CALL_HL:
+ jp (hl)
+ pop namespace
+#line 8 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/ink.asm"
+ ; Sets ink color in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+INK:
+ PROC
+ LOCAL __SET_INK
+ LOCAL __SET_INK2
+ ld de, ATTR_P
+__SET_INK:
+ cp 8
+ jr nz, __SET_INK2
+ inc de ; Points DE to MASK_T or MASK_P
+ ld a, (de)
+ or 7 ; Set bits 0,1,2 to enable transparency
+ ld (de), a
+ ret
+__SET_INK2:
+ ; Another entry. This will set the ink color at location pointer by DE
+ and 7 ; # Gets color mod 8
+ ld b, a ; Saves the color
+ ld a, (de)
+ and 0F8h ; Clears previous value
+ or b
+ ld (de), a
+ inc de ; Points DE to MASK_T or MASK_P
+ ld a, (de)
+ and 0F8h ; Reset bits 0,1,2 sign to disable transparency
+ ld (de), a ; Store new attr
+ ret
+ ; Sets the INK color passed in A register in the ATTR_T variable
+INK_TMP:
+ ld de, ATTR_T
+ jp __SET_INK
+ ENDP
+ pop namespace
+#line 9 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/paper.asm"
+ ; Sets paper color in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+PAPER:
+ PROC
+ LOCAL __SET_PAPER
+ LOCAL __SET_PAPER2
+ ld de, ATTR_P
+__SET_PAPER:
+ cp 8
+ jr nz, __SET_PAPER2
+ inc de
+ ld a, (de)
+ or 038h
+ ld (de), a
+ ret
+ ; Another entry. This will set the paper color at location pointer by DE
+__SET_PAPER2:
+ and 7 ; # Remove
+ rlca
+ rlca
+ rlca ; a *= 8
+ ld b, a ; Saves the color
+ ld a, (de)
+ and 0C7h ; Clears previous value
+ or b
+ ld (de), a
+ inc de ; Points to MASK_T or MASK_P accordingly
+ ld a, (de)
+ and 0C7h ; Resets bits 3,4,5
+ ld (de), a
+ ret
+ ; Sets the PAPER color passed in A register in the ATTR_T variable
+PAPER_TMP:
+ ld de, ATTR_T
+ jp __SET_PAPER
+ ENDP
+ pop namespace
+#line 10 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/flash.asm"
+ ; Sets flash flag in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+FLASH:
+ ld hl, ATTR_P
+ PROC
+ LOCAL IS_TR
+ LOCAL IS_ZERO
+__SET_FLASH:
+ ; Another entry. This will set the flash flag at location pointer by DE
+ cp 8
+ jr z, IS_TR
+ ; # Convert to 0/1
+ or a
+ jr z, IS_ZERO
+ ld a, 0x80
+IS_ZERO:
+ ld b, a ; Saves the color
+ ld a, (hl)
+ and 07Fh ; Clears previous value
+ or b
+ ld (hl), a
+ inc hl
+ res 7, (hl) ;Reset bit 7 to disable transparency
+ ret
+IS_TR: ; transparent
+ inc hl ; Points DE to MASK_T or MASK_P
+ set 7, (hl) ;Set bit 7 to enable transparency
+ ret
+ ; Sets the FLASH flag passed in A register in the ATTR_T variable
+FLASH_TMP:
+ ld hl, ATTR_T
+ jr __SET_FLASH
+ ENDP
+ pop namespace
+#line 11 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/bright.asm"
+ ; Sets bright flag in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+BRIGHT:
+ ld hl, ATTR_P
+ PROC
+ LOCAL IS_TR
+ LOCAL IS_ZERO
+__SET_BRIGHT:
+ ; Another entry. This will set the bright flag at location pointer by DE
+ cp 8
+ jr z, IS_TR
+ ; # Convert to 0/1
+ or a
+ jr z, IS_ZERO
+ ld a, 0x40
+IS_ZERO:
+ ld b, a ; Saves the color
+ ld a, (hl)
+ and 0BFh ; Clears previous value
+ or b
+ ld (hl), a
+ inc hl
+ res 6, (hl) ;Reset bit 6 to disable transparency
+ ret
+IS_TR: ; transparent
+ inc hl ; Points DE to MASK_T or MASK_P
+ set 6, (hl) ;Set bit 6 to enable transparency
+ ret
+ ; Sets the BRIGHT flag passed in A register in the ATTR_T variable
+BRIGHT_TMP:
+ ld hl, ATTR_T
+ jr __SET_BRIGHT
+ ENDP
+ pop namespace
+#line 12 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/over.asm"
+ ; Sets OVER flag in P_FLAG permanently
+; Parameter: OVER flag in bit 0 of A register
+ push namespace core
+OVER:
+ PROC
+ ld c, a ; saves it for later
+ and 2
+ ld hl, FLAGS2
+ res 1, (HL)
+ or (hl)
+ ld (hl), a
+ ld a, c ; Recovers previous value
+ and 1 ; # Convert to 0/1
+ add a, a; # Shift left 1 bit for permanent
+ ld hl, P_FLAG
+ res 1, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets OVER flag in P_FLAG temporarily
+OVER_TMP:
+ ld c, a ; saves it for later
+ and 2 ; gets bit 1; clears carry
+ rra
+ ld hl, FLAGS2
+ res 0, (hl)
+ or (hl)
+ ld (hl), a
+ ld a, c ; Recovers previous value
+ and 1
+ ld hl, P_FLAG
+ res 0, (hl)
+ or (hl)
+ ld (hl), a
+ jp __SET_ATTR_MODE
+ ENDP
+ pop namespace
+#line 13 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/inverse.asm"
+ ; Sets INVERSE flag in P_FLAG permanently
+; Parameter: INVERSE flag in bit 0 of A register
+ push namespace core
+INVERSE:
+ PROC
+ and 1 ; # Convert to 0/1
+ add a, a; # Shift left 3 bits for permanent
+ add a, a
+ add a, a
+ ld hl, P_FLAG
+ res 3, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets INVERSE flag in P_FLAG temporarily
+INVERSE_TMP:
+ and 1
+ add a, a
+ add a, a; # Shift left 2 bits for temporary
+ ld hl, P_FLAG
+ res 2, (hl)
+ or (hl)
+ ld (hl), a
+ jp __SET_ATTR_MODE
+ ENDP
+ pop namespace
+#line 14 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/bold.asm"
+ ; Sets BOLD flag in P_FLAG permanently
+; Parameter: BOLD flag in bit 0 of A register
+ push namespace core
+BOLD:
+ PROC
+ and 1
+ rlca
+ rlca
+ rlca
+ ld hl, FLAGS2
+ res 3, (HL)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets BOLD flag in P_FLAG temporarily
+BOLD_TMP:
+ and 1
+ rlca
+ rlca
+ ld hl, FLAGS2
+ res 2, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ENDP
+ pop namespace
+#line 15 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/italic.asm"
+ ; Sets ITALIC flag in P_FLAG permanently
+; Parameter: ITALIC flag in bit 0 of A register
+ push namespace core
+ITALIC:
+ PROC
+ and 1
+ rrca
+ rrca
+ rrca
+ ld hl, FLAGS2
+ res 5, (HL)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets ITALIC flag in P_FLAG temporarily
+ITALIC_TMP:
+ and 1
+ rrca
+ rrca
+ rrca
+ rrca
+ ld hl, FLAGS2
+ res 4, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ENDP
+ pop namespace
+#line 16 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ ; Putting a comment starting with @INIT
+ ; will make the compiler to add a CALL to
+ ; It is useful for initialization routines.
+ push namespace core
+__PRINT_INIT: ; To be called before program starts (initializes library)
+ PROC
+ ld hl, __PRINT_START
+ ld (PRINT_JUMP_STATE), hl
+ ;; Clears ATTR2 flags (OVER 2, etc)
+ xor a
+ ld (FLAGS2), a
+ ld hl, TV_FLAG
+ res 0, (hl)
+ LOCAL SET_SCR_ADDR
+ call __LOAD_S_POSN
+ jp __SET_SCR_PTR
+ ;; Receives HL = future value of S_POSN
+ ;; Stores it at (S_POSN) and refresh screen pointers (ATTR, SCR)
+SET_SCR_ADDR:
+ ld (S_POSN), hl
+ ex de, hl
+ ld hl, SCR_SIZE
+ or a
+ sbc hl, de
+ ex de, hl
+ dec e
+ jp __SET_SCR_PTR
+__PRINTCHAR: ; Print character store in accumulator (A register)
+ ; Modifies H'L', B'C', A'F', D'E', A
+ LOCAL PO_GR_1
+ LOCAL __PRCHAR
+ LOCAL __PRINT_JUMP
+ LOCAL __SRCADDR
+ LOCAL __PRINT_UDG
+ LOCAL __PRGRAPH
+ LOCAL __PRINT_START
+ PRINT_JUMP_STATE EQU __PRINT_JUMP + 2
+__PRINT_JUMP:
+ exx ; Switch to alternative registers
+ jp __PRINT_START ; Where to jump. If we print 22 (AT), next two calls jumps to AT1 and AT2 respectively
+__PRINT_START:
+__PRINT_CHR:
+ cp ' '
+ jr c, __PRINT_SPECIAL ; Characters below ' ' are special ones
+ ex af, af' ; Saves a value (char to print) for later
+ ld hl, (S_POSN)
+ dec l
+ jr nz, 1f
+ ld l, SCR_COLS - 1
+ dec h
+ jr nz, 2f
+ inc h
+ push hl
+ call __SCROLL_SCR
+ pop hl
+#line 94 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+2:
+ call SET_SCR_ADDR
+ jr 4f
+1:
+ ld (S_POSN), hl
+4:
+ ex af, af'
+ cp 80h ; Is it a "normal" (printable) char
+ jr c, __SRCADDR
+ cp 90h ; Is it an UDG?
+ jr nc, __PRINT_UDG
+ ; Print an 8 bit pattern (80h to 8Fh)
+ ld b, a
+ call PO_GR_1 ; This ROM routine will generate the bit pattern at MEM0
+ ld hl, MEM0
+ jp __PRGRAPH
+ PO_GR_1 EQU 0B38h
+__PRINT_UDG:
+ sub 90h ; Sub ASC code
+ ld bc, (UDG)
+ jr __PRGRAPH0
+ __SOURCEADDR EQU (__SRCADDR + 1) ; Address of the pointer to chars source
+__SRCADDR:
+ ld bc, (CHARS)
+__PRGRAPH0:
+ add a, a ; A = a * 2 (since a < 80h) ; Thanks to Metalbrain at http://foro.speccy.org
+ ld l, a
+ ld h, 0 ; HL = a * 2 (accumulator)
+ add hl, hl
+ add hl, hl ; HL = a * 8
+ add hl, bc ; HL = CHARS address
+__PRGRAPH:
+ ex de, hl ; HL = Write Address, DE = CHARS address
+ bit 2, (iy + $47)
+ call nz, __BOLD
+#line 141 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ bit 4, (iy + $47)
+ call nz, __ITALIC
+#line 146 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ ld hl, (DFCC)
+ push hl
+ ld b, 8 ; 8 bytes per char
+__PRCHAR:
+ ld a, (de) ; DE *must* be source, and HL destiny
+PRINT_MODE: ; Which operation is used to write on the screen
+ ; Set it with:
+ ; LD A,
+ ; LD (PRINT_MODE), A
+ ;
+ ; Available operations:
+ ; NORMAL : 0h --> NOP ; OVER 0
+ ; XOR : AEh --> XOR (HL) ; OVER 1
+ ; OR : B6h --> OR (HL) ; PUTSPRITE
+ ; AND : A6h --> AND (HL) ; PUTMASK
+ nop ; Set to one of the values above
+INVERSE_MODE: ; 00 -> NOP -> INVERSE 0
+ nop ; 2F -> CPL -> INVERSE 1
+ ld (hl), a
+ inc de
+ inc h ; Next line
+ djnz __PRCHAR
+ pop hl
+ inc hl
+ ld (DFCC), hl
+ ld hl, (DFCCL) ; current ATTR Pos
+ inc hl
+ ld (DFCCL), hl
+ dec hl
+ call __SET_ATTR
+ exx
+ ret
+ ; ------------- SPECIAL CHARS (< 32) -----------------
+__PRINT_SPECIAL: ; Jumps here if it is a special char
+ ld hl, __PRINT_TABLE
+ jp JUMP_HL_PLUS_2A
+PRINT_EOL: ; Called WHENEVER there is no ";" at end of PRINT sentence
+ exx
+__PRINT_0Dh: ; Called WHEN printing CHR$(13)
+ ld hl, (S_POSN)
+ dec l
+ jr nz, 1f
+ dec h
+ jr nz, 1f
+ inc h
+ push hl
+ call __SCROLL_SCR
+ pop hl
+#line 211 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+1:
+ ld l, 1
+__PRINT_EOL_END:
+ call SET_SCR_ADDR
+ exx
+ ret
+__PRINT_COM:
+ exx
+ push hl
+ push de
+ push bc
+ call PRINT_COMMA
+ pop bc
+ pop de
+ pop hl
+ ret
+__PRINT_TAB:
+ ld hl, __PRINT_TAB1
+ jr __PRINT_SET_STATE
+__PRINT_TAB1:
+ ld (MEM0), a
+ ld hl, __PRINT_TAB2
+ jr __PRINT_SET_STATE
+__PRINT_TAB2:
+ ld a, (MEM0) ; Load tab code (ignore the current one)
+ ld hl, __PRINT_START
+ ld (PRINT_JUMP_STATE), hl
+ exx
+ push hl
+ push bc
+ push de
+ call PRINT_TAB
+ pop de
+ pop bc
+ pop hl
+ ret
+__PRINT_AT:
+ ld hl, __PRINT_AT1
+ jr __PRINT_SET_STATE
+__PRINT_NOP:
+__PRINT_RESTART:
+ ld hl, __PRINT_START
+__PRINT_SET_STATE:
+ ld (PRINT_JUMP_STATE), hl ; Saves next entry call
+ exx
+ ret
+__PRINT_AT1: ; Jumps here if waiting for 1st parameter
+ ld hl, (S_POSN)
+ ld h, a
+ ld a, SCR_ROWS
+ sub h
+ ld (S_POSN + 1), a
+ ld hl, __PRINT_AT2
+ jr __PRINT_SET_STATE
+__PRINT_AT2:
+ call __LOAD_S_POSN
+ ld e, a
+ call __SAVE_S_POSN
+ jr __PRINT_RESTART
+__PRINT_DEL:
+ call __LOAD_S_POSN ; Gets current screen position
+ dec e
+ ld a, -1
+ cp e
+ jr nz, 3f
+ ld e, SCR_COLS - 2
+ dec d
+ cp d
+ jr nz, 3f
+ ld d, SCR_ROWS - 1
+3:
+ call __SAVE_S_POSN
+ exx
+ ret
+__PRINT_INK:
+ ld hl, __PRINT_INK2
+ jr __PRINT_SET_STATE
+__PRINT_INK2:
+ call INK_TMP
+ jr __PRINT_RESTART
+__PRINT_PAP:
+ ld hl, __PRINT_PAP2
+ jr __PRINT_SET_STATE
+__PRINT_PAP2:
+ call PAPER_TMP
+ jr __PRINT_RESTART
+__PRINT_FLA:
+ ld hl, __PRINT_FLA2
+ jr __PRINT_SET_STATE
+__PRINT_FLA2:
+ call FLASH_TMP
+ jr __PRINT_RESTART
+__PRINT_BRI:
+ ld hl, __PRINT_BRI2
+ jr __PRINT_SET_STATE
+__PRINT_BRI2:
+ call BRIGHT_TMP
+ jr __PRINT_RESTART
+__PRINT_INV:
+ ld hl, __PRINT_INV2
+ jr __PRINT_SET_STATE
+__PRINT_INV2:
+ call INVERSE_TMP
+ jr __PRINT_RESTART
+__PRINT_OVR:
+ ld hl, __PRINT_OVR2
+ jr __PRINT_SET_STATE
+__PRINT_OVR2:
+ call OVER_TMP
+ jr __PRINT_RESTART
+__PRINT_BOLD:
+ ld hl, __PRINT_BOLD2
+ jp __PRINT_SET_STATE
+__PRINT_BOLD2:
+ call BOLD_TMP
+ jp __PRINT_RESTART
+#line 355 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+__PRINT_ITA:
+ ld hl, __PRINT_ITA2
+ jp __PRINT_SET_STATE
+__PRINT_ITA2:
+ call ITALIC_TMP
+ jp __PRINT_RESTART
+#line 365 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ LOCAL __BOLD
+__BOLD:
+ push hl
+ ld hl, MEM0
+ ld b, 8
+1:
+ ld a, (de)
+ ld c, a
+ rlca
+ or c
+ ld (hl), a
+ inc hl
+ inc de
+ djnz 1b
+ pop hl
+ ld de, MEM0
+ ret
+#line 386 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ LOCAL __ITALIC
+__ITALIC:
+ push hl
+ ld hl, MEM0
+ ex de, hl
+ ld bc, 8
+ ldir
+ ld hl, MEM0
+ srl (hl)
+ inc hl
+ srl (hl)
+ inc hl
+ srl (hl)
+ inc hl
+ inc hl
+ inc hl
+ sla (hl)
+ inc hl
+ sla (hl)
+ inc hl
+ sla (hl)
+ pop hl
+ ld de, MEM0
+ ret
+#line 414 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ LOCAL __SCROLL_SCR
+#line 488 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ __SCROLL_SCR EQU 0DFEh ; Use ROM SCROLL
+#line 490 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 491 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+PRINT_COMMA:
+ call __LOAD_S_POSN
+ ld a, e
+ and 16
+ add a, 16
+PRINT_TAB:
+ ; Tabulates the number of spaces in A register
+ ; If the current cursor position is already A, does nothing
+ PROC
+ LOCAL LOOP
+ call __LOAD_S_POSN ; e = current row
+ sub e
+ and 31
+ ret z
+ ld b, a
+LOOP:
+ ld a, ' '
+ call __PRINTCHAR
+ djnz LOOP
+ ret
+ ENDP
+PRINT_AT: ; Changes cursor to ROW, COL
+ ; COL in A register
+ ; ROW in stack
+ pop hl ; Ret address
+ ex (sp), hl ; callee H = ROW
+ ld l, a
+ ex de, hl
+ call __IN_SCREEN
+ ret nc ; Return if out of screen
+ jp __SAVE_S_POSN
+ LOCAL __PRINT_COM
+ LOCAL __PRINT_AT1
+ LOCAL __PRINT_AT2
+ LOCAL __PRINT_BOLD
+ LOCAL __PRINT_ITA
+ LOCAL __PRINT_INK
+ LOCAL __PRINT_PAP
+ LOCAL __PRINT_SET_STATE
+ LOCAL __PRINT_TABLE
+ LOCAL __PRINT_TAB, __PRINT_TAB1, __PRINT_TAB2
+ LOCAL __PRINT_ITA2
+#line 547 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ LOCAL __PRINT_BOLD2
+#line 553 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+__PRINT_TABLE: ; Jump table for 0 .. 22 codes
+ DW __PRINT_NOP ; 0
+ DW __PRINT_NOP ; 1
+ DW __PRINT_NOP ; 2
+ DW __PRINT_NOP ; 3
+ DW __PRINT_NOP ; 4
+ DW __PRINT_NOP ; 5
+ DW __PRINT_COM ; 6 COMMA
+ DW __PRINT_NOP ; 7
+ DW __PRINT_DEL ; 8 DEL
+ DW __PRINT_NOP ; 9
+ DW __PRINT_NOP ; 10
+ DW __PRINT_NOP ; 11
+ DW __PRINT_NOP ; 12
+ DW __PRINT_0Dh ; 13
+ DW __PRINT_BOLD ; 14
+ DW __PRINT_ITA ; 15
+ DW __PRINT_INK ; 16
+ DW __PRINT_PAP ; 17
+ DW __PRINT_FLA ; 18
+ DW __PRINT_BRI ; 19
+ DW __PRINT_INV ; 20
+ DW __PRINT_OVR ; 21
+ DW __PRINT_AT ; 22 AT
+ DW __PRINT_TAB ; 23 TAB
+ ENDP
+ pop namespace
+#line 3 "/zxbasic/src/lib/arch/zxnext/runtime/copy_attr.asm"
+#line 4 "/zxbasic/src/lib/arch/zxnext/runtime/copy_attr.asm"
+ push namespace core
+COPY_ATTR:
+ ; Just copies current permanent attribs into temporal attribs
+ ; and sets print mode
+ PROC
+ LOCAL INVERSE1
+ LOCAL __REFRESH_TMP
+ INVERSE1 EQU 02Fh
+ ld hl, (ATTR_P)
+ ld (ATTR_T), hl
+ ld hl, FLAGS2
+ call __REFRESH_TMP
+ ld hl, P_FLAG
+ call __REFRESH_TMP
+__SET_ATTR_MODE: ; Another entry to set print modes. A contains (P_FLAG)
+ LOCAL TABLE
+ LOCAL CONT2
+ rra ; Over bit to carry
+ ld a, (FLAGS2)
+ rla ; Over bit in bit 1, Over2 bit in bit 2
+ and 3 ; Only bit 0 and 1 (OVER flag)
+ ld c, a
+ ld b, 0
+ ld hl, TABLE
+ add hl, bc
+ ld a, (hl)
+ ld (PRINT_MODE), a
+ ld hl, (P_FLAG)
+ xor a ; NOP -> INVERSE0
+ bit 2, l
+ jr z, CONT2
+ ld a, INVERSE1 ; CPL -> INVERSE1
+CONT2:
+ ld (INVERSE_MODE), a
+ ret
+TABLE:
+ nop ; NORMAL MODE
+ xor (hl) ; OVER 1 MODE
+ and (hl) ; OVER 2 MODE
+ or (hl) ; OVER 3 MODE
+#line 67 "/zxbasic/src/lib/arch/zxnext/runtime/copy_attr.asm"
+__REFRESH_TMP:
+ ld a, (hl)
+ and 0b10101010
+ ld c, a
+ rra
+ or c
+ ld (hl), a
+ ret
+ ENDP
+ pop namespace
+#line 20 "arch/zxnext/print_at2.bas"
+ END
diff --git a/tests/functional/arch/zxnext/print_at2.bas b/tests/functional/arch/zxnext/print_at2.bas
new file mode 100644
index 000000000..f2b3d0c14
--- /dev/null
+++ b/tests/functional/arch/zxnext/print_at2.bas
@@ -0,0 +1,3 @@
+DIM row, col as UByte
+PRINT AT row + 2, col + 1;
+
diff --git a/tests/functional/arch/zxnext/print_tab2.asm b/tests/functional/arch/zxnext/print_tab2.asm
new file mode 100644
index 000000000..0d5840c84
--- /dev/null
+++ b/tests/functional/arch/zxnext/print_tab2.asm
@@ -0,0 +1,950 @@
+ org 32768
+.core.__START_PROGRAM:
+ di
+ push iy
+ ld iy, 0x5C3A ; ZX Spectrum ROM variables address
+ ld (.core.__CALL_BACK__), sp
+ ei
+ call .core.__PRINT_INIT
+ jp .core.__MAIN_PROGRAM__
+.core.__CALL_BACK__:
+ DEFW 0
+.core.ZXBASIC_USER_DATA:
+ ; Defines USER DATA Length in bytes
+.core.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_END - .core.ZXBASIC_USER_DATA
+ .core.__LABEL__.ZXBASIC_USER_DATA_LEN EQU .core.ZXBASIC_USER_DATA_LEN
+ .core.__LABEL__.ZXBASIC_USER_DATA EQU .core.ZXBASIC_USER_DATA
+_i:
+ DEFB 00
+.core.ZXBASIC_USER_DATA_END:
+.core.__MAIN_PROGRAM__:
+ call .core.COPY_ATTR
+ ld a, (_i)
+ add a, 2
+ call .core.PRINT_TAB
+ ld hl, 0
+ ld b, h
+ ld c, l
+.core.__END_PROGRAM:
+ di
+ ld hl, (.core.__CALL_BACK__)
+ ld sp, hl
+ pop iy
+ ei
+ ret
+ ;; --- end of user code ---
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/copy_attr.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+; vim:ts=4:sw=4:et:
+ ; PRINT command routine
+ ; Does not print attribute. Use PRINT_STR or PRINT_NUM for that
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/sposn.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/sysvars.asm"
+ ;; -----------------------------------------------------------------------
+ ;; ZX Basic System Vars
+ ;; Some of them will be mapped over Sinclair ROM ones for compatibility
+ ;; -----------------------------------------------------------------------
+ push namespace core
+SCREEN_ADDR: DW 16384 ; Screen address (can be pointed to other place to use a screen buffer)
+SCREEN_ATTR_ADDR: DW 22528 ; Screen attribute address (ditto.)
+ ; These are mapped onto ZX Spectrum ROM VARS
+ CHARS EQU 23606 ; Pointer to ROM/RAM Charset
+ TV_FLAG EQU 23612 ; TV Flags
+ UDG EQU 23675 ; Pointer to UDG Charset
+ COORDS EQU 23677 ; Last PLOT coordinates
+ FLAGS2 EQU 23681 ;
+ ECHO_E EQU 23682 ;
+ DFCC EQU 23684 ; Next screen addr for PRINT
+ DFCCL EQU 23686 ; Next screen attr for PRINT
+ S_POSN EQU 23688
+ ATTR_P EQU 23693 ; Current Permanent ATTRS set with INK, PAPER, etc commands
+ ATTR_T EQU 23695 ; temporary ATTRIBUTES
+ P_FLAG EQU 23697 ;
+ MEM0 EQU 23698 ; Temporary memory buffer used by ROM chars
+ SCR_COLS EQU 33 ; Screen with in columns + 1
+ SCR_ROWS EQU 24 ; Screen height in rows
+ SCR_SIZE EQU (SCR_ROWS << 8) + SCR_COLS
+ pop namespace
+#line 2 "/zxbasic/src/lib/arch/zxnext/runtime/sposn.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/attr.asm"
+ ; Attribute routines
+; vim:ts=4:et:sw:
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/error.asm"
+ ; Simple error control routines
+; vim:ts=4:et:
+ push namespace core
+ ERR_NR EQU 23610 ; Error code system variable
+ ; Error code definitions (as in ZX spectrum manual)
+; Set error code with:
+ ; ld a, ERROR_CODE
+ ; ld (ERR_NR), a
+ ERROR_Ok EQU -1
+ ERROR_SubscriptWrong EQU 2
+ ERROR_OutOfMemory EQU 3
+ ERROR_OutOfScreen EQU 4
+ ERROR_NumberTooBig EQU 5
+ ERROR_InvalidArg EQU 9
+ ERROR_IntOutOfRange EQU 10
+ ERROR_NonsenseInBasic EQU 11
+ ERROR_InvalidFileName EQU 14
+ ERROR_InvalidColour EQU 19
+ ERROR_BreakIntoProgram EQU 20
+ ERROR_TapeLoadingErr EQU 26
+ ; Raises error using RST #8
+__ERROR:
+ ld (__ERROR_CODE), a
+ rst 8
+__ERROR_CODE:
+ nop
+ ret
+ ; Sets the error system variable, but keeps running.
+ ; Usually this instruction if followed by the END intermediate instruction.
+__STOP:
+ ld (ERR_NR), a
+ ret
+ pop namespace
+#line 6 "/zxbasic/src/lib/arch/zxnext/runtime/attr.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/in_screen.asm"
+ push namespace core
+__IN_SCREEN:
+ ; Returns NO carry if current coords (D, E)
+ ; are OUT of the screen limits
+ PROC
+ LOCAL __IN_SCREEN_ERR
+ ld hl, SCR_SIZE
+ ld a, e
+ cp l
+ jr nc, __IN_SCREEN_ERR ; Do nothing and return if out of range
+ ld a, d
+ cp h
+ ret c ; Return if carry (OK)
+__IN_SCREEN_ERR:
+__OUT_OF_SCREEN_ERR:
+ ; Jumps here if out of screen
+ ld a, ERROR_OutOfScreen
+ jp __STOP ; Saves error code and exits
+ ENDP
+ pop namespace
+#line 7 "/zxbasic/src/lib/arch/zxnext/runtime/attr.asm"
+ push namespace core
+__ATTR_ADDR:
+ ; calc start address in DE (as (32 * d) + e)
+ ; Contributed by Santiago Romero at http://www.speccy.org
+ ld h, 0 ; 7 T-States
+ ld a, d ; 4 T-States
+ ld d, h
+ add a, a ; a * 2 ; 4 T-States
+ add a, a ; a * 4 ; 4 T-States
+ ld l, a ; HL = A * 4 ; 4 T-States
+ add hl, hl ; HL = A * 8 ; 15 T-States
+ add hl, hl ; HL = A * 16 ; 15 T-States
+ add hl, hl ; HL = A * 32 ; 15 T-States
+ add hl, de
+ ld de, (SCREEN_ATTR_ADDR) ; Adds the screen address
+ add hl, de
+ ; Return current screen address in HL
+ ret
+ ; Sets the attribute at a given screen coordinate (D, E).
+ ; The attribute is taken from the ATTR_T memory variable
+ ; Used by PRINT routines
+SET_ATTR:
+ ; Checks for valid coords
+ call __IN_SCREEN
+ ret nc
+ call __ATTR_ADDR
+__SET_ATTR:
+ ; Internal __FASTCALL__ Entry used by printing routines
+ ; HL contains the address of the ATTR cell to set
+ PROC
+__SET_ATTR2: ; Sets attr from ATTR_T to (HL) which points to the scr address
+ ld de, (ATTR_T) ; E = ATTR_T, D = MASK_T
+ ld a, d
+ and (hl)
+ ld c, a ; C = current screen color, masked
+ ld a, d
+ cpl ; Negate mask
+ and e ; Mask current attributes
+ or c ; Mix them
+ ld (hl), a ; Store result in screen
+ ret
+ ENDP
+ pop namespace
+#line 3 "/zxbasic/src/lib/arch/zxnext/runtime/sposn.asm"
+ ; Printing positioning library.
+ push namespace core
+ ; Loads into DE current ROW, COL print position from S_POSN mem var.
+__LOAD_S_POSN:
+ PROC
+ ld de, (S_POSN)
+ ld hl, SCR_SIZE
+ or a
+ sbc hl, de
+ ex de, hl
+ ret
+ ENDP
+ ; Saves ROW, COL from DE into S_POSN mem var.
+__SAVE_S_POSN:
+ PROC
+ ld hl, SCR_SIZE
+ or a
+ sbc hl, de
+ ld (S_POSN), hl ; saves it again
+__SET_SCR_PTR: ;; Fast
+ push de
+ call __ATTR_ADDR
+ ld (DFCCL), hl
+ pop de
+ ld a, d
+ ld c, a ; Saves it for later
+ and 0F8h ; Masks 3 lower bit ; zy
+ ld d, a
+ ld a, c ; Recovers it
+ and 07h ; MOD 7 ; y1
+ rrca
+ rrca
+ rrca
+ or e
+ ld e, a
+ ld hl, (SCREEN_ADDR)
+ add hl, de ; HL = Screen address + DE
+ ld (DFCC), hl
+ ret
+ ENDP
+ pop namespace
+#line 6 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/table_jump.asm"
+ push namespace core
+JUMP_HL_PLUS_2A: ; Does JP (HL + A*2) Modifies DE. Modifies A
+ add a, a
+JUMP_HL_PLUS_A: ; Does JP (HL + A) Modifies DE
+ ld e, a
+ ld d, 0
+JUMP_HL_PLUS_DE: ; Does JP (HL + DE)
+ add hl, de
+ ld e, (hl)
+ inc hl
+ ld d, (hl)
+ ex de, hl
+CALL_HL:
+ jp (hl)
+ pop namespace
+#line 8 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/ink.asm"
+ ; Sets ink color in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+INK:
+ PROC
+ LOCAL __SET_INK
+ LOCAL __SET_INK2
+ ld de, ATTR_P
+__SET_INK:
+ cp 8
+ jr nz, __SET_INK2
+ inc de ; Points DE to MASK_T or MASK_P
+ ld a, (de)
+ or 7 ; Set bits 0,1,2 to enable transparency
+ ld (de), a
+ ret
+__SET_INK2:
+ ; Another entry. This will set the ink color at location pointer by DE
+ and 7 ; # Gets color mod 8
+ ld b, a ; Saves the color
+ ld a, (de)
+ and 0F8h ; Clears previous value
+ or b
+ ld (de), a
+ inc de ; Points DE to MASK_T or MASK_P
+ ld a, (de)
+ and 0F8h ; Reset bits 0,1,2 sign to disable transparency
+ ld (de), a ; Store new attr
+ ret
+ ; Sets the INK color passed in A register in the ATTR_T variable
+INK_TMP:
+ ld de, ATTR_T
+ jp __SET_INK
+ ENDP
+ pop namespace
+#line 9 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/paper.asm"
+ ; Sets paper color in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+PAPER:
+ PROC
+ LOCAL __SET_PAPER
+ LOCAL __SET_PAPER2
+ ld de, ATTR_P
+__SET_PAPER:
+ cp 8
+ jr nz, __SET_PAPER2
+ inc de
+ ld a, (de)
+ or 038h
+ ld (de), a
+ ret
+ ; Another entry. This will set the paper color at location pointer by DE
+__SET_PAPER2:
+ and 7 ; # Remove
+ rlca
+ rlca
+ rlca ; a *= 8
+ ld b, a ; Saves the color
+ ld a, (de)
+ and 0C7h ; Clears previous value
+ or b
+ ld (de), a
+ inc de ; Points to MASK_T or MASK_P accordingly
+ ld a, (de)
+ and 0C7h ; Resets bits 3,4,5
+ ld (de), a
+ ret
+ ; Sets the PAPER color passed in A register in the ATTR_T variable
+PAPER_TMP:
+ ld de, ATTR_T
+ jp __SET_PAPER
+ ENDP
+ pop namespace
+#line 10 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/flash.asm"
+ ; Sets flash flag in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+FLASH:
+ ld hl, ATTR_P
+ PROC
+ LOCAL IS_TR
+ LOCAL IS_ZERO
+__SET_FLASH:
+ ; Another entry. This will set the flash flag at location pointer by DE
+ cp 8
+ jr z, IS_TR
+ ; # Convert to 0/1
+ or a
+ jr z, IS_ZERO
+ ld a, 0x80
+IS_ZERO:
+ ld b, a ; Saves the color
+ ld a, (hl)
+ and 07Fh ; Clears previous value
+ or b
+ ld (hl), a
+ inc hl
+ res 7, (hl) ;Reset bit 7 to disable transparency
+ ret
+IS_TR: ; transparent
+ inc hl ; Points DE to MASK_T or MASK_P
+ set 7, (hl) ;Set bit 7 to enable transparency
+ ret
+ ; Sets the FLASH flag passed in A register in the ATTR_T variable
+FLASH_TMP:
+ ld hl, ATTR_T
+ jr __SET_FLASH
+ ENDP
+ pop namespace
+#line 11 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/bright.asm"
+ ; Sets bright flag in ATTR_P permanently
+; Parameter: Paper color in A register
+ push namespace core
+BRIGHT:
+ ld hl, ATTR_P
+ PROC
+ LOCAL IS_TR
+ LOCAL IS_ZERO
+__SET_BRIGHT:
+ ; Another entry. This will set the bright flag at location pointer by DE
+ cp 8
+ jr z, IS_TR
+ ; # Convert to 0/1
+ or a
+ jr z, IS_ZERO
+ ld a, 0x40
+IS_ZERO:
+ ld b, a ; Saves the color
+ ld a, (hl)
+ and 0BFh ; Clears previous value
+ or b
+ ld (hl), a
+ inc hl
+ res 6, (hl) ;Reset bit 6 to disable transparency
+ ret
+IS_TR: ; transparent
+ inc hl ; Points DE to MASK_T or MASK_P
+ set 6, (hl) ;Set bit 6 to enable transparency
+ ret
+ ; Sets the BRIGHT flag passed in A register in the ATTR_T variable
+BRIGHT_TMP:
+ ld hl, ATTR_T
+ jr __SET_BRIGHT
+ ENDP
+ pop namespace
+#line 12 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/over.asm"
+ ; Sets OVER flag in P_FLAG permanently
+; Parameter: OVER flag in bit 0 of A register
+ push namespace core
+OVER:
+ PROC
+ ld c, a ; saves it for later
+ and 2
+ ld hl, FLAGS2
+ res 1, (HL)
+ or (hl)
+ ld (hl), a
+ ld a, c ; Recovers previous value
+ and 1 ; # Convert to 0/1
+ add a, a; # Shift left 1 bit for permanent
+ ld hl, P_FLAG
+ res 1, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets OVER flag in P_FLAG temporarily
+OVER_TMP:
+ ld c, a ; saves it for later
+ and 2 ; gets bit 1; clears carry
+ rra
+ ld hl, FLAGS2
+ res 0, (hl)
+ or (hl)
+ ld (hl), a
+ ld a, c ; Recovers previous value
+ and 1
+ ld hl, P_FLAG
+ res 0, (hl)
+ or (hl)
+ ld (hl), a
+ jp __SET_ATTR_MODE
+ ENDP
+ pop namespace
+#line 13 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/inverse.asm"
+ ; Sets INVERSE flag in P_FLAG permanently
+; Parameter: INVERSE flag in bit 0 of A register
+ push namespace core
+INVERSE:
+ PROC
+ and 1 ; # Convert to 0/1
+ add a, a; # Shift left 3 bits for permanent
+ add a, a
+ add a, a
+ ld hl, P_FLAG
+ res 3, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets INVERSE flag in P_FLAG temporarily
+INVERSE_TMP:
+ and 1
+ add a, a
+ add a, a; # Shift left 2 bits for temporary
+ ld hl, P_FLAG
+ res 2, (hl)
+ or (hl)
+ ld (hl), a
+ jp __SET_ATTR_MODE
+ ENDP
+ pop namespace
+#line 14 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/bold.asm"
+ ; Sets BOLD flag in P_FLAG permanently
+; Parameter: BOLD flag in bit 0 of A register
+ push namespace core
+BOLD:
+ PROC
+ and 1
+ rlca
+ rlca
+ rlca
+ ld hl, FLAGS2
+ res 3, (HL)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets BOLD flag in P_FLAG temporarily
+BOLD_TMP:
+ and 1
+ rlca
+ rlca
+ ld hl, FLAGS2
+ res 2, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ENDP
+ pop namespace
+#line 15 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 1 "/zxbasic/src/lib/arch/zxnext/runtime/italic.asm"
+ ; Sets ITALIC flag in P_FLAG permanently
+; Parameter: ITALIC flag in bit 0 of A register
+ push namespace core
+ITALIC:
+ PROC
+ and 1
+ rrca
+ rrca
+ rrca
+ ld hl, FLAGS2
+ res 5, (HL)
+ or (hl)
+ ld (hl), a
+ ret
+ ; Sets ITALIC flag in P_FLAG temporarily
+ITALIC_TMP:
+ and 1
+ rrca
+ rrca
+ rrca
+ rrca
+ ld hl, FLAGS2
+ res 4, (hl)
+ or (hl)
+ ld (hl), a
+ ret
+ ENDP
+ pop namespace
+#line 16 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ ; Putting a comment starting with @INIT
+ ; will make the compiler to add a CALL to
+ ; It is useful for initialization routines.
+ push namespace core
+__PRINT_INIT: ; To be called before program starts (initializes library)
+ PROC
+ ld hl, __PRINT_START
+ ld (PRINT_JUMP_STATE), hl
+ ;; Clears ATTR2 flags (OVER 2, etc)
+ xor a
+ ld (FLAGS2), a
+ ld hl, TV_FLAG
+ res 0, (hl)
+ LOCAL SET_SCR_ADDR
+ call __LOAD_S_POSN
+ jp __SET_SCR_PTR
+ ;; Receives HL = future value of S_POSN
+ ;; Stores it at (S_POSN) and refresh screen pointers (ATTR, SCR)
+SET_SCR_ADDR:
+ ld (S_POSN), hl
+ ex de, hl
+ ld hl, SCR_SIZE
+ or a
+ sbc hl, de
+ ex de, hl
+ dec e
+ jp __SET_SCR_PTR
+__PRINTCHAR: ; Print character store in accumulator (A register)
+ ; Modifies H'L', B'C', A'F', D'E', A
+ LOCAL PO_GR_1
+ LOCAL __PRCHAR
+ LOCAL __PRINT_JUMP
+ LOCAL __SRCADDR
+ LOCAL __PRINT_UDG
+ LOCAL __PRGRAPH
+ LOCAL __PRINT_START
+ PRINT_JUMP_STATE EQU __PRINT_JUMP + 2
+__PRINT_JUMP:
+ exx ; Switch to alternative registers
+ jp __PRINT_START ; Where to jump. If we print 22 (AT), next two calls jumps to AT1 and AT2 respectively
+__PRINT_START:
+__PRINT_CHR:
+ cp ' '
+ jr c, __PRINT_SPECIAL ; Characters below ' ' are special ones
+ ex af, af' ; Saves a value (char to print) for later
+ ld hl, (S_POSN)
+ dec l
+ jr nz, 1f
+ ld l, SCR_COLS - 1
+ dec h
+ jr nz, 2f
+ inc h
+ push hl
+ call __SCROLL_SCR
+ pop hl
+#line 94 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+2:
+ call SET_SCR_ADDR
+ jr 4f
+1:
+ ld (S_POSN), hl
+4:
+ ex af, af'
+ cp 80h ; Is it a "normal" (printable) char
+ jr c, __SRCADDR
+ cp 90h ; Is it an UDG?
+ jr nc, __PRINT_UDG
+ ; Print an 8 bit pattern (80h to 8Fh)
+ ld b, a
+ call PO_GR_1 ; This ROM routine will generate the bit pattern at MEM0
+ ld hl, MEM0
+ jp __PRGRAPH
+ PO_GR_1 EQU 0B38h
+__PRINT_UDG:
+ sub 90h ; Sub ASC code
+ ld bc, (UDG)
+ jr __PRGRAPH0
+ __SOURCEADDR EQU (__SRCADDR + 1) ; Address of the pointer to chars source
+__SRCADDR:
+ ld bc, (CHARS)
+__PRGRAPH0:
+ add a, a ; A = a * 2 (since a < 80h) ; Thanks to Metalbrain at http://foro.speccy.org
+ ld l, a
+ ld h, 0 ; HL = a * 2 (accumulator)
+ add hl, hl
+ add hl, hl ; HL = a * 8
+ add hl, bc ; HL = CHARS address
+__PRGRAPH:
+ ex de, hl ; HL = Write Address, DE = CHARS address
+ bit 2, (iy + $47)
+ call nz, __BOLD
+#line 141 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ bit 4, (iy + $47)
+ call nz, __ITALIC
+#line 146 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ ld hl, (DFCC)
+ push hl
+ ld b, 8 ; 8 bytes per char
+__PRCHAR:
+ ld a, (de) ; DE *must* be source, and HL destiny
+PRINT_MODE: ; Which operation is used to write on the screen
+ ; Set it with:
+ ; LD A,
+ ; LD (PRINT_MODE), A
+ ;
+ ; Available operations:
+ ; NORMAL : 0h --> NOP ; OVER 0
+ ; XOR : AEh --> XOR (HL) ; OVER 1
+ ; OR : B6h --> OR (HL) ; PUTSPRITE
+ ; AND : A6h --> AND (HL) ; PUTMASK
+ nop ; Set to one of the values above
+INVERSE_MODE: ; 00 -> NOP -> INVERSE 0
+ nop ; 2F -> CPL -> INVERSE 1
+ ld (hl), a
+ inc de
+ inc h ; Next line
+ djnz __PRCHAR
+ pop hl
+ inc hl
+ ld (DFCC), hl
+ ld hl, (DFCCL) ; current ATTR Pos
+ inc hl
+ ld (DFCCL), hl
+ dec hl
+ call __SET_ATTR
+ exx
+ ret
+ ; ------------- SPECIAL CHARS (< 32) -----------------
+__PRINT_SPECIAL: ; Jumps here if it is a special char
+ ld hl, __PRINT_TABLE
+ jp JUMP_HL_PLUS_2A
+PRINT_EOL: ; Called WHENEVER there is no ";" at end of PRINT sentence
+ exx
+__PRINT_0Dh: ; Called WHEN printing CHR$(13)
+ ld hl, (S_POSN)
+ dec l
+ jr nz, 1f
+ dec h
+ jr nz, 1f
+ inc h
+ push hl
+ call __SCROLL_SCR
+ pop hl
+#line 211 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+1:
+ ld l, 1
+__PRINT_EOL_END:
+ call SET_SCR_ADDR
+ exx
+ ret
+__PRINT_COM:
+ exx
+ push hl
+ push de
+ push bc
+ call PRINT_COMMA
+ pop bc
+ pop de
+ pop hl
+ ret
+__PRINT_TAB:
+ ld hl, __PRINT_TAB1
+ jr __PRINT_SET_STATE
+__PRINT_TAB1:
+ ld (MEM0), a
+ ld hl, __PRINT_TAB2
+ jr __PRINT_SET_STATE
+__PRINT_TAB2:
+ ld a, (MEM0) ; Load tab code (ignore the current one)
+ ld hl, __PRINT_START
+ ld (PRINT_JUMP_STATE), hl
+ exx
+ push hl
+ push bc
+ push de
+ call PRINT_TAB
+ pop de
+ pop bc
+ pop hl
+ ret
+__PRINT_AT:
+ ld hl, __PRINT_AT1
+ jr __PRINT_SET_STATE
+__PRINT_NOP:
+__PRINT_RESTART:
+ ld hl, __PRINT_START
+__PRINT_SET_STATE:
+ ld (PRINT_JUMP_STATE), hl ; Saves next entry call
+ exx
+ ret
+__PRINT_AT1: ; Jumps here if waiting for 1st parameter
+ ld hl, (S_POSN)
+ ld h, a
+ ld a, SCR_ROWS
+ sub h
+ ld (S_POSN + 1), a
+ ld hl, __PRINT_AT2
+ jr __PRINT_SET_STATE
+__PRINT_AT2:
+ call __LOAD_S_POSN
+ ld e, a
+ call __SAVE_S_POSN
+ jr __PRINT_RESTART
+__PRINT_DEL:
+ call __LOAD_S_POSN ; Gets current screen position
+ dec e
+ ld a, -1
+ cp e
+ jr nz, 3f
+ ld e, SCR_COLS - 2
+ dec d
+ cp d
+ jr nz, 3f
+ ld d, SCR_ROWS - 1
+3:
+ call __SAVE_S_POSN
+ exx
+ ret
+__PRINT_INK:
+ ld hl, __PRINT_INK2
+ jr __PRINT_SET_STATE
+__PRINT_INK2:
+ call INK_TMP
+ jr __PRINT_RESTART
+__PRINT_PAP:
+ ld hl, __PRINT_PAP2
+ jr __PRINT_SET_STATE
+__PRINT_PAP2:
+ call PAPER_TMP
+ jr __PRINT_RESTART
+__PRINT_FLA:
+ ld hl, __PRINT_FLA2
+ jr __PRINT_SET_STATE
+__PRINT_FLA2:
+ call FLASH_TMP
+ jr __PRINT_RESTART
+__PRINT_BRI:
+ ld hl, __PRINT_BRI2
+ jr __PRINT_SET_STATE
+__PRINT_BRI2:
+ call BRIGHT_TMP
+ jr __PRINT_RESTART
+__PRINT_INV:
+ ld hl, __PRINT_INV2
+ jr __PRINT_SET_STATE
+__PRINT_INV2:
+ call INVERSE_TMP
+ jr __PRINT_RESTART
+__PRINT_OVR:
+ ld hl, __PRINT_OVR2
+ jr __PRINT_SET_STATE
+__PRINT_OVR2:
+ call OVER_TMP
+ jr __PRINT_RESTART
+__PRINT_BOLD:
+ ld hl, __PRINT_BOLD2
+ jp __PRINT_SET_STATE
+__PRINT_BOLD2:
+ call BOLD_TMP
+ jp __PRINT_RESTART
+#line 355 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+__PRINT_ITA:
+ ld hl, __PRINT_ITA2
+ jp __PRINT_SET_STATE
+__PRINT_ITA2:
+ call ITALIC_TMP
+ jp __PRINT_RESTART
+#line 365 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ LOCAL __BOLD
+__BOLD:
+ push hl
+ ld hl, MEM0
+ ld b, 8
+1:
+ ld a, (de)
+ ld c, a
+ rlca
+ or c
+ ld (hl), a
+ inc hl
+ inc de
+ djnz 1b
+ pop hl
+ ld de, MEM0
+ ret
+#line 386 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ LOCAL __ITALIC
+__ITALIC:
+ push hl
+ ld hl, MEM0
+ ex de, hl
+ ld bc, 8
+ ldir
+ ld hl, MEM0
+ srl (hl)
+ inc hl
+ srl (hl)
+ inc hl
+ srl (hl)
+ inc hl
+ inc hl
+ inc hl
+ sla (hl)
+ inc hl
+ sla (hl)
+ inc hl
+ sla (hl)
+ pop hl
+ ld de, MEM0
+ ret
+#line 414 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ LOCAL __SCROLL_SCR
+#line 488 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ __SCROLL_SCR EQU 0DFEh ; Use ROM SCROLL
+#line 490 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+#line 491 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+PRINT_COMMA:
+ call __LOAD_S_POSN
+ ld a, e
+ and 16
+ add a, 16
+PRINT_TAB:
+ ; Tabulates the number of spaces in A register
+ ; If the current cursor position is already A, does nothing
+ PROC
+ LOCAL LOOP
+ call __LOAD_S_POSN ; e = current row
+ sub e
+ and 31
+ ret z
+ ld b, a
+LOOP:
+ ld a, ' '
+ call __PRINTCHAR
+ djnz LOOP
+ ret
+ ENDP
+PRINT_AT: ; Changes cursor to ROW, COL
+ ; COL in A register
+ ; ROW in stack
+ pop hl ; Ret address
+ ex (sp), hl ; callee H = ROW
+ ld l, a
+ ex de, hl
+ call __IN_SCREEN
+ ret nc ; Return if out of screen
+ jp __SAVE_S_POSN
+ LOCAL __PRINT_COM
+ LOCAL __PRINT_AT1
+ LOCAL __PRINT_AT2
+ LOCAL __PRINT_BOLD
+ LOCAL __PRINT_ITA
+ LOCAL __PRINT_INK
+ LOCAL __PRINT_PAP
+ LOCAL __PRINT_SET_STATE
+ LOCAL __PRINT_TABLE
+ LOCAL __PRINT_TAB, __PRINT_TAB1, __PRINT_TAB2
+ LOCAL __PRINT_ITA2
+#line 547 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+ LOCAL __PRINT_BOLD2
+#line 553 "/zxbasic/src/lib/arch/zxnext/runtime/print.asm"
+__PRINT_TABLE: ; Jump table for 0 .. 22 codes
+ DW __PRINT_NOP ; 0
+ DW __PRINT_NOP ; 1
+ DW __PRINT_NOP ; 2
+ DW __PRINT_NOP ; 3
+ DW __PRINT_NOP ; 4
+ DW __PRINT_NOP ; 5
+ DW __PRINT_COM ; 6 COMMA
+ DW __PRINT_NOP ; 7
+ DW __PRINT_DEL ; 8 DEL
+ DW __PRINT_NOP ; 9
+ DW __PRINT_NOP ; 10
+ DW __PRINT_NOP ; 11
+ DW __PRINT_NOP ; 12
+ DW __PRINT_0Dh ; 13
+ DW __PRINT_BOLD ; 14
+ DW __PRINT_ITA ; 15
+ DW __PRINT_INK ; 16
+ DW __PRINT_PAP ; 17
+ DW __PRINT_FLA ; 18
+ DW __PRINT_BRI ; 19
+ DW __PRINT_INV ; 20
+ DW __PRINT_OVR ; 21
+ DW __PRINT_AT ; 22 AT
+ DW __PRINT_TAB ; 23 TAB
+ ENDP
+ pop namespace
+#line 3 "/zxbasic/src/lib/arch/zxnext/runtime/copy_attr.asm"
+#line 4 "/zxbasic/src/lib/arch/zxnext/runtime/copy_attr.asm"
+ push namespace core
+COPY_ATTR:
+ ; Just copies current permanent attribs into temporal attribs
+ ; and sets print mode
+ PROC
+ LOCAL INVERSE1
+ LOCAL __REFRESH_TMP
+ INVERSE1 EQU 02Fh
+ ld hl, (ATTR_P)
+ ld (ATTR_T), hl
+ ld hl, FLAGS2
+ call __REFRESH_TMP
+ ld hl, P_FLAG
+ call __REFRESH_TMP
+__SET_ATTR_MODE: ; Another entry to set print modes. A contains (P_FLAG)
+ LOCAL TABLE
+ LOCAL CONT2
+ rra ; Over bit to carry
+ ld a, (FLAGS2)
+ rla ; Over bit in bit 1, Over2 bit in bit 2
+ and 3 ; Only bit 0 and 1 (OVER flag)
+ ld c, a
+ ld b, 0
+ ld hl, TABLE
+ add hl, bc
+ ld a, (hl)
+ ld (PRINT_MODE), a
+ ld hl, (P_FLAG)
+ xor a ; NOP -> INVERSE0
+ bit 2, l
+ jr z, CONT2
+ ld a, INVERSE1 ; CPL -> INVERSE1
+CONT2:
+ ld (INVERSE_MODE), a
+ ret
+TABLE:
+ nop ; NORMAL MODE
+ xor (hl) ; OVER 1 MODE
+ and (hl) ; OVER 2 MODE
+ or (hl) ; OVER 3 MODE
+#line 67 "/zxbasic/src/lib/arch/zxnext/runtime/copy_attr.asm"
+__REFRESH_TMP:
+ ld a, (hl)
+ and 0b10101010
+ ld c, a
+ rra
+ or c
+ ld (hl), a
+ ret
+ ENDP
+ pop namespace
+#line 17 "arch/zxnext/print_tab2.bas"
+ END
diff --git a/tests/functional/arch/zxnext/print_tab2.bas b/tests/functional/arch/zxnext/print_tab2.bas
new file mode 100644
index 000000000..f49690308
--- /dev/null
+++ b/tests/functional/arch/zxnext/print_tab2.bas
@@ -0,0 +1,4 @@
+DIM i As UByte
+
+PRINT TAB i + 2;
+