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; +