diff --git a/api/check.py b/api/check.py index 6817bb29b..4329d422c 100644 --- a/api/check.py +++ b/api/check.py @@ -411,6 +411,12 @@ def is_block_accessed(block): return False +def is_temporary_value(node) -> bool: + """ Returns if the AST node value is a variable or a temporary copy in the heap. + """ + return node.token not in ('STRING', 'VAR') and node.t[0] not in ('_', '#') + + def common_type(a, b): """ Returns a type which is common for both a and b types. Returns None if no common types allowed. diff --git a/arch/zx48k/translator.py b/arch/zx48k/translator.py index dcf49f860..b9fa67242 100644 --- a/arch/zx48k/translator.py +++ b/arch/zx48k/translator.py @@ -314,12 +314,12 @@ def visit_LETARRAY(self, node): def visit_LETSUBSTR(self, node): yield node.children[3] - self.ic_param(TYPE.string, node.children[3].t) - if node.children[3].token != 'STRING' and (node.children[3].token != 'VAR' or - node.children[3].mangled[0] != '_'): - self.ic_param(TYPE.ubyte, 1) # If the argument is not a variable, it must be freed + if check.is_temporary_value(node.children[3]): + self.ic_param(TYPE.string, node.children[3].t) + self.ic_param(TYPE.ubyte, 1) else: + self.ic_param(gl.PTR_TYPE, node.children[3].t) self.ic_param(TYPE.ubyte, 0) yield node.children[1] @@ -338,6 +338,7 @@ def visit_LETARRAYSUBSTR(self, node): yield expr self.ic_param(TYPE.string, expr.t) + # TODO: this produces a memory leak if expr.token != 'STRING' and (expr.token != 'VAR' or expr.mangled[0] != '_'): self.ic_param(TYPE.ubyte, 1) # If the argument is not a variable, it must be freed else: diff --git a/library-asm/letsubstr.asm b/library-asm/letsubstr.asm index fdd88bd63..f71d6b510 100644 --- a/library-asm/letsubstr.asm +++ b/library-asm/letsubstr.asm @@ -16,7 +16,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address @@ -32,10 +31,11 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl @@ -46,21 +46,21 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back @@ -72,7 +72,7 @@ __LETSUBSTR: ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de @@ -143,15 +143,10 @@ __CONT1: pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl - -__FREE_STR0: - ex de, hl - __FREE_STR: + pop hl ex af, af' - or a ; If not 0, free + or a ; If not 0, free jp nz, __MEM_FREE ret diff --git a/tests/functional/id_substr_eq_expr.asm b/tests/functional/id_substr_eq_expr.asm index 0b1127226..09dc41aa3 100644 --- a/tests/functional/id_substr_eq_expr.asm +++ b/tests/functional/id_substr_eq_expr.asm @@ -14,7 +14,6 @@ __START_PROGRAM: ei call __MEM_INIT ld hl, __LABEL0 - call __LOADSTR push hl xor a push af @@ -325,7 +324,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -337,9 +335,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -348,18 +347,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -367,7 +366,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -421,242 +420,14 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE ret ENDP -#line 36 "id_substr_eq_expr.bas" -#line 1 "loadstr.asm" -#line 1 "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 "error.asm" - ; Simple error control routines -; vim:ts=4:et: - 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 -#line 69 "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) - ; --------------------------------------------------------------------- -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 111 "/zxbasic/library-asm/alloc.asm" - ret z ; NULL -#line 113 "/zxbasic/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 -#line 2 "loadstr.asm" - ; Loads a string (ptr) from HL - ; and duplicates it on dynamic memory again - ; Finally, it returns result pointer in HL -__ILOADSTR: ; This is the indirect pointer entry HL = (HL) - ld a, h - or l - ret z - ld a, (hl) - inc hl - ld h, (hl) - ld l, a -__LOADSTR: ; __FASTCALL__ entry - ld a, h - or l - ret z ; Return if NULL - ld c, (hl) - inc hl - ld b, (hl) - dec hl ; BC = LEN(a$) - inc bc - inc bc ; BC = LEN(a$) + 2 (two bytes for length) - push hl - push bc - call __MEM_ALLOC - pop bc ; Recover length - pop de ; Recover origin - ld a, h - or l - ret z ; Return if NULL (No memory) - ex de, hl ; ldir takes HL as source, DE as destiny, so SWAP HL,DE - push de ; Saves destiny start - ldir ; Copies string (length number included) - pop hl ; Recovers destiny in hl as result - ret -#line 37 "id_substr_eq_expr.bas" +#line 35 "id_substr_eq_expr.bas" ZXBASIC_USER_DATA: _a: DEFB 00, 00 diff --git a/tests/functional/let_array_substr.asm b/tests/functional/let_array_substr.asm index c9bea14cd..4a984124a 100644 --- a/tests/functional/let_array_substr.asm +++ b/tests/functional/let_array_substr.asm @@ -479,7 +479,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -491,9 +490,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -502,18 +502,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -521,7 +521,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -575,11 +575,8 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE diff --git a/tests/functional/let_array_substr1.asm b/tests/functional/let_array_substr1.asm index ab4600b83..0054a0664 100644 --- a/tests/functional/let_array_substr1.asm +++ b/tests/functional/let_array_substr1.asm @@ -479,7 +479,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -491,9 +490,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -502,18 +502,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -521,7 +521,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -575,11 +575,8 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE diff --git a/tests/functional/let_array_substr10.asm b/tests/functional/let_array_substr10.asm index 1987ff1e7..e7c7b3a37 100644 --- a/tests/functional/let_array_substr10.asm +++ b/tests/functional/let_array_substr10.asm @@ -340,7 +340,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -352,9 +351,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -363,18 +363,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -382,7 +382,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -436,11 +436,8 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE diff --git a/tests/functional/let_array_substr11.asm b/tests/functional/let_array_substr11.asm index 8edd48907..7608d9357 100644 --- a/tests/functional/let_array_substr11.asm +++ b/tests/functional/let_array_substr11.asm @@ -340,7 +340,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -352,9 +351,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -363,18 +363,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -382,7 +382,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -436,11 +436,8 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE diff --git a/tests/functional/let_array_substr12.asm b/tests/functional/let_array_substr12.asm index 93d8ffc7e..b960c6bd3 100644 --- a/tests/functional/let_array_substr12.asm +++ b/tests/functional/let_array_substr12.asm @@ -340,7 +340,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -352,9 +351,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -363,18 +363,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -382,7 +382,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -436,11 +436,8 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE diff --git a/tests/functional/let_array_substr13.asm b/tests/functional/let_array_substr13.asm index 02ba114d2..77bc19dcb 100644 --- a/tests/functional/let_array_substr13.asm +++ b/tests/functional/let_array_substr13.asm @@ -340,7 +340,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -352,9 +351,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -363,18 +363,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -382,7 +382,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -436,11 +436,8 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE diff --git a/tests/functional/let_array_substr2.asm b/tests/functional/let_array_substr2.asm index 01348a61e..31b143183 100644 --- a/tests/functional/let_array_substr2.asm +++ b/tests/functional/let_array_substr2.asm @@ -340,7 +340,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -352,9 +351,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -363,18 +363,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -382,7 +382,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -436,11 +436,8 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE diff --git a/tests/functional/let_array_substr3.asm b/tests/functional/let_array_substr3.asm index 68cb5f692..b02cdcc9e 100644 --- a/tests/functional/let_array_substr3.asm +++ b/tests/functional/let_array_substr3.asm @@ -340,7 +340,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -352,9 +351,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -363,18 +363,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -382,7 +382,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -436,11 +436,8 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE diff --git a/tests/functional/let_array_substr5.asm b/tests/functional/let_array_substr5.asm index 423a7c1e2..6314a90e5 100644 --- a/tests/functional/let_array_substr5.asm +++ b/tests/functional/let_array_substr5.asm @@ -479,7 +479,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -491,9 +490,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -502,18 +502,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -521,7 +521,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -575,11 +575,8 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE diff --git a/tests/functional/let_array_substr7.asm b/tests/functional/let_array_substr7.asm index 12e21c615..4b66284a3 100644 --- a/tests/functional/let_array_substr7.asm +++ b/tests/functional/let_array_substr7.asm @@ -340,7 +340,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -352,9 +351,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -363,18 +363,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -382,7 +382,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -436,11 +436,8 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE diff --git a/tests/functional/let_array_substr9.asm b/tests/functional/let_array_substr9.asm index dfb10a3dd..75eb7aebd 100644 --- a/tests/functional/let_array_substr9.asm +++ b/tests/functional/let_array_substr9.asm @@ -340,7 +340,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -352,9 +351,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -363,18 +363,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -382,7 +382,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -436,11 +436,8 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE diff --git a/tests/functional/llc.asm b/tests/functional/llc.asm index 38c77678b..b9008f025 100644 --- a/tests/functional/llc.asm +++ b/tests/functional/llc.asm @@ -14,7 +14,6 @@ __START_PROGRAM: ei call __MEM_INIT ld hl, __LABEL0 - call __LOADSTR push hl xor a push af @@ -321,7 +320,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -333,9 +331,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -344,18 +343,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -363,7 +362,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -417,242 +416,14 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE ret ENDP -#line 32 "llc.bas" -#line 1 "loadstr.asm" -#line 1 "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 "error.asm" - ; Simple error control routines -; vim:ts=4:et: - 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 -#line 69 "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) - ; --------------------------------------------------------------------- -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 111 "/zxbasic/library-asm/alloc.asm" - ret z ; NULL -#line 113 "/zxbasic/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 -#line 2 "loadstr.asm" - ; Loads a string (ptr) from HL - ; and duplicates it on dynamic memory again - ; Finally, it returns result pointer in HL -__ILOADSTR: ; This is the indirect pointer entry HL = (HL) - ld a, h - or l - ret z - ld a, (hl) - inc hl - ld h, (hl) - ld l, a -__LOADSTR: ; __FASTCALL__ entry - ld a, h - or l - ret z ; Return if NULL - ld c, (hl) - inc hl - ld b, (hl) - dec hl ; BC = LEN(a$) - inc bc - inc bc ; BC = LEN(a$) + 2 (two bytes for length) - push hl - push bc - call __MEM_ALLOC - pop bc ; Recover length - pop de ; Recover origin - ld a, h - or l - ret z ; Return if NULL (No memory) - ex de, hl ; ldir takes HL as source, DE as destiny, so SWAP HL,DE - push de ; Saves destiny start - ldir ; Copies string (length number included) - pop hl ; Recovers destiny in hl as result - ret -#line 33 "llc.bas" +#line 31 "llc.bas" ZXBASIC_USER_DATA: _r: DEFB 00, 00 diff --git a/tests/functional/lvalsubstr_nolet.asm b/tests/functional/lvalsubstr_nolet.asm index 7ae15d25b..102e12a02 100644 --- a/tests/functional/lvalsubstr_nolet.asm +++ b/tests/functional/lvalsubstr_nolet.asm @@ -14,7 +14,6 @@ __START_PROGRAM: ei call __MEM_INIT ld hl, __LABEL0 - call __LOADSTR push hl xor a push af @@ -321,7 +320,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -333,9 +331,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -344,18 +343,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -363,7 +362,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -417,242 +416,14 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE ret ENDP -#line 32 "lvalsubstr_nolet.bas" -#line 1 "loadstr.asm" -#line 1 "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 "error.asm" - ; Simple error control routines -; vim:ts=4:et: - 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 -#line 69 "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) - ; --------------------------------------------------------------------- -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 111 "/zxbasic/library-asm/alloc.asm" - ret z ; NULL -#line 113 "/zxbasic/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 -#line 2 "loadstr.asm" - ; Loads a string (ptr) from HL - ; and duplicates it on dynamic memory again - ; Finally, it returns result pointer in HL -__ILOADSTR: ; This is the indirect pointer entry HL = (HL) - ld a, h - or l - ret z - ld a, (hl) - inc hl - ld h, (hl) - ld l, a -__LOADSTR: ; __FASTCALL__ entry - ld a, h - or l - ret z ; Return if NULL - ld c, (hl) - inc hl - ld b, (hl) - dec hl ; BC = LEN(a$) - inc bc - inc bc ; BC = LEN(a$) + 2 (two bytes for length) - push hl - push bc - call __MEM_ALLOC - pop bc ; Recover length - pop de ; Recover origin - ld a, h - or l - ret z ; Return if NULL (No memory) - ex de, hl ; ldir takes HL as source, DE as destiny, so SWAP HL,DE - push de ; Saves destiny start - ldir ; Copies string (length number included) - pop hl ; Recovers destiny in hl as result - ret -#line 33 "lvalsubstr_nolet.bas" +#line 31 "lvalsubstr_nolet.bas" ZXBASIC_USER_DATA: _a: DEFB 00, 00 diff --git a/tests/functional/strbase.asm b/tests/functional/strbase.asm index cf137b2dc..a172fbaa5 100644 --- a/tests/functional/strbase.asm +++ b/tests/functional/strbase.asm @@ -17,7 +17,6 @@ __START_PROGRAM: ld hl, _a call __STORE_STR ld hl, __LABEL1 - call __LOADSTR push hl xor a push af @@ -341,7 +340,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -353,9 +351,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -364,18 +363,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -383,7 +382,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -437,29 +436,37 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE ret ENDP -#line 52 "strbase.bas" -#line 1 "loadstr.asm" -#line 1 "alloc.asm" +#line 51 "strbase.bas" +#line 1 "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 "strcpy.asm" +#line 1 "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 MIT license + ; 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 MIT license on the internet + ; 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: @@ -500,7 +507,7 @@ __FREE_STR: ; | (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 + ; 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 @@ -541,7 +548,67 @@ __ERROR_CODE: __STOP: ld (ERR_NR), a ret -#line 69 "alloc.asm" +#line 70 "realloc.asm" +#line 1 "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. ; --------------------------------------------------------------------- ; MEM_ALLOC ; Allocates a block of memory in the heap. @@ -637,114 +704,7 @@ __MEM_SUBTRACT: inc hl ; Return hl ret ENDP -#line 2 "loadstr.asm" - ; Loads a string (ptr) from HL - ; and duplicates it on dynamic memory again - ; Finally, it returns result pointer in HL -__ILOADSTR: ; This is the indirect pointer entry HL = (HL) - ld a, h - or l - ret z - ld a, (hl) - inc hl - ld h, (hl) - ld l, a -__LOADSTR: ; __FASTCALL__ entry - ld a, h - or l - ret z ; Return if NULL - ld c, (hl) - inc hl - ld b, (hl) - dec hl ; BC = LEN(a$) - inc bc - inc bc ; BC = LEN(a$) + 2 (two bytes for length) - push hl - push bc - call __MEM_ALLOC - pop bc ; Recover length - pop de ; Recover origin - ld a, h - or l - ret z ; Return if NULL (No memory) - ex de, hl ; ldir takes HL as source, DE as destiny, so SWAP HL,DE - push de ; Saves destiny start - ldir ; Copies string (length number included) - pop hl ; Recovers destiny in hl as result - ret -#line 53 "strbase.bas" -#line 1 "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 "strcpy.asm" -#line 1 "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 71 "realloc.asm" ; --------------------------------------------------------------------- ; MEM_REALLOC ; Reallocates a block of memory in the heap. @@ -915,7 +875,7 @@ __STORE_STR: 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 -#line 54 "strbase.bas" +#line 52 "strbase.bas" #line 1 "storestr2.asm" ; Similar to __STORE_STR, but this one is called when ; the value of B$ if already duplicated onto the stack. @@ -947,7 +907,7 @@ __STORE_STR2: ld (hl), d dec hl ; HL points to mem address variable. This might be useful in the future. ret -#line 55 "strbase.bas" +#line 53 "strbase.bas" #line 1 "strslice.asm" ; String slicing library ; HL = Str pointer @@ -1045,7 +1005,7 @@ __FREE_ON_EXIT: pop hl ; Recover result ret ENDP -#line 56 "strbase.bas" +#line 54 "strbase.bas" ZXBASIC_USER_DATA: _a: DEFB 00, 00 diff --git a/tests/functional/strbase2.asm b/tests/functional/strbase2.asm index b2f4d3f83..8cc3e581d 100644 --- a/tests/functional/strbase2.asm +++ b/tests/functional/strbase2.asm @@ -18,7 +18,6 @@ __START_PROGRAM: ld hl, _a call __STORE_STR ld hl, __LABEL1 - call __LOADSTR push hl xor a push af @@ -350,7 +349,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -362,9 +360,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -373,18 +372,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -392,7 +391,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -446,242 +445,14 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE ret ENDP -#line 60 "strbase2.bas" -#line 1 "loadstr.asm" -#line 1 "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 "error.asm" - ; Simple error control routines -; vim:ts=4:et: - 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 -#line 69 "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) - ; --------------------------------------------------------------------- -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 111 "/zxbasic/library-asm/alloc.asm" - ret z ; NULL -#line 113 "/zxbasic/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 -#line 2 "loadstr.asm" - ; Loads a string (ptr) from HL - ; and duplicates it on dynamic memory again - ; Finally, it returns result pointer in HL -__ILOADSTR: ; This is the indirect pointer entry HL = (HL) - ld a, h - or l - ret z - ld a, (hl) - inc hl - ld h, (hl) - ld l, a -__LOADSTR: ; __FASTCALL__ entry - ld a, h - or l - ret z ; Return if NULL - ld c, (hl) - inc hl - ld b, (hl) - dec hl ; BC = LEN(a$) - inc bc - inc bc ; BC = LEN(a$) + 2 (two bytes for length) - push hl - push bc - call __MEM_ALLOC - pop bc ; Recover length - pop de ; Recover origin - ld a, h - or l - ret z ; Return if NULL (No memory) - ex de, hl ; ldir takes HL as source, DE as destiny, so SWAP HL,DE - push de ; Saves destiny start - ldir ; Copies string (length number included) - pop hl ; Recovers destiny in hl as result - ret -#line 61 "strbase2.bas" +#line 59 "strbase2.bas" #line 1 "print_eol_attr.asm" ; Calls PRINT_EOL and then COPY_ATTR, so saves ; 3 bytes @@ -752,6 +523,39 @@ __CLS_SCR: ENDP #line 7 "print.asm" #line 1 "in_screen.asm" +#line 1 "error.asm" + ; Simple error control routines +; vim:ts=4:et: + 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 +#line 3 "in_screen.asm" __IN_SCREEN: ; Returns NO carry if current coords (D, E) ; are OUT of the screen limits (MAXX, MAXY) @@ -1577,7 +1381,7 @@ __PRINT_TABLE: ; Jump table for 0 .. 22 codes PRINT_EOL_ATTR: call PRINT_EOL jp COPY_ATTR -#line 62 "strbase2.bas" +#line 60 "strbase2.bas" #line 1 "printstr.asm" ; PRINT command routine ; Prints string pointed by HL @@ -1619,7 +1423,7 @@ __PRINT_STR: ld d, a ; Saves a FLAG jp __PRINT_STR_LOOP ENDP -#line 63 "strbase2.bas" +#line 61 "strbase2.bas" #line 1 "storestr.asm" ; vim:ts=4:et:sw=4 ; Stores value of current string pointed by DE register into address pointed by HL @@ -1692,6 +1496,162 @@ __PRINT_STR: ; HL = BLOCK Start & DE = Length. ; An init directive is useful for initialization routines. ; They will be added automatically if needed. +#line 1 "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. + ; --------------------------------------------------------------------- + ; 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) + ; --------------------------------------------------------------------- +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 111 "/zxbasic/library-asm/alloc.asm" + ret z ; NULL +#line 113 "/zxbasic/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 +#line 71 "realloc.asm" ; --------------------------------------------------------------------- ; MEM_REALLOC ; Reallocates a block of memory in the heap. @@ -1862,7 +1822,7 @@ __STORE_STR: 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 -#line 64 "strbase2.bas" +#line 62 "strbase2.bas" #line 1 "storestr2.asm" ; Similar to __STORE_STR, but this one is called when ; the value of B$ if already duplicated onto the stack. @@ -1894,7 +1854,7 @@ __STORE_STR2: ld (hl), d dec hl ; HL points to mem address variable. This might be useful in the future. ret -#line 65 "strbase2.bas" +#line 63 "strbase2.bas" #line 1 "strslice.asm" ; String slicing library ; HL = Str pointer @@ -1992,7 +1952,7 @@ __FREE_ON_EXIT: pop hl ; Recover result ret ENDP -#line 66 "strbase2.bas" +#line 64 "strbase2.bas" ZXBASIC_USER_DATA: _a: DEFB 00, 00 diff --git a/tests/functional/substrlval.asm b/tests/functional/substrlval.asm index 05976c04a..62c64efaa 100644 --- a/tests/functional/substrlval.asm +++ b/tests/functional/substrlval.asm @@ -19,7 +19,6 @@ __LABEL__10: call __STORE_STR __LABEL__30: ld hl, __LABEL1 - call __LOADSTR push hl xor a push af @@ -335,7 +334,6 @@ __LETSUBSTR: LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 exx pop hl ; Return address pop de ; p1 @@ -347,9 +345,10 @@ __LETSUBSTR: exx push hl ; push ret addr back exx + push de ; B$ addr to be freed upon return (if A != 0) ld a, h or l - jp z, __FREE_STR0 ; Return if null + jp z, __FREE_STR ; Return if null ld c, (hl) inc hl ld b, (hl) ; BC = Str length @@ -358,18 +357,18 @@ __LETSUBSTR: exx ex de, hl or a - sbc hl, bc ; HL = Length of string requester by user + sbc hl, bc ; HL = Length of string requested by user inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 ex de, hl ; Saves it in DE pop hl ; HL = String length exx - jp c, __FREE_STR0 ; Return if greather - exx ; Return if p0 > p1 + jp c, __FREE_STR ; Return if p0 > p1 + exx or a sbc hl, bc ; P0 >= String length? exx - jp z, __FREE_STR0 ; Return if equal - jp c, __FREE_STR0 ; Return if greather + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater exx add hl, bc ; Add it back sbc hl, de ; Length of substring > string => Truncate it @@ -377,7 +376,7 @@ __LETSUBSTR: jr nc, __CONT0 ; Length of substring within a$ ld d, h ld e, l ; Truncate length of substring to fit within the strlen -__CONT0: ; At this point DE = Length of subtring to copy +__CONT0: ; At this point DE = Length of substring to copy ; BC = start of char to copy push de push bc @@ -431,29 +430,37 @@ __CONT1: pop hl pop de ldir ; Copy b$ into a$(x to y) - exx - ex de, hl -__FREE_STR0: - ex de, hl __FREE_STR: + pop hl ex af, af' or a ; If not 0, free jp nz, __MEM_FREE ret ENDP -#line 46 "substrlval.bas" -#line 1 "loadstr.asm" -#line 1 "alloc.asm" +#line 45 "substrlval.bas" +#line 1 "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 "strcpy.asm" +#line 1 "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 MIT license + ; 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 MIT license on the internet + ; 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: @@ -494,7 +501,7 @@ __FREE_STR: ; | (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 + ; 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 @@ -535,7 +542,67 @@ __ERROR_CODE: __STOP: ld (ERR_NR), a ret -#line 69 "alloc.asm" +#line 70 "realloc.asm" +#line 1 "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. ; --------------------------------------------------------------------- ; MEM_ALLOC ; Allocates a block of memory in the heap. @@ -631,114 +698,7 @@ __MEM_SUBTRACT: inc hl ; Return hl ret ENDP -#line 2 "loadstr.asm" - ; Loads a string (ptr) from HL - ; and duplicates it on dynamic memory again - ; Finally, it returns result pointer in HL -__ILOADSTR: ; This is the indirect pointer entry HL = (HL) - ld a, h - or l - ret z - ld a, (hl) - inc hl - ld h, (hl) - ld l, a -__LOADSTR: ; __FASTCALL__ entry - ld a, h - or l - ret z ; Return if NULL - ld c, (hl) - inc hl - ld b, (hl) - dec hl ; BC = LEN(a$) - inc bc - inc bc ; BC = LEN(a$) + 2 (two bytes for length) - push hl - push bc - call __MEM_ALLOC - pop bc ; Recover length - pop de ; Recover origin - ld a, h - or l - ret z ; Return if NULL (No memory) - ex de, hl ; ldir takes HL as source, DE as destiny, so SWAP HL,DE - push de ; Saves destiny start - ldir ; Copies string (length number included) - pop hl ; Recovers destiny in hl as result - ret -#line 47 "substrlval.bas" -#line 1 "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 "strcpy.asm" -#line 1 "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 71 "realloc.asm" ; --------------------------------------------------------------------- ; MEM_REALLOC ; Reallocates a block of memory in the heap. @@ -909,7 +869,7 @@ __STORE_STR: 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 -#line 48 "substrlval.bas" +#line 46 "substrlval.bas" ZXBASIC_USER_DATA: _a: DEFB 00, 00 diff --git a/tests/functional/sys_letsubstr0.asm b/tests/functional/sys_letsubstr0.asm new file mode 100644 index 000000000..a78a77dd7 --- /dev/null +++ b/tests/functional/sys_letsubstr0.asm @@ -0,0 +1,877 @@ + org 32768 + ; Defines HEAP SIZE +ZXBASIC_HEAP_SIZE EQU 4768 +__START_PROGRAM: + di + push ix + push iy + exx + push hl + exx + ld hl, 0 + add hl, sp + ld (__CALL_BACK__), hl + ei + call __MEM_INIT + ld de, __LABEL0 + ld hl, _a + call __STORE_STR + ld hl, __LABEL1 + push hl + xor a + push af + ld hl, 1 + push hl + ld hl, 1 + push hl + ld hl, (_a) + call __LETSUBSTR + ld hl, 0 + ld b, h + ld c, l +__END_PROGRAM: + di + ld hl, (__CALL_BACK__) + ld sp, hl + exx + pop hl + exx + pop iy + pop ix + ei + ret +__CALL_BACK__: + DEFW 0 +__LABEL0: + DEFW 0005h + DEFB 48h + DEFB 45h + DEFB 4Ch + DEFB 4Ch + DEFB 4Fh +__LABEL1: + DEFW 0001h + DEFB 41h +#line 1 "letsubstr.asm" + ; Substring assigment eg. LET a$(p0 TO p1) = "xxxx" + ; HL = Start of string + ; TOP of the stack -> p1 (16 bit, unsigned) + ; TOP -1 of the stack -> p0 register + ; TOP -2 Flag (popped out in A register) + ; A Register => 0 if HL is not freed from memory + ; => Not 0 if HL must be freed from memory on exit + ; TOP -3 B$ address +#line 1 "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. +#line 1 "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 + ; --------------------------------------------------------------------- +__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 +#line 69 "free.asm" + ; --------------------------------------------------------------------- + ; MEM_FREE + ; Frees a block of memory + ; +; Parameters: + ; HL = Pointer to the block to be freed. If HL is NULL (0) nothing + ; is done + ; --------------------------------------------------------------------- +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 +#line 11 "letsubstr.asm" +__LETSUBSTR: + PROC + LOCAL __CONT0 + LOCAL __CONT1 + LOCAL __CONT2 + LOCAL __FREE_STR + exx + pop hl ; Return address + pop de ; p1 + pop bc ; p0 + exx + pop af ; Flag + ex af, af' ; Save it for later + pop de ; B$ + exx + push hl ; push ret addr back + exx + push de ; B$ addr to be freed upon return (if A != 0) + ld a, h + or l + jp z, __FREE_STR ; Return if null + ld c, (hl) + inc hl + ld b, (hl) ; BC = Str length + inc hl ; HL = String start + push bc + exx + ex de, hl + or a + sbc hl, bc ; HL = Length of string requested by user + inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 + ex de, hl ; Saves it in DE + pop hl ; HL = String length + exx + jp c, __FREE_STR ; Return if p0 > p1 + exx + or a + sbc hl, bc ; P0 >= String length? + exx + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater + exx + add hl, bc ; Add it back + sbc hl, de ; Length of substring > string => Truncate it + add hl, de ; add it back + jr nc, __CONT0 ; Length of substring within a$ + ld d, h + ld e, l ; Truncate length of substring to fit within the strlen +__CONT0: ; At this point DE = Length of substring to copy + ; BC = start of char to copy + push de + push bc + exx + pop bc + add hl, bc ; Start address (within a$) so copy from b$ (in DE) + push hl + exx + pop hl ; Start address (within a$) so copy from b$ (in DE) + ld b, d ; Length of string + ld c, e + ld (hl), ' ' + ld d, h + ld e, l + inc de + dec bc + ld a, b + or c + jr z, __CONT2 + ; At this point HL = DE = Start of Write zone in a$ + ; BC = Number of chars to write + ldir +__CONT2: + pop bc ; Recovers Length of string to copy + exx + ex de, hl ; HL = Source, DE = Target + ld a, h + or l + jp z, __FREE_STR ; Return if B$ is NULL + ld c, (hl) + inc hl + ld b, (hl) + inc hl + ld a, b + or c + jp z, __FREE_STR ; Return if len(b$) = 0 + ; Now if len(b$) < len(char to copy), copy only len(b$) chars + push de + push hl + push bc + exx + pop hl ; LEN (b$) + or a + sbc hl, bc + add hl, bc + jr nc, __CONT1 + ; If len(b$) < len(to copy) + ld b, h ; BC = len(to copy) + ld c, l +__CONT1: + pop hl + pop de + ldir ; Copy b$ into a$(x to y) +__FREE_STR: + pop hl + ex af, af' + or a ; If not 0, free + jp nz, __MEM_FREE + ret + ENDP +#line 41 "sys_letsubstr0.bas" +#line 1 "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 "strcpy.asm" +#line 1 "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 "error.asm" + ; Simple error control routines +; vim:ts=4:et: + 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 +#line 70 "realloc.asm" +#line 1 "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. + ; --------------------------------------------------------------------- + ; 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) + ; --------------------------------------------------------------------- +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 111 "/zxbasic/library-asm/alloc.asm" + ret z ; NULL +#line 113 "/zxbasic/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 +#line 71 "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. + ; + ; --------------------------------------------------------------------- +__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 +#line 2 "strcpy.asm" + ; String library +__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 +#line 14 "storestr.asm" +__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 +#line 42 "sys_letsubstr0.bas" +ZXBASIC_USER_DATA: +_a: + DEFB 00, 00 +ZXBASIC_MEM_HEAP: + ; Defines DATA END +ZXBASIC_USER_DATA_END EQU ZXBASIC_MEM_HEAP + ZXBASIC_HEAP_SIZE + ; Defines USER DATA Length in bytes +ZXBASIC_USER_DATA_LEN EQU ZXBASIC_USER_DATA_END - ZXBASIC_USER_DATA + END diff --git a/tests/functional/sys_letsubstr0.bas b/tests/functional/sys_letsubstr0.bas new file mode 100644 index 000000000..ad80ddc96 --- /dev/null +++ b/tests/functional/sys_letsubstr0.bas @@ -0,0 +1,4 @@ + +LET a$ = "HELLO" +LET a$(1) = "A" + diff --git a/tests/functional/sys_letsubstr1.asm b/tests/functional/sys_letsubstr1.asm new file mode 100644 index 000000000..b042724e0 --- /dev/null +++ b/tests/functional/sys_letsubstr1.asm @@ -0,0 +1,882 @@ + org 32768 + ; Defines HEAP SIZE +ZXBASIC_HEAP_SIZE EQU 4768 +__START_PROGRAM: + di + push ix + push iy + exx + push hl + exx + ld hl, 0 + add hl, sp + ld (__CALL_BACK__), hl + ei + call __MEM_INIT + ld de, __LABEL0 + ld hl, _a + call __STORE_STR + ld de, __LABEL1 + ld hl, _c + call __STORE_STR + ld hl, (_c) + push hl + xor a + push af + ld hl, 1 + push hl + ld hl, 1 + push hl + ld hl, (_a) + call __LETSUBSTR + ld hl, 0 + ld b, h + ld c, l +__END_PROGRAM: + di + ld hl, (__CALL_BACK__) + ld sp, hl + exx + pop hl + exx + pop iy + pop ix + ei + ret +__CALL_BACK__: + DEFW 0 +__LABEL0: + DEFW 0005h + DEFB 48h + DEFB 45h + DEFB 4Ch + DEFB 4Ch + DEFB 4Fh +__LABEL1: + DEFW 0001h + DEFB 41h +#line 1 "letsubstr.asm" + ; Substring assigment eg. LET a$(p0 TO p1) = "xxxx" + ; HL = Start of string + ; TOP of the stack -> p1 (16 bit, unsigned) + ; TOP -1 of the stack -> p0 register + ; TOP -2 Flag (popped out in A register) + ; A Register => 0 if HL is not freed from memory + ; => Not 0 if HL must be freed from memory on exit + ; TOP -3 B$ address +#line 1 "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. +#line 1 "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 + ; --------------------------------------------------------------------- +__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 +#line 69 "free.asm" + ; --------------------------------------------------------------------- + ; MEM_FREE + ; Frees a block of memory + ; +; Parameters: + ; HL = Pointer to the block to be freed. If HL is NULL (0) nothing + ; is done + ; --------------------------------------------------------------------- +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 +#line 11 "letsubstr.asm" +__LETSUBSTR: + PROC + LOCAL __CONT0 + LOCAL __CONT1 + LOCAL __CONT2 + LOCAL __FREE_STR + exx + pop hl ; Return address + pop de ; p1 + pop bc ; p0 + exx + pop af ; Flag + ex af, af' ; Save it for later + pop de ; B$ + exx + push hl ; push ret addr back + exx + push de ; B$ addr to be freed upon return (if A != 0) + ld a, h + or l + jp z, __FREE_STR ; Return if null + ld c, (hl) + inc hl + ld b, (hl) ; BC = Str length + inc hl ; HL = String start + push bc + exx + ex de, hl + or a + sbc hl, bc ; HL = Length of string requested by user + inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 + ex de, hl ; Saves it in DE + pop hl ; HL = String length + exx + jp c, __FREE_STR ; Return if p0 > p1 + exx + or a + sbc hl, bc ; P0 >= String length? + exx + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater + exx + add hl, bc ; Add it back + sbc hl, de ; Length of substring > string => Truncate it + add hl, de ; add it back + jr nc, __CONT0 ; Length of substring within a$ + ld d, h + ld e, l ; Truncate length of substring to fit within the strlen +__CONT0: ; At this point DE = Length of substring to copy + ; BC = start of char to copy + push de + push bc + exx + pop bc + add hl, bc ; Start address (within a$) so copy from b$ (in DE) + push hl + exx + pop hl ; Start address (within a$) so copy from b$ (in DE) + ld b, d ; Length of string + ld c, e + ld (hl), ' ' + ld d, h + ld e, l + inc de + dec bc + ld a, b + or c + jr z, __CONT2 + ; At this point HL = DE = Start of Write zone in a$ + ; BC = Number of chars to write + ldir +__CONT2: + pop bc ; Recovers Length of string to copy + exx + ex de, hl ; HL = Source, DE = Target + ld a, h + or l + jp z, __FREE_STR ; Return if B$ is NULL + ld c, (hl) + inc hl + ld b, (hl) + inc hl + ld a, b + or c + jp z, __FREE_STR ; Return if len(b$) = 0 + ; Now if len(b$) < len(char to copy), copy only len(b$) chars + push de + push hl + push bc + exx + pop hl ; LEN (b$) + or a + sbc hl, bc + add hl, bc + jr nc, __CONT1 + ; If len(b$) < len(to copy) + ld b, h ; BC = len(to copy) + ld c, l +__CONT1: + pop hl + pop de + ldir ; Copy b$ into a$(x to y) +__FREE_STR: + pop hl + ex af, af' + or a ; If not 0, free + jp nz, __MEM_FREE + ret + ENDP +#line 44 "sys_letsubstr1.bas" +#line 1 "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 "strcpy.asm" +#line 1 "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 "error.asm" + ; Simple error control routines +; vim:ts=4:et: + 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 +#line 70 "realloc.asm" +#line 1 "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. + ; --------------------------------------------------------------------- + ; 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) + ; --------------------------------------------------------------------- +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 111 "/zxbasic/library-asm/alloc.asm" + ret z ; NULL +#line 113 "/zxbasic/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 +#line 71 "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. + ; + ; --------------------------------------------------------------------- +__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 +#line 2 "strcpy.asm" + ; String library +__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 +#line 14 "storestr.asm" +__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 +#line 45 "sys_letsubstr1.bas" +ZXBASIC_USER_DATA: +_a: + DEFB 00, 00 +_c: + DEFB 00, 00 +ZXBASIC_MEM_HEAP: + ; Defines DATA END +ZXBASIC_USER_DATA_END EQU ZXBASIC_MEM_HEAP + ZXBASIC_HEAP_SIZE + ; Defines USER DATA Length in bytes +ZXBASIC_USER_DATA_LEN EQU ZXBASIC_USER_DATA_END - ZXBASIC_USER_DATA + END diff --git a/tests/functional/sys_letsubstr1.bas b/tests/functional/sys_letsubstr1.bas new file mode 100644 index 000000000..64e7cef93 --- /dev/null +++ b/tests/functional/sys_letsubstr1.bas @@ -0,0 +1,5 @@ + +LET a$ = "HELLO" +LET c$ = "A" +LET a$(1) = c$ + diff --git a/tests/functional/sys_letsubstr2.asm b/tests/functional/sys_letsubstr2.asm new file mode 100644 index 000000000..85493064c --- /dev/null +++ b/tests/functional/sys_letsubstr2.asm @@ -0,0 +1,993 @@ + org 32768 + ; Defines HEAP SIZE +ZXBASIC_HEAP_SIZE EQU 4768 +__START_PROGRAM: + di + push ix + push iy + exx + push hl + exx + ld hl, 0 + add hl, sp + ld (__CALL_BACK__), hl + ei + call __MEM_INIT + ld de, __LABEL0 + ld hl, _a + call __STORE_STR + ld de, __LABEL1 + ld hl, _c + call __STORE_STR + ld de, __LABEL1 + ld hl, (_c) + call __ADDSTR + push hl + ld a, 1 + push af + ld hl, 1 + push hl + ld hl, 1 + push hl + ld hl, (_a) + call __LETSUBSTR + ld hl, 0 + ld b, h + ld c, l +__END_PROGRAM: + di + ld hl, (__CALL_BACK__) + ld sp, hl + exx + pop hl + exx + pop iy + pop ix + ei + ret +__CALL_BACK__: + DEFW 0 +__LABEL0: + DEFW 0005h + DEFB 48h + DEFB 45h + DEFB 4Ch + DEFB 4Ch + DEFB 4Fh +__LABEL1: + DEFW 0001h + DEFB 41h +#line 1 "letsubstr.asm" + ; Substring assigment eg. LET a$(p0 TO p1) = "xxxx" + ; HL = Start of string + ; TOP of the stack -> p1 (16 bit, unsigned) + ; TOP -1 of the stack -> p0 register + ; TOP -2 Flag (popped out in A register) + ; A Register => 0 if HL is not freed from memory + ; => Not 0 if HL must be freed from memory on exit + ; TOP -3 B$ address +#line 1 "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. +#line 1 "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 + ; --------------------------------------------------------------------- +__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 +#line 69 "free.asm" + ; --------------------------------------------------------------------- + ; MEM_FREE + ; Frees a block of memory + ; +; Parameters: + ; HL = Pointer to the block to be freed. If HL is NULL (0) nothing + ; is done + ; --------------------------------------------------------------------- +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 +#line 11 "letsubstr.asm" +__LETSUBSTR: + PROC + LOCAL __CONT0 + LOCAL __CONT1 + LOCAL __CONT2 + LOCAL __FREE_STR + exx + pop hl ; Return address + pop de ; p1 + pop bc ; p0 + exx + pop af ; Flag + ex af, af' ; Save it for later + pop de ; B$ + exx + push hl ; push ret addr back + exx + push de ; B$ addr to be freed upon return (if A != 0) + ld a, h + or l + jp z, __FREE_STR ; Return if null + ld c, (hl) + inc hl + ld b, (hl) ; BC = Str length + inc hl ; HL = String start + push bc + exx + ex de, hl + or a + sbc hl, bc ; HL = Length of string requested by user + inc hl ; len (a$(p0 TO p1)) = p1 - p0 + 1 + ex de, hl ; Saves it in DE + pop hl ; HL = String length + exx + jp c, __FREE_STR ; Return if p0 > p1 + exx + or a + sbc hl, bc ; P0 >= String length? + exx + jp z, __FREE_STR ; Return if equal + jp c, __FREE_STR ; Return if greater + exx + add hl, bc ; Add it back + sbc hl, de ; Length of substring > string => Truncate it + add hl, de ; add it back + jr nc, __CONT0 ; Length of substring within a$ + ld d, h + ld e, l ; Truncate length of substring to fit within the strlen +__CONT0: ; At this point DE = Length of substring to copy + ; BC = start of char to copy + push de + push bc + exx + pop bc + add hl, bc ; Start address (within a$) so copy from b$ (in DE) + push hl + exx + pop hl ; Start address (within a$) so copy from b$ (in DE) + ld b, d ; Length of string + ld c, e + ld (hl), ' ' + ld d, h + ld e, l + inc de + dec bc + ld a, b + or c + jr z, __CONT2 + ; At this point HL = DE = Start of Write zone in a$ + ; BC = Number of chars to write + ldir +__CONT2: + pop bc ; Recovers Length of string to copy + exx + ex de, hl ; HL = Source, DE = Target + ld a, h + or l + jp z, __FREE_STR ; Return if B$ is NULL + ld c, (hl) + inc hl + ld b, (hl) + inc hl + ld a, b + or c + jp z, __FREE_STR ; Return if len(b$) = 0 + ; Now if len(b$) < len(char to copy), copy only len(b$) chars + push de + push hl + push bc + exx + pop hl ; LEN (b$) + or a + sbc hl, bc + add hl, bc + jr nc, __CONT1 + ; If len(b$) < len(to copy) + ld b, h ; BC = len(to copy) + ld c, l +__CONT1: + pop hl + pop de + ldir ; Copy b$ into a$(x to y) +__FREE_STR: + pop hl + ex af, af' + or a ; If not 0, free + jp nz, __MEM_FREE + ret + ENDP +#line 46 "sys_letsubstr2.bas" +#line 1 "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 "strcpy.asm" +#line 1 "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 "error.asm" + ; Simple error control routines +; vim:ts=4:et: + 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 +#line 70 "realloc.asm" +#line 1 "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. + ; --------------------------------------------------------------------- + ; 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) + ; --------------------------------------------------------------------- +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 111 "/zxbasic/library-asm/alloc.asm" + ret z ; NULL +#line 113 "/zxbasic/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 +#line 71 "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. + ; + ; --------------------------------------------------------------------- +__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 +#line 2 "strcpy.asm" + ; String library +__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 +#line 14 "storestr.asm" +__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 +#line 47 "sys_letsubstr2.bas" +#line 1 "strcat.asm" +#line 1 "strlen.asm" + ; Returns len if a string + ; If a string is NULL, its len is also 0 + ; Result returned in HL +__STRLEN: ; Direct FASTCALL entry + ld a, h + or l + ret z + ld a, (hl) + inc hl + ld h, (hl) ; LEN(str) in HL + ld l, a + ret +#line 3 "strcat.asm" +__ADDSTR: ; Implements c$ = a$ + b$ + ; hl = &a$, de = &b$ (pointers) +__STRCAT2: ; This routine creates a new string in dynamic space + ; making room for it. Then copies a$ + b$ into it. + ; HL = a$, DE = b$ + PROC + LOCAL __STR_CONT + LOCAL __STRCATEND + push hl + call __STRLEN + ld c, l + ld b, h ; BC = LEN(a$) + ex (sp), hl ; (SP) = LEN (a$), HL = a$ + push hl ; Saves pointer to a$ + inc bc + inc bc ; +2 bytes to store length + ex de, hl + push hl + call __STRLEN + ; HL = len(b$) + add hl, bc ; Total str length => 2 + len(a$) + len(b$) + ld c, l + ld b, h ; BC = Total str length + 2 + call __MEM_ALLOC + pop de ; HL = c$, DE = b$ + ex de, hl ; HL = b$, DE = c$ + ex (sp), hl ; HL = a$, (SP) = b$ + exx + pop de ; D'E' = b$ + exx + pop bc ; LEN(a$) + ld a, d + or e + ret z ; If no memory: RETURN +__STR_CONT: + push de ; Address of c$ + ld a, h + or l + jr nz, __STR_CONT1 ; If len(a$) != 0 do copy + ; a$ is NULL => uses HL = DE for transfer + ld h, d + ld l, e + ld (hl), a ; This will copy 00 00 at (DE) location + inc de ; + dec bc ; Ensure BC will be set to 1 in the next step +__STR_CONT1: ; Copies a$ (HL) into c$ (DE) + inc bc + inc bc ; BC = BC + 2 + ldir ; MEMCOPY: c$ = a$ + pop hl ; HL = c$ + exx + push de ; Recovers b$; A ex hl,hl' would be very handy + exx + pop de ; DE = b$ +__STRCAT: ; ConCATenate two strings a$ = a$ + b$. HL = ptr to a$, DE = ptr to b$ + ; NOTE: Both DE, BC and AF are modified and lost + ; Returns HL (pointer to a$) + ; a$ Must be NOT NULL + ld a, d + or e + ret z ; Returns if de is NULL (nothing to copy) + push hl ; Saves HL to return it later + ld c, (hl) + inc hl + ld b, (hl) + inc hl + add hl, bc ; HL = end of (a$) string ; bc = len(a$) + push bc ; Saves LEN(a$) for later + ex de, hl ; DE = end of string (Begin of copy addr) + ld c, (hl) + inc hl + ld b, (hl) ; BC = len(b$) + ld a, b + or c + jr z, __STRCATEND; Return if len(b$) == 0 + push bc ; Save LEN(b$) + inc hl ; Skip 2nd byte of len(b$) + ldir ; Concatenate b$ + pop bc ; Recovers length (b$) + pop hl ; Recovers length (a$) + add hl, bc ; HL = LEN(a$) + LEN(b$) = LEN(a$+b$) + ex de, hl ; DE = LEN(a$+b$) + pop hl + ld (hl), e ; Updates new LEN and return + inc hl + ld (hl), d + dec hl + ret +__STRCATEND: + pop hl ; Removes Len(a$) + pop hl ; Restores original HL, so HL = a$ + ret + ENDP +#line 48 "sys_letsubstr2.bas" +ZXBASIC_USER_DATA: +_a: + DEFB 00, 00 +_c: + DEFB 00, 00 +ZXBASIC_MEM_HEAP: + ; Defines DATA END +ZXBASIC_USER_DATA_END EQU ZXBASIC_MEM_HEAP + ZXBASIC_HEAP_SIZE + ; Defines USER DATA Length in bytes +ZXBASIC_USER_DATA_LEN EQU ZXBASIC_USER_DATA_END - ZXBASIC_USER_DATA + END diff --git a/tests/functional/sys_letsubstr2.bas b/tests/functional/sys_letsubstr2.bas new file mode 100644 index 000000000..0e9100691 --- /dev/null +++ b/tests/functional/sys_letsubstr2.bas @@ -0,0 +1,5 @@ + +LET a$ = "HELLO" +LET c$ = "A" +LET a$(1) = c$ + "A" +