diff --git a/src/api/check.py b/src/api/check.py index a40eec425..9083902e9 100644 --- a/src/api/check.py +++ b/src/api/check.py @@ -320,6 +320,10 @@ def is_number(*p): return all(i.token in ("NUMBER", "CONST") for i in p) +def is_static_str(*p): + return all(i.token == "STRING" for i in p) + + def is_var(*p): """Returns True if ALL the arguments are AST nodes containing ID diff --git a/src/arch/z80/backend/common.py b/src/arch/z80/backend/common.py index 7373cb836..604fc8463 100644 --- a/src/arch/z80/backend/common.py +++ b/src/arch/z80/backend/common.py @@ -8,6 +8,7 @@ from src.api import global_, tmp_labels from src.api.exception import TempAlreadyFreedError +from src.symbols.symbol_ import Symbol from . import exception from .exception import InvalidICError as InvalidIC @@ -110,7 +111,7 @@ def __init__(self, *args): if len(args) - 1 != QUADS[args[0]].nargs: exception.throw_invalid_quad_params(args[0], len(args) - 1, QUADS[args[0]].nargs) - args = tuple([str(x) for x in args]) # Convert it to strings + args = tuple([str(x.t if isinstance(x, Symbol) else x) for x in args]) # Convert it to strings self.quad = args self.op = args[0] diff --git a/src/arch/z80/translator.py b/src/arch/z80/translator.py index 67797214d..4d5a1c00a 100644 --- a/src/arch/z80/translator.py +++ b/src/arch/z80/translator.py @@ -94,7 +94,7 @@ def visit_LABEL(self, node): self.ic_label(node.mangled) def visit_CONST(self, node): - yield node.t + yield node.symbol def visit_VAR(self, node): __DEBUG__( @@ -977,7 +977,7 @@ def loop_cont_label(self, loop_type): raise InvalidLoopError(loop_type) @classmethod - def default_value(cls, type_, expr): # TODO: This function must be moved to api.xx + def default_value(cls, type_: symbols.TYPE, expr) -> list[str]: # TODO: This function must be moved to api.xx """Returns a list of bytes (as hexadecimal 2 char string)""" assert isinstance(type_, symbols.TYPE) assert type_.is_basic @@ -986,44 +986,37 @@ def default_value(cls, type_, expr): # TODO: This function must be moved to api if expr.token in ("CONSTEXPR", "CONST"): # a constant expression like @label + 1 if type_ in (cls.TYPE(TYPE.float), cls.TYPE(TYPE.string)): error(expr.lineno, f"Can't convert non-numeric value to {type_.name} at compile time") - return [""] + return [""] # dummy placeholder so the compilation continues val = Translator.traverse_const(expr) if type_.size == 1: # U/byte if expr.type_.size != 1: - return ["#({0}) & 0xFF".format(val)] + return [f"#({val}) & 0xFF"] else: - return ["#{0}".format(val)] + return [f"#{val}"] if type_.size == 2: # U/integer if expr.type_.size != 2: - return ["##({0}) & 0xFFFF".format(val)] + return [f"##({val}) & 0xFFFF"] else: - return ["##{0}".format(val)] + return [f"##{val}"] if type_ == cls.TYPE(TYPE.fixed): - return ["0000", "##({0}) & 0xFFFF".format(val)] + return ["0000", f"##({val}) & 0xFFFF"] # U/Long - return ["##({0}) & 0xFFFF".format(val), "##(({0}) >> 16) & 0xFFFF".format(val)] + return [f"##({val}) & 0xFFFF", f"##(({val}) >> 16) & 0xFFFF"] if type_ == cls.TYPE(TYPE.float): C, DE, HL = _float(expr.value) C = C[:-1] # Remove 'h' suffix - if len(C) > 2: - C = C[-2:] + C = C[-2:] DE = DE[:-1] # Remove 'h' suffix - if len(DE) > 4: - DE = DE[-4:] - elif len(DE) < 3: - DE = "00" + DE + DE = ("00" + DE)[-4:] HL = HL[:-1] # Remove 'h' suffix - if len(HL) > 4: - HL = HL[-4:] - elif len(HL) < 3: - HL = "00" + HL + HL = ("00" + HL)[-4:] return [C, DE[-2:], DE[:-2], HL[-2:], HL[:-2]] @@ -1032,8 +1025,8 @@ def default_value(cls, type_, expr): # TODO: This function must be moved to api else: value = int(expr.value) - result = [value, value >> 8, value >> 16, value >> 24] - result = ["%02X" % (v & 0xFF) for v in result] + values = [value, value >> 8, value >> 16, value >> 24] + result = ["%02X" % (v & 0xFF) for v in values] return result[: type_.size] @staticmethod @@ -1459,7 +1452,10 @@ def visit_FUNCTION(self, node): if node.convention == CONVENTION.stdcall: for local_var in node.local_symbol_table.values(): scope = local_var.scope - if local_var.type_ == self.TYPE(TYPE.string): # Only if it's string we free it + if local_var.type_ == self.TYPE(TYPE.string): + if local_var.class_ == CLASS.const: + continue + # Only if it's string we free it if local_var.class_ != CLASS.array: # Ok just free it if scope == SCOPE.local or (scope == SCOPE.parameter and not local_var.byref): if not preserve_hl: @@ -1469,8 +1465,6 @@ def visit_FUNCTION(self, node): offset = -local_var.offset if scope == SCOPE.local else local_var.offset self.ic_fpload(TYPE.string, local_var.t, offset) self.runtime_call(RuntimeLabel.MEM_FREE, 0) - elif local_var.class_ == CLASS.const: - continue else: # This is an array of strings, we must free it unless it's a by_ref array if scope == SCOPE.local or (scope == SCOPE.parameter and not local_var.byref): if not preserve_hl: diff --git a/src/symbols/id_/ref/constref.py b/src/symbols/id_/ref/constref.py index b8bd44955..c57d50089 100644 --- a/src/symbols/id_/ref/constref.py +++ b/src/symbols/id_/ref/constref.py @@ -9,7 +9,7 @@ class ConstRef(SymbolRef): def __init__(self, parent: SymbolID, default_value: Symbol): super().__init__(parent) - assert default_value.token in ("CONSTEXPR", "NUMBER", "CONST") + assert default_value.token in ("CONSTEXPR", "NUMBER", "CONST", "STRING") self._value = default_value @property @@ -26,7 +26,11 @@ def t(self) -> str: @property def value(self): - if self._value.token in ("NUMBER", "CONST"): + if self._value.token in ("NUMBER", "CONST", "STRING"): return self._value.value return self.t + + @property + def symbol(self) -> Symbol: + return self._value diff --git a/src/zxbc/zxbparser.py b/src/zxbc/zxbparser.py index 9e4fef48d..b1f9f11f9 100755 --- a/src/zxbc/zxbparser.py +++ b/src/zxbc/zxbparser.py @@ -38,6 +38,7 @@ is_number, is_numeric, is_static, + is_static_str, is_string, is_unsigned, ) @@ -365,8 +366,8 @@ def make_call(id_: str, lineno: int, args: sym.ARGLIST): A "call" is just an ID followed by a list of arguments. E.g. a(4) - a(4) can be a function call if 'a' is a function - - a(4) can be a string slice if a is a string variable: a$(4) - - a(4) can be an access to an array if a is an array + - a(4) can be a string slice if 'a' is a string variable: a$(4) + - a(4) can be an access to an array if 'a' is an array This function will inspect the id_. If it is undeclared then id_ will be taken as a forwarded function. @@ -705,8 +706,11 @@ def p_var_decl_ini(p): else: # keyword == "CONST" if defval is None: - errmsg.syntax_error_not_constant(p.lineno(4)) - return + if not is_static_str(value): + errmsg.syntax_error_not_constant(p.lineno(4)) + return + else: + defval = value SYMBOL_TABLE.declare_const(idlist[0].name, idlist[0].lineno, typedef, default_value=defval) diff --git a/tests/functional/zx48k/const_str.asm b/tests/functional/zx48k/const_str.asm new file mode 100644 index 000000000..6b4eebd30 --- /dev/null +++ b/tests/functional/zx48k/const_str.asm @@ -0,0 +1,773 @@ + org 32768 +.core.__START_PROGRAM: + di + push ix + push iy + exx + push hl + exx + ld hl, 0 + add hl, sp + ld (.core.__CALL_BACK__), hl + ei + call .core.__MEM_INIT + jp .core.__MAIN_PROGRAM__ +.core.__CALL_BACK__: + DEFW 0 +.core.ZXBASIC_USER_DATA: + ; Defines HEAP SIZE +.core.ZXBASIC_HEAP_SIZE EQU 4768 +.core.ZXBASIC_MEM_HEAP: + DEFS 4768 + ; 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 +_b: + DEFB 00, 00 +.core.ZXBASIC_USER_DATA_END: +.core.__MAIN_PROGRAM__: + ld de, .LABEL.__LABEL0 + ld hl, _b + call .core.__STORE_STR + ld de, .LABEL.__LABEL0 + ld hl, _b + call .core.__STORE_STR + 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 +.LABEL.__LABEL0: + DEFW 000Bh + DEFB 48h + DEFB 65h + DEFB 6Ch + DEFB 6Ch + DEFB 6Fh + DEFB 20h + DEFB 57h + DEFB 6Fh + DEFB 72h + DEFB 6Ch + DEFB 64h + ;; --- end of user code --- +#line 1 "/zxbasic/src/arch/zx48k/library-asm/storestr.asm" +; vim:ts=4:et:sw=4 + ; Stores value of current string pointed by DE register into address pointed by HL + ; Returns DE = Address pointer (&a$) + ; Returns HL = HL (b$ => might be needed later to free it from the heap) + ; + ; e.g. => HL = _variableName (DIM _variableName$) + ; DE = Address into the HEAP + ; + ; This function will resize (REALLOC) the space pointed by HL + ; before copying the content of b$ into a$ +#line 1 "/zxbasic/src/arch/zx48k/library-asm/strcpy.asm" +#line 1 "/zxbasic/src/arch/zx48k/library-asm/realloc.asm" +; vim: ts=4:et:sw=4: + ; Copyleft (K) by Jose M. Rodriguez de la Rosa + ; (a.k.a. Boriel) +; http://www.boriel.com + ; + ; This ASM library is licensed under the BSD license + ; you can use it for any purpose (even for commercial + ; closed source programs). + ; + ; Please read the BSD license on the internet + ; ----- IMPLEMENTATION NOTES ------ + ; The heap is implemented as a linked list of free blocks. +; Each free block contains this info: + ; + ; +----------------+ <-- HEAP START + ; | Size (2 bytes) | + ; | 0 | <-- Size = 0 => DUMMY HEADER BLOCK + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ <-+ + ; | Size (2 bytes) | + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ | + ; | | | <-- If Size > 4, then this contains (size - 4) bytes + ; | (0 if Size = 4)| | + ; +----------------+ <-+ + ; | Size (2 bytes) | + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ | + ; | | | + ; | (0 if Size = 4)| | + ; +----------------+ | + ; | <-- This zone is in use (Already allocated) + ; +----------------+ <-+ + ; | Size (2 bytes) | + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ | + ; | | | + ; | (0 if Size = 4)| | + ; +----------------+ <-+ + ; | Next (2 bytes) |--> NULL => END OF LIST + ; | 0 = NULL | + ; +----------------+ + ; | | + ; | (0 if Size = 4)| + ; +----------------+ + ; When a block is FREED, the previous and next pointers are examined to see + ; if we can defragment the heap. If the block to be breed is just next to the + ; previous, or to the next (or both) they will be converted into a single + ; block (so defragmented). + ; MEMORY MANAGER + ; + ; This library must be initialized calling __MEM_INIT with + ; HL = BLOCK Start & DE = Length. + ; An init directive is useful for initialization routines. + ; They will be added automatically if needed. +#line 1 "/zxbasic/src/arch/zx48k/library-asm/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 70 "/zxbasic/src/arch/zx48k/library-asm/realloc.asm" +#line 1 "/zxbasic/src/arch/zx48k/library-asm/alloc.asm" +; vim: ts=4:et:sw=4: + ; Copyleft (K) by Jose M. Rodriguez de la Rosa + ; (a.k.a. Boriel) +; http://www.boriel.com + ; + ; This ASM library is licensed under the MIT license + ; you can use it for any purpose (even for commercial + ; closed source programs). + ; + ; Please read the MIT license on the internet + ; ----- IMPLEMENTATION NOTES ------ + ; The heap is implemented as a linked list of free blocks. +; Each free block contains this info: + ; + ; +----------------+ <-- HEAP START + ; | Size (2 bytes) | + ; | 0 | <-- Size = 0 => DUMMY HEADER BLOCK + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ <-+ + ; | Size (2 bytes) | + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ | + ; | | | <-- If Size > 4, then this contains (size - 4) bytes + ; | (0 if Size = 4)| | + ; +----------------+ <-+ + ; | Size (2 bytes) | + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ | + ; | | | + ; | (0 if Size = 4)| | + ; +----------------+ | + ; | <-- This zone is in use (Already allocated) + ; +----------------+ <-+ + ; | Size (2 bytes) | + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ | + ; | | | + ; | (0 if Size = 4)| | + ; +----------------+ <-+ + ; | Next (2 bytes) |--> NULL => END OF LIST + ; | 0 = NULL | + ; +----------------+ + ; | | + ; | (0 if Size = 4)| + ; +----------------+ + ; When a block is FREED, the previous and next pointers are examined to see + ; if we can defragment the heap. If the block to be freed is just next to the + ; previous, or to the next (or both) they will be converted into a single + ; block (so defragmented). + ; MEMORY MANAGER + ; + ; This library must be initialized calling __MEM_INIT with + ; HL = BLOCK Start & DE = Length. + ; An init directive is useful for initialization routines. + ; They will be added automatically if needed. +#line 1 "/zxbasic/src/arch/zx48k/library-asm/heapinit.asm" +; vim: ts=4:et:sw=4: + ; Copyleft (K) by Jose M. Rodriguez de la Rosa + ; (a.k.a. Boriel) +; http://www.boriel.com + ; + ; This ASM library is licensed under the BSD license + ; you can use it for any purpose (even for commercial + ; closed source programs). + ; + ; Please read the BSD license on the internet + ; ----- IMPLEMENTATION NOTES ------ + ; The heap is implemented as a linked list of free blocks. +; Each free block contains this info: + ; + ; +----------------+ <-- HEAP START + ; | Size (2 bytes) | + ; | 0 | <-- Size = 0 => DUMMY HEADER BLOCK + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ <-+ + ; | Size (2 bytes) | + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ | + ; | | | <-- If Size > 4, then this contains (size - 4) bytes + ; | (0 if Size = 4)| | + ; +----------------+ <-+ + ; | Size (2 bytes) | + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ | + ; | | | + ; | (0 if Size = 4)| | + ; +----------------+ | + ; | <-- This zone is in use (Already allocated) + ; +----------------+ <-+ + ; | Size (2 bytes) | + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ | + ; | | | + ; | (0 if Size = 4)| | + ; +----------------+ <-+ + ; | Next (2 bytes) |--> NULL => END OF LIST + ; | 0 = NULL | + ; +----------------+ + ; | | + ; | (0 if Size = 4)| + ; +----------------+ + ; When a block is FREED, the previous and next pointers are examined to see + ; if we can defragment the heap. If the block to be breed is just next to the + ; previous, or to the next (or both) they will be converted into a single + ; block (so defragmented). + ; MEMORY MANAGER + ; + ; This library must be initialized calling __MEM_INIT with + ; HL = BLOCK Start & DE = Length. + ; An init directive is useful for initialization routines. + ; They will be added automatically if needed. + ; --------------------------------------------------------------------- + ; __MEM_INIT must be called to initalize this library with the + ; standard parameters + ; --------------------------------------------------------------------- + push namespace core +__MEM_INIT: ; Initializes the library using (RAMTOP) as start, and + ld hl, ZXBASIC_MEM_HEAP ; Change this with other address of heap start + ld de, ZXBASIC_HEAP_SIZE ; Change this with your size + ; --------------------------------------------------------------------- + ; __MEM_INIT2 initalizes this library +; Parameters: +; HL : Memory address of 1st byte of the memory heap +; DE : Length in bytes of the Memory Heap + ; --------------------------------------------------------------------- +__MEM_INIT2: + ; HL as TOP + PROC + dec de + dec de + dec de + dec de ; DE = length - 4; HL = start + ; This is done, because we require 4 bytes for the empty dummy-header block + xor a + ld (hl), a + inc hl + ld (hl), a ; First "free" block is a header: size=0, Pointer=&(Block) + 4 + inc hl + ld b, h + ld c, l + inc bc + inc bc ; BC = starts of next block + ld (hl), c + inc hl + ld (hl), b + inc hl ; Pointer to next block + ld (hl), e + inc hl + ld (hl), d + inc hl ; Block size (should be length - 4 at start); This block contains all the available memory + ld (hl), a ; NULL (0000h) ; No more blocks (a list with a single block) + inc hl + ld (hl), a + ld a, 201 + ld (__MEM_INIT), a; "Pokes" with a RET so ensure this routine is not called again + ret + ENDP + pop namespace +#line 70 "/zxbasic/src/arch/zx48k/library-asm/alloc.asm" + ; --------------------------------------------------------------------- + ; MEM_ALLOC + ; Allocates a block of memory in the heap. + ; + ; Parameters + ; BC = Length of requested memory block + ; +; Returns: + ; HL = Pointer to the allocated block in memory. Returns 0 (NULL) + ; if the block could not be allocated (out of memory) + ; --------------------------------------------------------------------- + push namespace core +MEM_ALLOC: +__MEM_ALLOC: ; Returns the 1st free block found of the given length (in BC) + PROC + LOCAL __MEM_LOOP + LOCAL __MEM_DONE + LOCAL __MEM_SUBTRACT + LOCAL __MEM_START + LOCAL TEMP, TEMP0 + TEMP EQU TEMP0 + 1 + ld hl, 0 + ld (TEMP), hl +__MEM_START: + ld hl, ZXBASIC_MEM_HEAP ; This label point to the heap start + inc bc + inc bc ; BC = BC + 2 ; block size needs 2 extra bytes for hidden pointer +__MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE + ld a, h ; HL = NULL (No memory available?) + or l +#line 113 "/zxbasic/src/arch/zx48k/library-asm/alloc.asm" + ret z ; NULL +#line 115 "/zxbasic/src/arch/zx48k/library-asm/alloc.asm" + ; HL = Pointer to Free block + ld e, (hl) + inc hl + ld d, (hl) + inc hl ; DE = Block Length + push hl ; HL = *pointer to -> next block + ex de, hl + or a ; CF = 0 + sbc hl, bc ; FREE >= BC (Length) (HL = BlockLength - Length) + jp nc, __MEM_DONE + pop hl + ld (TEMP), hl + ex de, hl + ld e, (hl) + inc hl + ld d, (hl) + ex de, hl + jp __MEM_LOOP +__MEM_DONE: ; A free block has been found. + ; Check if at least 4 bytes remains free (HL >= 4) + push hl + exx ; exx to preserve bc + pop hl + ld bc, 4 + or a + sbc hl, bc + exx + jp nc, __MEM_SUBTRACT + ; At this point... + ; less than 4 bytes remains free. So we return this block entirely + ; We must link the previous block with the next to this one + ; (DE) => Pointer to next block + ; (TEMP) => &(previous->next) + pop hl ; Discard current block pointer + push de + ex de, hl ; DE = Previous block pointer; (HL) = Next block pointer + ld a, (hl) + inc hl + ld h, (hl) + ld l, a ; HL = (HL) + ex de, hl ; HL = Previous block pointer; DE = Next block pointer +TEMP0: + ld hl, 0 ; Pre-previous block pointer + ld (hl), e + inc hl + ld (hl), d ; LINKED + pop hl ; Returning block. + ret +__MEM_SUBTRACT: + ; At this point we have to store HL value (Length - BC) into (DE - 2) + ex de, hl + dec hl + ld (hl), d + dec hl + ld (hl), e ; Store new block length + add hl, de ; New length + DE => free-block start + pop de ; Remove previous HL off the stack + ld (hl), c ; Store length on its 1st word + inc hl + ld (hl), b + inc hl ; Return hl + ret + ENDP + pop namespace +#line 71 "/zxbasic/src/arch/zx48k/library-asm/realloc.asm" +#line 1 "/zxbasic/src/arch/zx48k/library-asm/free.asm" +; vim: ts=4:et:sw=4: + ; Copyleft (K) by Jose M. Rodriguez de la Rosa + ; (a.k.a. Boriel) +; http://www.boriel.com + ; + ; This ASM library is licensed under the BSD license + ; you can use it for any purpose (even for commercial + ; closed source programs). + ; + ; Please read the BSD license on the internet + ; ----- IMPLEMENTATION NOTES ------ + ; The heap is implemented as a linked list of free blocks. +; Each free block contains this info: + ; + ; +----------------+ <-- HEAP START + ; | Size (2 bytes) | + ; | 0 | <-- Size = 0 => DUMMY HEADER BLOCK + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ <-+ + ; | Size (2 bytes) | + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ | + ; | | | <-- If Size > 4, then this contains (size - 4) bytes + ; | (0 if Size = 4)| | + ; +----------------+ <-+ + ; | Size (2 bytes) | + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ | + ; | | | + ; | (0 if Size = 4)| | + ; +----------------+ | + ; | <-- This zone is in use (Already allocated) + ; +----------------+ <-+ + ; | Size (2 bytes) | + ; +----------------+ + ; | Next (2 bytes) |---+ + ; +----------------+ | + ; | | | + ; | (0 if Size = 4)| | + ; +----------------+ <-+ + ; | Next (2 bytes) |--> NULL => END OF LIST + ; | 0 = NULL | + ; +----------------+ + ; | | + ; | (0 if Size = 4)| + ; +----------------+ + ; When a block is FREED, the previous and next pointers are examined to see + ; if we can defragment the heap. If the block to be breed is just next to the + ; previous, or to the next (or both) they will be converted into a single + ; block (so defragmented). + ; MEMORY MANAGER + ; + ; This library must be initialized calling __MEM_INIT with + ; HL = BLOCK Start & DE = Length. + ; An init directive is useful for initialization routines. + ; They will be added automatically if needed. + ; --------------------------------------------------------------------- + ; MEM_FREE + ; Frees a block of memory + ; +; Parameters: + ; HL = Pointer to the block to be freed. If HL is NULL (0) nothing + ; is done + ; --------------------------------------------------------------------- + push namespace core +MEM_FREE: +__MEM_FREE: ; Frees the block pointed by HL + ; HL DE BC & AF modified + PROC + LOCAL __MEM_LOOP2 + LOCAL __MEM_LINK_PREV + LOCAL __MEM_JOIN_TEST + LOCAL __MEM_BLOCK_JOIN + ld a, h + or l + ret z ; Return if NULL pointer + dec hl + dec hl + ld b, h + ld c, l ; BC = Block pointer + ld hl, ZXBASIC_MEM_HEAP ; This label point to the heap start +__MEM_LOOP2: + inc hl + inc hl ; Next block ptr + ld e, (hl) + inc hl + ld d, (hl) ; Block next ptr + ex de, hl ; DE = &(block->next); HL = block->next + ld a, h ; HL == NULL? + or l + jp z, __MEM_LINK_PREV; if so, link with previous + or a ; Clear carry flag + sbc hl, bc ; Carry if BC > HL => This block if before + add hl, bc ; Restores HL, preserving Carry flag + jp c, __MEM_LOOP2 ; This block is before. Keep searching PASS the block + ;------ At this point current HL is PAST BC, so we must link (DE) with BC, and HL in BC->next +__MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL + ex de, hl + push hl + dec hl + ld (hl), c + inc hl + ld (hl), b ; (DE) <- BC + ld h, b ; HL <- BC (Free block ptr) + ld l, c + inc hl ; Skip block length (2 bytes) + inc hl + ld (hl), e ; Block->next = DE + inc hl + ld (hl), d + ; --- LINKED ; HL = &(BC->next) + 2 + call __MEM_JOIN_TEST + pop hl +__MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them + ; hl = Ptr to current block + 2 + ld d, (hl) + dec hl + ld e, (hl) + dec hl + ld b, (hl) ; Loads block length into BC + dec hl + ld c, (hl) ; + push hl ; Saves it for later + add hl, bc ; Adds its length. If HL == DE now, it must be joined + or a + sbc hl, de ; If Z, then HL == DE => We must join + pop hl + ret nz +__MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed by DE). HL->length already in BC + push hl ; Saves it for later + ex de, hl + ld e, (hl) ; DE -> block->next->length + inc hl + ld d, (hl) + inc hl + ex de, hl ; DE = &(block->next) + add hl, bc ; HL = Total Length + ld b, h + ld c, l ; BC = Total Length + ex de, hl + ld e, (hl) + inc hl + ld d, (hl) ; DE = block->next + pop hl ; Recovers Pointer to block + ld (hl), c + inc hl + ld (hl), b ; Length Saved + inc hl + ld (hl), e + inc hl + ld (hl), d ; Next saved + ret + ENDP + pop namespace +#line 72 "/zxbasic/src/arch/zx48k/library-asm/realloc.asm" + ; --------------------------------------------------------------------- + ; MEM_REALLOC + ; Reallocates a block of memory in the heap. + ; + ; Parameters + ; HL = Pointer to the original block + ; BC = New Length of requested memory block + ; +; Returns: + ; HL = Pointer to the allocated block in memory. Returns 0 (NULL) + ; if the block could not be allocated (out of memory) + ; +; Notes: + ; If BC = 0, the block is freed, otherwise + ; the content of the original block is copied to the new one, and + ; the new size is adjusted. If BC < original length, the content + ; will be truncated. Otherwise, extra block content might contain + ; memory garbage. + ; + ; --------------------------------------------------------------------- + push namespace core +__REALLOC: ; Reallocates block pointed by HL, with new length BC + PROC + LOCAL __REALLOC_END + ld a, h + or l + jp z, __MEM_ALLOC ; If HL == NULL, just do a malloc + ld e, (hl) + inc hl + ld d, (hl) ; DE = First 2 bytes of HL block + push hl + exx + pop de + inc de ; DE' <- HL + 2 + exx ; DE' <- HL (Saves current pointer into DE') + dec hl ; HL = Block start + push de + push bc + call __MEM_FREE ; Frees current block + pop bc + push bc + call __MEM_ALLOC ; Gets a new block of length BC + pop bc + pop de + ld a, h + or l + ret z ; Return if HL == NULL (No memory) + ld (hl), e + inc hl + ld (hl), d + inc hl ; Recovers first 2 bytes in HL + dec bc + dec bc ; BC = BC - 2 (Two bytes copied) + ld a, b + or c + jp z, __REALLOC_END ; Ret if nothing to copy (BC == 0) + exx + push de + exx + pop de ; DE <- DE' ; Start of remaining block + push hl ; Saves current Block + 2 start + ex de, hl ; Exchanges them: DE is destiny block + ldir ; Copies BC Bytes + pop hl ; Recovers Block + 2 start +__REALLOC_END: + dec hl ; Set HL + dec hl ; To begin of block + ret + ENDP + pop namespace +#line 2 "/zxbasic/src/arch/zx48k/library-asm/strcpy.asm" + ; String library + push namespace core +__STRASSIGN: ; Performs a$ = b$ (HL = address of a$; DE = Address of b$) + PROC + LOCAL __STRREALLOC + LOCAL __STRCONTINUE + LOCAL __B_IS_NULL + LOCAL __NOTHING_TO_COPY + ld b, d + ld c, e + ld a, b + or c + jr z, __B_IS_NULL + ex de, hl + ld c, (hl) + inc hl + ld b, (hl) + dec hl ; BC = LEN(b$) + ex de, hl ; DE = &b$ +__B_IS_NULL: ; Jumps here if B$ pointer is NULL + inc bc + inc bc ; BC = BC + 2 ; (LEN(b$) + 2 bytes for storing length) + push de + push hl + ld a, h + or l + jr z, __STRREALLOC + dec hl + ld d, (hl) + dec hl + ld e, (hl) ; DE = MEMBLOCKSIZE(a$) + dec de + dec de ; DE = DE - 2 ; (Membloksize takes 2 bytes for memblock length) + ld h, b + ld l, c ; HL = LEN(b$) + 2 => Minimum block size required + ex de, hl ; Now HL = BLOCKSIZE(a$), DE = LEN(b$) + 2 + or a ; Prepare to subtract BLOCKSIZE(a$) - LEN(b$) + sbc hl, de ; Carry if len(b$) > Blocklen(a$) + jr c, __STRREALLOC ; No need to realloc + ; Need to reallocate at least to len(b$) + 2 + ex de, hl ; DE = Remaining bytes in a$ mem block. + ld hl, 4 + sbc hl, de ; if remaining bytes < 4 we can continue + jr nc,__STRCONTINUE ; Otherwise, we realloc, to free some bytes +__STRREALLOC: + pop hl + call __REALLOC ; Returns in HL a new pointer with BC bytes allocated + push hl +__STRCONTINUE: ; Pops hl and de SWAPPED + pop de ; DE = &a$ + pop hl ; HL = &b$ + ld a, d ; Return if not enough memory for new length + or e + ret z ; Return if DE == NULL (0) +__STRCPY: ; Copies string pointed by HL into string pointed by DE + ; Returns DE as HL (new pointer) + ld a, h + or l + jr z, __NOTHING_TO_COPY + ld c, (hl) + inc hl + ld b, (hl) + dec hl + inc bc + inc bc + push de + ldir + pop hl + ret +__NOTHING_TO_COPY: + ex de, hl + ld (hl), e + inc hl + ld (hl), d + dec hl + ret + ENDP + pop namespace +#line 14 "/zxbasic/src/arch/zx48k/library-asm/storestr.asm" + push namespace core +__PISTORE_STR: ; Indirect assignement at (IX + BC) + push ix + pop hl + add hl, bc +__ISTORE_STR: ; Indirect assignement, hl point to a pointer to a pointer to the heap! + ld c, (hl) + inc hl + ld h, (hl) + ld l, c ; HL = (HL) +__STORE_STR: + push de ; Pointer to b$ + push hl ; Array pointer to variable memory address + ld c, (hl) + inc hl + ld h, (hl) + ld l, c ; HL = (HL) + call __STRASSIGN ; HL (a$) = DE (b$); HL changed to a new dynamic memory allocation + ex de, hl ; DE = new address of a$ + pop hl ; Recover variable memory address pointer + ld (hl), e + inc hl + ld (hl), d ; Stores a$ ptr into elemem ptr + pop hl ; Returns ptr to b$ in HL (Caller might needed to free it from memory) + ret + pop namespace +#line 36 "zx48k/const_str.bas" + END diff --git a/tests/functional/zx48k/const_str.bas b/tests/functional/zx48k/const_str.bas new file mode 100644 index 000000000..88a65c6b1 --- /dev/null +++ b/tests/functional/zx48k/const_str.bas @@ -0,0 +1,4 @@ +CONST a = "Hello World" + +LET b = a +LET b = "Hello World"