From 0e23e9baf8214f3a942a3bf408f017610eb9332e Mon Sep 17 00:00:00 2001 From: Jose Rodriguez Date: Fri, 15 Nov 2019 01:13:51 +0100 Subject: [PATCH 1/3] Fix error with LETSUBSTR Expressions like LET a$(4) = "A" were causing a memory leak. Fixed. --- arch/zx48k/translator.py | 7 +- library-asm/letsubstr.asm | 25 +- tests/functional/id_substr_eq_expr.asm | 168 +++++++- tests/functional/let_array_substr.asm | 279 +++++++++++- tests/functional/let_array_substr1.asm | 279 +++++++++++- tests/functional/let_array_substr10.asm | 228 +++++++++- tests/functional/let_array_substr11.asm | 228 +++++++++- tests/functional/let_array_substr12.asm | 228 +++++++++- tests/functional/let_array_substr13.asm | 228 +++++++++- tests/functional/let_array_substr2.asm | 228 +++++++++- tests/functional/let_array_substr3.asm | 228 +++++++++- tests/functional/let_array_substr5.asm | 279 +++++++++++- tests/functional/let_array_substr7.asm | 228 +++++++++- tests/functional/let_array_substr9.asm | 228 +++++++++- tests/functional/llc.asm | 168 +++++++- tests/functional/lvalsubstr_nolet.asm | 168 +++++++- tests/functional/strbase.asm | 269 +++++++++++- tests/functional/strbase2.asm | 551 +++++++++++++++++++++++- tests/functional/substrlval.asm | 230 +++++++++- 19 files changed, 3965 insertions(+), 282 deletions(-) diff --git a/arch/zx48k/translator.py b/arch/zx48k/translator.py index dcf49f860..6a45e243f 100644 --- a/arch/zx48k/translator.py +++ b/arch/zx48k/translator.py @@ -315,12 +315,7 @@ 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 - else: - self.ic_param(TYPE.ubyte, 0) + self.ic_param(TYPE.ubyte, 1) yield node.children[1] self.ic_param(gl.PTR_TYPE, node.children[1].t) 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..e4a397e81 100644 --- a/tests/functional/id_substr_eq_expr.asm +++ b/tests/functional/id_substr_eq_expr.asm @@ -16,7 +16,7 @@ __START_PROGRAM: ld hl, __LABEL0 call __LOADSTR push hl - xor a + ld a, 1 push af ld hl, 1 push hl @@ -48,6 +48,7 @@ __LABEL0: DEFB 6Ch DEFB 6Fh #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) @@ -56,7 +57,9 @@ __LABEL0: ; 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) @@ -67,8 +70,10 @@ __LABEL0: ; 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 @@ -106,17 +111,24 @@ __LABEL0: ; | | ; | (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) @@ -127,8 +139,10 @@ __LABEL0: ; 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 @@ -166,16 +180,25 @@ __LABEL0: ; | | ; | (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 @@ -183,6 +206,7 @@ __LABEL0: __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: @@ -192,36 +216,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -230,44 +264,57 @@ __MEM_INIT2: ; 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) @@ -276,8 +323,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -287,27 +336,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -317,68 +373,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -387,24 +466,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -414,26 +502,30 @@ __CONT2: 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) - 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) @@ -444,8 +536,10 @@ __FREE_STR: ; 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 @@ -483,24 +577,37 @@ __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 ; 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 @@ -513,6 +620,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -520,12 +629,16 @@ __ERROR: __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. @@ -537,32 +650,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -570,12 +690,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -601,11 +723,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -613,18 +738,24 @@ __MEM_SUBTRACT: 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 @@ -633,30 +764,37 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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" + 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..ce15f4369 100644 --- a/tests/functional/let_array_substr.asm +++ b/tests/functional/let_array_substr.asm @@ -73,6 +73,7 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #line 1 "array.asm" + ; vim: ts=4:et:sw=4: ; Copyleft (K) by Jose M. Rodriguez de la Rosa ; (a.k.a. Boriel) @@ -83,48 +84,66 @@ __LABEL1: ; HL = Start of array memory (First two bytes contains N-1 dimensions) ; Dimension values on the stack, (top of the stack, highest dimension) ; E.g. A(2, 4) -> PUSH <4>; PUSH <2> + ; For any array of N dimension A(aN-1, ..., a1, a0) ; and dimensions D[bN-1, ..., b1, b0], the offset is calculated as ; O = [a0 + b0 * (a1 + b1 * (a2 + ... bN-2(aN-1)))] ; What I will do here is to calculate the following sequence: ; ((aN-1 * bN-2) + aN-2) * bN-3 + ... + + #line 1 "mul16.asm" + __MUL16: ; Mutiplies HL with the last value stored into de stack ; Works for both signed and unsigned + PROC + LOCAL __MUL16LOOP LOCAL __MUL16NOADD + ex de, hl pop hl ; Return address ex (sp), hl ; CALLEE caller convention + __MUL16_FAST: ld b, 16 ld a, h ld c, l ld hl, 0 + __MUL16LOOP: add hl, hl ; hl << 1 sla c rla ; a,c << 1 jp nc, __MUL16NOADD add hl, de + __MUL16NOADD: djnz __MUL16LOOP + ret ; Result in hl (16 lower bits) + ENDP + #line 20 "array.asm" -#line 24 "/zxbasic/library-asm/array.asm" + +#line 24 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" + __ARRAY_PTR: ;; computes an array offset from a pointer ld c, (hl) inc hl ld h, (hl) ld l, c + __ARRAY: PROC + LOCAL LOOP LOCAL ARRAY_END LOCAL RET_ADDRESS ; Stores return address LOCAL TMP_ARR_PTR ; Stores pointer temporarily + ld e, (hl) inc hl ld d, (hl) @@ -133,6 +152,7 @@ __ARRAY: ex de, hl ex (sp), hl ; Return address in HL, array address in the stack ld (RET_ADDRESS + 1), hl ; Stores it for later + exx pop hl ; Will use H'L' as the pointer ld c, (hl) ; Loads Number of dimensions from (hl) @@ -140,16 +160,22 @@ __ARRAY: ld b, (hl) inc hl ; Ready exx + ld hl, 0 ; HL = Offset "accumulator" + LOOP: -#line 62 "/zxbasic/library-asm/array.asm" +#line 62 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" pop bc ; Get next index (Ai) from the stack -#line 72 "/zxbasic/library-asm/array.asm" + +#line 72 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" + add hl, bc ; Adds current index + exx ; Checks if B'C' = 0 ld a, b ; Which means we must exit (last element is not multiplied by anything) or c jr z, ARRAY_END ; if B'Ci == 0 we are done + ld e, (hl) ; Loads next dimension into D'E' inc hl ld d, (hl) @@ -158,20 +184,26 @@ LOOP: dec bc ; Decrements loop counter exx pop de ; DE = Max bound Number (i-th dimension) + call __FNMUL jp LOOP + ARRAY_END: ld a, (hl) exx -#line 101 "/zxbasic/library-asm/array.asm" + +#line 101 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" LOCAL ARRAY_SIZE_LOOP + ex de, hl ld hl, 0 ld b, a ARRAY_SIZE_LOOP: add hl, de djnz ARRAY_SIZE_LOOP -#line 111 "/zxbasic/library-asm/array.asm" + +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" + ex de, hl ld hl, (TMP_ARR_PTR) ld a, (hl) @@ -179,29 +211,39 @@ ARRAY_SIZE_LOOP: ld h, (hl) ld l, a add hl, de ; Adds element start + RET_ADDRESS: jp 0 + ;; Performs a faster multiply for little 16bit numbs LOCAL __FNMUL, __FNMUL2 + __FNMUL: xor a or h jp nz, __MUL16_FAST or l ret z + cp 33 jp nc, __MUL16_FAST + ld b, l ld l, h ; HL = 0 + __FNMUL2: add hl, de djnz __FNMUL2 ret + TMP_ARR_PTR: DW 0 ; temporary storage for pointer to tables + ENDP + #line 61 "let_array_substr.bas" #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) @@ -210,7 +252,9 @@ TMP_ARR_PTR: ; 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) @@ -221,8 +265,10 @@ TMP_ARR_PTR: ; 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 @@ -260,17 +306,24 @@ TMP_ARR_PTR: ; | | ; | (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) @@ -281,8 +334,10 @@ TMP_ARR_PTR: ; 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 @@ -320,16 +375,25 @@ TMP_ARR_PTR: ; | | ; | (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 @@ -337,6 +401,7 @@ TMP_ARR_PTR: __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: @@ -346,36 +411,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -384,44 +459,57 @@ __MEM_INIT2: ; 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) @@ -430,8 +518,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -441,27 +531,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -471,68 +568,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -541,24 +661,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -568,26 +697,30 @@ __CONT2: 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) - 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 62 "let_array_substr.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) @@ -598,8 +731,10 @@ __FREE_STR: ; 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 @@ -637,24 +772,37 @@ __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 ; 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 @@ -667,6 +815,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -674,12 +824,16 @@ __ERROR: __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. @@ -691,32 +845,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -724,12 +885,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -755,11 +918,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -767,18 +933,24 @@ __MEM_SUBTRACT: 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 @@ -787,24 +959,30 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -812,6 +990,7 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 63 "let_array_substr.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$) @@ -822,8 +1001,12 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -834,8 +1017,10 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -873,16 +1058,28 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -905,19 +1102,25 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -926,67 +1129,89 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -995,16 +1220,20 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1020,6 +1249,7 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret + __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1027,33 +1257,44 @@ __NOTHING_TO_COPY: 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 64 "let_array_substr.bas" + ZXBASIC_USER_DATA: _b: DEFB 03h diff --git a/tests/functional/let_array_substr1.asm b/tests/functional/let_array_substr1.asm index ab4600b83..59b47decc 100644 --- a/tests/functional/let_array_substr1.asm +++ b/tests/functional/let_array_substr1.asm @@ -73,6 +73,7 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #line 1 "array.asm" + ; vim: ts=4:et:sw=4: ; Copyleft (K) by Jose M. Rodriguez de la Rosa ; (a.k.a. Boriel) @@ -83,48 +84,66 @@ __LABEL1: ; HL = Start of array memory (First two bytes contains N-1 dimensions) ; Dimension values on the stack, (top of the stack, highest dimension) ; E.g. A(2, 4) -> PUSH <4>; PUSH <2> + ; For any array of N dimension A(aN-1, ..., a1, a0) ; and dimensions D[bN-1, ..., b1, b0], the offset is calculated as ; O = [a0 + b0 * (a1 + b1 * (a2 + ... bN-2(aN-1)))] ; What I will do here is to calculate the following sequence: ; ((aN-1 * bN-2) + aN-2) * bN-3 + ... + + #line 1 "mul16.asm" + __MUL16: ; Mutiplies HL with the last value stored into de stack ; Works for both signed and unsigned + PROC + LOCAL __MUL16LOOP LOCAL __MUL16NOADD + ex de, hl pop hl ; Return address ex (sp), hl ; CALLEE caller convention + __MUL16_FAST: ld b, 16 ld a, h ld c, l ld hl, 0 + __MUL16LOOP: add hl, hl ; hl << 1 sla c rla ; a,c << 1 jp nc, __MUL16NOADD add hl, de + __MUL16NOADD: djnz __MUL16LOOP + ret ; Result in hl (16 lower bits) + ENDP + #line 20 "array.asm" -#line 24 "/zxbasic/library-asm/array.asm" + +#line 24 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" + __ARRAY_PTR: ;; computes an array offset from a pointer ld c, (hl) inc hl ld h, (hl) ld l, c + __ARRAY: PROC + LOCAL LOOP LOCAL ARRAY_END LOCAL RET_ADDRESS ; Stores return address LOCAL TMP_ARR_PTR ; Stores pointer temporarily + ld e, (hl) inc hl ld d, (hl) @@ -133,6 +152,7 @@ __ARRAY: ex de, hl ex (sp), hl ; Return address in HL, array address in the stack ld (RET_ADDRESS + 1), hl ; Stores it for later + exx pop hl ; Will use H'L' as the pointer ld c, (hl) ; Loads Number of dimensions from (hl) @@ -140,16 +160,22 @@ __ARRAY: ld b, (hl) inc hl ; Ready exx + ld hl, 0 ; HL = Offset "accumulator" + LOOP: -#line 62 "/zxbasic/library-asm/array.asm" +#line 62 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" pop bc ; Get next index (Ai) from the stack -#line 72 "/zxbasic/library-asm/array.asm" + +#line 72 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" + add hl, bc ; Adds current index + exx ; Checks if B'C' = 0 ld a, b ; Which means we must exit (last element is not multiplied by anything) or c jr z, ARRAY_END ; if B'Ci == 0 we are done + ld e, (hl) ; Loads next dimension into D'E' inc hl ld d, (hl) @@ -158,20 +184,26 @@ LOOP: dec bc ; Decrements loop counter exx pop de ; DE = Max bound Number (i-th dimension) + call __FNMUL jp LOOP + ARRAY_END: ld a, (hl) exx -#line 101 "/zxbasic/library-asm/array.asm" + +#line 101 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" LOCAL ARRAY_SIZE_LOOP + ex de, hl ld hl, 0 ld b, a ARRAY_SIZE_LOOP: add hl, de djnz ARRAY_SIZE_LOOP -#line 111 "/zxbasic/library-asm/array.asm" + +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" + ex de, hl ld hl, (TMP_ARR_PTR) ld a, (hl) @@ -179,29 +211,39 @@ ARRAY_SIZE_LOOP: ld h, (hl) ld l, a add hl, de ; Adds element start + RET_ADDRESS: jp 0 + ;; Performs a faster multiply for little 16bit numbs LOCAL __FNMUL, __FNMUL2 + __FNMUL: xor a or h jp nz, __MUL16_FAST or l ret z + cp 33 jp nc, __MUL16_FAST + ld b, l ld l, h ; HL = 0 + __FNMUL2: add hl, de djnz __FNMUL2 ret + TMP_ARR_PTR: DW 0 ; temporary storage for pointer to tables + ENDP + #line 61 "let_array_substr1.bas" #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) @@ -210,7 +252,9 @@ TMP_ARR_PTR: ; 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) @@ -221,8 +265,10 @@ TMP_ARR_PTR: ; 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 @@ -260,17 +306,24 @@ TMP_ARR_PTR: ; | | ; | (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) @@ -281,8 +334,10 @@ TMP_ARR_PTR: ; 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 @@ -320,16 +375,25 @@ TMP_ARR_PTR: ; | | ; | (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 @@ -337,6 +401,7 @@ TMP_ARR_PTR: __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: @@ -346,36 +411,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -384,44 +459,57 @@ __MEM_INIT2: ; 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) @@ -430,8 +518,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -441,27 +531,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -471,68 +568,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -541,24 +661,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -568,26 +697,30 @@ __CONT2: 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) - 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 62 "let_array_substr1.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) @@ -598,8 +731,10 @@ __FREE_STR: ; 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 @@ -637,24 +772,37 @@ __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 ; 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 @@ -667,6 +815,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -674,12 +824,16 @@ __ERROR: __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. @@ -691,32 +845,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -724,12 +885,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -755,11 +918,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -767,18 +933,24 @@ __MEM_SUBTRACT: 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 @@ -787,24 +959,30 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -812,6 +990,7 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 63 "let_array_substr1.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$) @@ -822,8 +1001,12 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -834,8 +1017,10 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -873,16 +1058,28 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -905,19 +1102,25 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -926,67 +1129,89 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -995,16 +1220,20 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1020,6 +1249,7 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret + __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1027,33 +1257,44 @@ __NOTHING_TO_COPY: 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 64 "let_array_substr1.bas" + ZXBASIC_USER_DATA: _b: DEFB 03h diff --git a/tests/functional/let_array_substr10.asm b/tests/functional/let_array_substr10.asm index 1987ff1e7..1d04cdc45 100644 --- a/tests/functional/let_array_substr10.asm +++ b/tests/functional/let_array_substr10.asm @@ -63,6 +63,7 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -71,7 +72,9 @@ __LABEL1: ; 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) @@ -82,8 +85,10 @@ __LABEL1: ; 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 @@ -121,17 +126,24 @@ __LABEL1: ; | | ; | (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) @@ -142,8 +154,10 @@ __LABEL1: ; 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 @@ -181,16 +195,25 @@ __LABEL1: ; | | ; | (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 @@ -198,6 +221,7 @@ __LABEL1: __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: @@ -207,36 +231,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -245,44 +279,57 @@ __MEM_INIT2: ; 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) @@ -291,8 +338,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -302,27 +351,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -332,68 +388,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -402,24 +481,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -429,26 +517,30 @@ __CONT2: 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) - 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 51 "let_array_substr10.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) @@ -459,8 +551,10 @@ __FREE_STR: ; 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 @@ -498,24 +592,37 @@ __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 ; 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 @@ -528,6 +635,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -535,12 +644,16 @@ __ERROR: __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. @@ -552,32 +665,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -585,12 +705,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -616,11 +738,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -628,18 +753,24 @@ __MEM_SUBTRACT: 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 @@ -648,24 +779,30 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -673,6 +810,7 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr10.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$) @@ -683,8 +821,12 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -695,8 +837,10 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -734,16 +878,28 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -766,19 +922,25 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -787,67 +949,89 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -856,16 +1040,20 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -881,6 +1069,7 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret + __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -888,33 +1077,44 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr10.bas" + ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/let_array_substr11.asm b/tests/functional/let_array_substr11.asm index 8edd48907..754a3c184 100644 --- a/tests/functional/let_array_substr11.asm +++ b/tests/functional/let_array_substr11.asm @@ -63,6 +63,7 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -71,7 +72,9 @@ __LABEL1: ; 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) @@ -82,8 +85,10 @@ __LABEL1: ; 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 @@ -121,17 +126,24 @@ __LABEL1: ; | | ; | (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) @@ -142,8 +154,10 @@ __LABEL1: ; 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 @@ -181,16 +195,25 @@ __LABEL1: ; | | ; | (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 @@ -198,6 +221,7 @@ __LABEL1: __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: @@ -207,36 +231,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -245,44 +279,57 @@ __MEM_INIT2: ; 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) @@ -291,8 +338,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -302,27 +351,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -332,68 +388,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -402,24 +481,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -429,26 +517,30 @@ __CONT2: 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) - 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 51 "let_array_substr11.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) @@ -459,8 +551,10 @@ __FREE_STR: ; 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 @@ -498,24 +592,37 @@ __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 ; 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 @@ -528,6 +635,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -535,12 +644,16 @@ __ERROR: __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. @@ -552,32 +665,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -585,12 +705,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -616,11 +738,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -628,18 +753,24 @@ __MEM_SUBTRACT: 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 @@ -648,24 +779,30 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -673,6 +810,7 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr11.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$) @@ -683,8 +821,12 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -695,8 +837,10 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -734,16 +878,28 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -766,19 +922,25 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -787,67 +949,89 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -856,16 +1040,20 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -881,6 +1069,7 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret + __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -888,33 +1077,44 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr11.bas" + ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/let_array_substr12.asm b/tests/functional/let_array_substr12.asm index 93d8ffc7e..adbbdf5a9 100644 --- a/tests/functional/let_array_substr12.asm +++ b/tests/functional/let_array_substr12.asm @@ -63,6 +63,7 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -71,7 +72,9 @@ __LABEL1: ; 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) @@ -82,8 +85,10 @@ __LABEL1: ; 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 @@ -121,17 +126,24 @@ __LABEL1: ; | | ; | (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) @@ -142,8 +154,10 @@ __LABEL1: ; 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 @@ -181,16 +195,25 @@ __LABEL1: ; | | ; | (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 @@ -198,6 +221,7 @@ __LABEL1: __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: @@ -207,36 +231,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -245,44 +279,57 @@ __MEM_INIT2: ; 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) @@ -291,8 +338,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -302,27 +351,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -332,68 +388,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -402,24 +481,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -429,26 +517,30 @@ __CONT2: 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) - 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 51 "let_array_substr12.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) @@ -459,8 +551,10 @@ __FREE_STR: ; 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 @@ -498,24 +592,37 @@ __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 ; 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 @@ -528,6 +635,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -535,12 +644,16 @@ __ERROR: __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. @@ -552,32 +665,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -585,12 +705,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -616,11 +738,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -628,18 +753,24 @@ __MEM_SUBTRACT: 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 @@ -648,24 +779,30 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -673,6 +810,7 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr12.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$) @@ -683,8 +821,12 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -695,8 +837,10 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -734,16 +878,28 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -766,19 +922,25 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -787,67 +949,89 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -856,16 +1040,20 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -881,6 +1069,7 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret + __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -888,33 +1077,44 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr12.bas" + ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/let_array_substr13.asm b/tests/functional/let_array_substr13.asm index 02ba114d2..531852de7 100644 --- a/tests/functional/let_array_substr13.asm +++ b/tests/functional/let_array_substr13.asm @@ -63,6 +63,7 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -71,7 +72,9 @@ __LABEL1: ; 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) @@ -82,8 +85,10 @@ __LABEL1: ; 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 @@ -121,17 +126,24 @@ __LABEL1: ; | | ; | (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) @@ -142,8 +154,10 @@ __LABEL1: ; 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 @@ -181,16 +195,25 @@ __LABEL1: ; | | ; | (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 @@ -198,6 +221,7 @@ __LABEL1: __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: @@ -207,36 +231,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -245,44 +279,57 @@ __MEM_INIT2: ; 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) @@ -291,8 +338,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -302,27 +351,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -332,68 +388,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -402,24 +481,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -429,26 +517,30 @@ __CONT2: 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) - 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 51 "let_array_substr13.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) @@ -459,8 +551,10 @@ __FREE_STR: ; 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 @@ -498,24 +592,37 @@ __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 ; 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 @@ -528,6 +635,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -535,12 +644,16 @@ __ERROR: __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. @@ -552,32 +665,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -585,12 +705,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -616,11 +738,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -628,18 +753,24 @@ __MEM_SUBTRACT: 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 @@ -648,24 +779,30 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -673,6 +810,7 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr13.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$) @@ -683,8 +821,12 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -695,8 +837,10 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -734,16 +878,28 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -766,19 +922,25 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -787,67 +949,89 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -856,16 +1040,20 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -881,6 +1069,7 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret + __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -888,33 +1077,44 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr13.bas" + ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/let_array_substr2.asm b/tests/functional/let_array_substr2.asm index 01348a61e..cf0b67a3f 100644 --- a/tests/functional/let_array_substr2.asm +++ b/tests/functional/let_array_substr2.asm @@ -63,6 +63,7 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -71,7 +72,9 @@ __LABEL1: ; 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) @@ -82,8 +85,10 @@ __LABEL1: ; 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 @@ -121,17 +126,24 @@ __LABEL1: ; | | ; | (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) @@ -142,8 +154,10 @@ __LABEL1: ; 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 @@ -181,16 +195,25 @@ __LABEL1: ; | | ; | (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 @@ -198,6 +221,7 @@ __LABEL1: __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: @@ -207,36 +231,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -245,44 +279,57 @@ __MEM_INIT2: ; 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) @@ -291,8 +338,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -302,27 +351,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -332,68 +388,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -402,24 +481,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -429,26 +517,30 @@ __CONT2: 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) - 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 51 "let_array_substr2.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) @@ -459,8 +551,10 @@ __FREE_STR: ; 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 @@ -498,24 +592,37 @@ __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 ; 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 @@ -528,6 +635,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -535,12 +644,16 @@ __ERROR: __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. @@ -552,32 +665,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -585,12 +705,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -616,11 +738,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -628,18 +753,24 @@ __MEM_SUBTRACT: 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 @@ -648,24 +779,30 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -673,6 +810,7 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr2.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$) @@ -683,8 +821,12 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -695,8 +837,10 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -734,16 +878,28 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -766,19 +922,25 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -787,67 +949,89 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -856,16 +1040,20 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -881,6 +1069,7 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret + __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -888,33 +1077,44 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr2.bas" + ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/let_array_substr3.asm b/tests/functional/let_array_substr3.asm index 68cb5f692..2f447ee9e 100644 --- a/tests/functional/let_array_substr3.asm +++ b/tests/functional/let_array_substr3.asm @@ -63,6 +63,7 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -71,7 +72,9 @@ __LABEL1: ; 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) @@ -82,8 +85,10 @@ __LABEL1: ; 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 @@ -121,17 +126,24 @@ __LABEL1: ; | | ; | (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) @@ -142,8 +154,10 @@ __LABEL1: ; 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 @@ -181,16 +195,25 @@ __LABEL1: ; | | ; | (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 @@ -198,6 +221,7 @@ __LABEL1: __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: @@ -207,36 +231,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -245,44 +279,57 @@ __MEM_INIT2: ; 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) @@ -291,8 +338,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -302,27 +351,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -332,68 +388,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -402,24 +481,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -429,26 +517,30 @@ __CONT2: 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) - 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 51 "let_array_substr3.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) @@ -459,8 +551,10 @@ __FREE_STR: ; 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 @@ -498,24 +592,37 @@ __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 ; 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 @@ -528,6 +635,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -535,12 +644,16 @@ __ERROR: __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. @@ -552,32 +665,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -585,12 +705,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -616,11 +738,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -628,18 +753,24 @@ __MEM_SUBTRACT: 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 @@ -648,24 +779,30 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -673,6 +810,7 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr3.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$) @@ -683,8 +821,12 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -695,8 +837,10 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -734,16 +878,28 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -766,19 +922,25 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -787,67 +949,89 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -856,16 +1040,20 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -881,6 +1069,7 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret + __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -888,33 +1077,44 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr3.bas" + ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/let_array_substr5.asm b/tests/functional/let_array_substr5.asm index 423a7c1e2..1dc9e807a 100644 --- a/tests/functional/let_array_substr5.asm +++ b/tests/functional/let_array_substr5.asm @@ -73,6 +73,7 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #line 1 "array.asm" + ; vim: ts=4:et:sw=4: ; Copyleft (K) by Jose M. Rodriguez de la Rosa ; (a.k.a. Boriel) @@ -83,48 +84,66 @@ __LABEL1: ; HL = Start of array memory (First two bytes contains N-1 dimensions) ; Dimension values on the stack, (top of the stack, highest dimension) ; E.g. A(2, 4) -> PUSH <4>; PUSH <2> + ; For any array of N dimension A(aN-1, ..., a1, a0) ; and dimensions D[bN-1, ..., b1, b0], the offset is calculated as ; O = [a0 + b0 * (a1 + b1 * (a2 + ... bN-2(aN-1)))] ; What I will do here is to calculate the following sequence: ; ((aN-1 * bN-2) + aN-2) * bN-3 + ... + + #line 1 "mul16.asm" + __MUL16: ; Mutiplies HL with the last value stored into de stack ; Works for both signed and unsigned + PROC + LOCAL __MUL16LOOP LOCAL __MUL16NOADD + ex de, hl pop hl ; Return address ex (sp), hl ; CALLEE caller convention + __MUL16_FAST: ld b, 16 ld a, h ld c, l ld hl, 0 + __MUL16LOOP: add hl, hl ; hl << 1 sla c rla ; a,c << 1 jp nc, __MUL16NOADD add hl, de + __MUL16NOADD: djnz __MUL16LOOP + ret ; Result in hl (16 lower bits) + ENDP + #line 20 "array.asm" -#line 24 "/zxbasic/library-asm/array.asm" + +#line 24 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" + __ARRAY_PTR: ;; computes an array offset from a pointer ld c, (hl) inc hl ld h, (hl) ld l, c + __ARRAY: PROC + LOCAL LOOP LOCAL ARRAY_END LOCAL RET_ADDRESS ; Stores return address LOCAL TMP_ARR_PTR ; Stores pointer temporarily + ld e, (hl) inc hl ld d, (hl) @@ -133,6 +152,7 @@ __ARRAY: ex de, hl ex (sp), hl ; Return address in HL, array address in the stack ld (RET_ADDRESS + 1), hl ; Stores it for later + exx pop hl ; Will use H'L' as the pointer ld c, (hl) ; Loads Number of dimensions from (hl) @@ -140,16 +160,22 @@ __ARRAY: ld b, (hl) inc hl ; Ready exx + ld hl, 0 ; HL = Offset "accumulator" + LOOP: -#line 62 "/zxbasic/library-asm/array.asm" +#line 62 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" pop bc ; Get next index (Ai) from the stack -#line 72 "/zxbasic/library-asm/array.asm" + +#line 72 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" + add hl, bc ; Adds current index + exx ; Checks if B'C' = 0 ld a, b ; Which means we must exit (last element is not multiplied by anything) or c jr z, ARRAY_END ; if B'Ci == 0 we are done + ld e, (hl) ; Loads next dimension into D'E' inc hl ld d, (hl) @@ -158,20 +184,26 @@ LOOP: dec bc ; Decrements loop counter exx pop de ; DE = Max bound Number (i-th dimension) + call __FNMUL jp LOOP + ARRAY_END: ld a, (hl) exx -#line 101 "/zxbasic/library-asm/array.asm" + +#line 101 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" LOCAL ARRAY_SIZE_LOOP + ex de, hl ld hl, 0 ld b, a ARRAY_SIZE_LOOP: add hl, de djnz ARRAY_SIZE_LOOP -#line 111 "/zxbasic/library-asm/array.asm" + +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" + ex de, hl ld hl, (TMP_ARR_PTR) ld a, (hl) @@ -179,29 +211,39 @@ ARRAY_SIZE_LOOP: ld h, (hl) ld l, a add hl, de ; Adds element start + RET_ADDRESS: jp 0 + ;; Performs a faster multiply for little 16bit numbs LOCAL __FNMUL, __FNMUL2 + __FNMUL: xor a or h jp nz, __MUL16_FAST or l ret z + cp 33 jp nc, __MUL16_FAST + ld b, l ld l, h ; HL = 0 + __FNMUL2: add hl, de djnz __FNMUL2 ret + TMP_ARR_PTR: DW 0 ; temporary storage for pointer to tables + ENDP + #line 61 "let_array_substr5.bas" #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) @@ -210,7 +252,9 @@ TMP_ARR_PTR: ; 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) @@ -221,8 +265,10 @@ TMP_ARR_PTR: ; 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 @@ -260,17 +306,24 @@ TMP_ARR_PTR: ; | | ; | (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) @@ -281,8 +334,10 @@ TMP_ARR_PTR: ; 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 @@ -320,16 +375,25 @@ TMP_ARR_PTR: ; | | ; | (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 @@ -337,6 +401,7 @@ TMP_ARR_PTR: __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: @@ -346,36 +411,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -384,44 +459,57 @@ __MEM_INIT2: ; 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) @@ -430,8 +518,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -441,27 +531,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -471,68 +568,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -541,24 +661,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -568,26 +697,30 @@ __CONT2: 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) - 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 62 "let_array_substr5.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) @@ -598,8 +731,10 @@ __FREE_STR: ; 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 @@ -637,24 +772,37 @@ __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 ; 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 @@ -667,6 +815,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -674,12 +824,16 @@ __ERROR: __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. @@ -691,32 +845,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -724,12 +885,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -755,11 +918,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -767,18 +933,24 @@ __MEM_SUBTRACT: 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 @@ -787,24 +959,30 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -812,6 +990,7 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 63 "let_array_substr5.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$) @@ -822,8 +1001,12 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -834,8 +1017,10 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -873,16 +1058,28 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -905,19 +1102,25 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -926,67 +1129,89 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -995,16 +1220,20 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1020,6 +1249,7 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret + __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1027,33 +1257,44 @@ __NOTHING_TO_COPY: 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 64 "let_array_substr5.bas" + ZXBASIC_USER_DATA: _b: DEFB 03h diff --git a/tests/functional/let_array_substr7.asm b/tests/functional/let_array_substr7.asm index 12e21c615..8dd7c25e0 100644 --- a/tests/functional/let_array_substr7.asm +++ b/tests/functional/let_array_substr7.asm @@ -63,6 +63,7 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -71,7 +72,9 @@ __LABEL1: ; 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) @@ -82,8 +85,10 @@ __LABEL1: ; 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 @@ -121,17 +126,24 @@ __LABEL1: ; | | ; | (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) @@ -142,8 +154,10 @@ __LABEL1: ; 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 @@ -181,16 +195,25 @@ __LABEL1: ; | | ; | (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 @@ -198,6 +221,7 @@ __LABEL1: __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: @@ -207,36 +231,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -245,44 +279,57 @@ __MEM_INIT2: ; 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) @@ -291,8 +338,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -302,27 +351,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -332,68 +388,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -402,24 +481,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -429,26 +517,30 @@ __CONT2: 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) - 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 51 "let_array_substr7.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) @@ -459,8 +551,10 @@ __FREE_STR: ; 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 @@ -498,24 +592,37 @@ __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 ; 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 @@ -528,6 +635,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -535,12 +644,16 @@ __ERROR: __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. @@ -552,32 +665,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -585,12 +705,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -616,11 +738,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -628,18 +753,24 @@ __MEM_SUBTRACT: 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 @@ -648,24 +779,30 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -673,6 +810,7 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr7.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$) @@ -683,8 +821,12 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -695,8 +837,10 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -734,16 +878,28 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -766,19 +922,25 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -787,67 +949,89 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -856,16 +1040,20 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -881,6 +1069,7 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret + __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -888,33 +1077,44 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr7.bas" + ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/let_array_substr9.asm b/tests/functional/let_array_substr9.asm index dfb10a3dd..01d44a952 100644 --- a/tests/functional/let_array_substr9.asm +++ b/tests/functional/let_array_substr9.asm @@ -63,6 +63,7 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -71,7 +72,9 @@ __LABEL1: ; 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) @@ -82,8 +85,10 @@ __LABEL1: ; 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 @@ -121,17 +126,24 @@ __LABEL1: ; | | ; | (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) @@ -142,8 +154,10 @@ __LABEL1: ; 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 @@ -181,16 +195,25 @@ __LABEL1: ; | | ; | (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 @@ -198,6 +221,7 @@ __LABEL1: __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: @@ -207,36 +231,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -245,44 +279,57 @@ __MEM_INIT2: ; 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) @@ -291,8 +338,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -302,27 +351,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -332,68 +388,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -402,24 +481,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -429,26 +517,30 @@ __CONT2: 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) - 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 51 "let_array_substr9.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) @@ -459,8 +551,10 @@ __FREE_STR: ; 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 @@ -498,24 +592,37 @@ __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 ; 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 @@ -528,6 +635,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -535,12 +644,16 @@ __ERROR: __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. @@ -552,32 +665,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -585,12 +705,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -616,11 +738,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -628,18 +753,24 @@ __MEM_SUBTRACT: 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 @@ -648,24 +779,30 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -673,6 +810,7 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr9.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$) @@ -683,8 +821,12 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -695,8 +837,10 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -734,16 +878,28 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -766,19 +922,25 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -787,67 +949,89 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -856,16 +1040,20 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -881,6 +1069,7 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret + __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -888,33 +1077,44 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr9.bas" + ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/llc.asm b/tests/functional/llc.asm index 38c77678b..69f6a37d9 100644 --- a/tests/functional/llc.asm +++ b/tests/functional/llc.asm @@ -16,7 +16,7 @@ __START_PROGRAM: ld hl, __LABEL0 call __LOADSTR push hl - xor a + ld a, 1 push af ld hl, 1 push hl @@ -44,6 +44,7 @@ __LABEL0: DEFW 0001h DEFB 2Eh #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) @@ -52,7 +53,9 @@ __LABEL0: ; 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) @@ -63,8 +66,10 @@ __LABEL0: ; 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 @@ -102,17 +107,24 @@ __LABEL0: ; | | ; | (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) @@ -123,8 +135,10 @@ __LABEL0: ; 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 @@ -162,16 +176,25 @@ __LABEL0: ; | | ; | (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 @@ -179,6 +202,7 @@ __LABEL0: __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: @@ -188,36 +212,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -226,44 +260,57 @@ __MEM_INIT2: ; 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) @@ -272,8 +319,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -283,27 +332,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -313,68 +369,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -383,24 +462,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -410,26 +498,30 @@ __CONT2: 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) - 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) @@ -440,8 +532,10 @@ __FREE_STR: ; 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 @@ -479,24 +573,37 @@ __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 ; 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 @@ -509,6 +616,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -516,12 +625,16 @@ __ERROR: __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. @@ -533,32 +646,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -566,12 +686,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -597,11 +719,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -609,18 +734,24 @@ __MEM_SUBTRACT: 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 @@ -629,30 +760,37 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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" + ZXBASIC_USER_DATA: _r: DEFB 00, 00 diff --git a/tests/functional/lvalsubstr_nolet.asm b/tests/functional/lvalsubstr_nolet.asm index 7ae15d25b..2925882fc 100644 --- a/tests/functional/lvalsubstr_nolet.asm +++ b/tests/functional/lvalsubstr_nolet.asm @@ -16,7 +16,7 @@ __START_PROGRAM: ld hl, __LABEL0 call __LOADSTR push hl - xor a + ld a, 1 push af ld hl, 1 push hl @@ -44,6 +44,7 @@ __LABEL0: DEFW 0001h DEFB 2Eh #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) @@ -52,7 +53,9 @@ __LABEL0: ; 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) @@ -63,8 +66,10 @@ __LABEL0: ; 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 @@ -102,17 +107,24 @@ __LABEL0: ; | | ; | (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) @@ -123,8 +135,10 @@ __LABEL0: ; 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 @@ -162,16 +176,25 @@ __LABEL0: ; | | ; | (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 @@ -179,6 +202,7 @@ __LABEL0: __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: @@ -188,36 +212,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -226,44 +260,57 @@ __MEM_INIT2: ; 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) @@ -272,8 +319,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -283,27 +332,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -313,68 +369,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -383,24 +462,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -410,26 +498,30 @@ __CONT2: 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) - 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) @@ -440,8 +532,10 @@ __FREE_STR: ; 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 @@ -479,24 +573,37 @@ __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 ; 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 @@ -509,6 +616,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -516,12 +625,16 @@ __ERROR: __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. @@ -533,32 +646,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -566,12 +686,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -597,11 +719,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -609,18 +734,24 @@ __MEM_SUBTRACT: 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 @@ -629,30 +760,37 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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" + ZXBASIC_USER_DATA: _a: DEFB 00, 00 diff --git a/tests/functional/strbase.asm b/tests/functional/strbase.asm index cf137b2dc..c477a4535 100644 --- a/tests/functional/strbase.asm +++ b/tests/functional/strbase.asm @@ -19,7 +19,7 @@ __START_PROGRAM: ld hl, __LABEL1 call __LOADSTR push hl - xor a + ld a, 1 push af ld hl, 0 push hl @@ -64,6 +64,7 @@ __LABEL1: DEFW 0001h DEFB 6Fh #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) @@ -72,7 +73,9 @@ __LABEL1: ; 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) @@ -83,8 +86,10 @@ __LABEL1: ; 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 @@ -122,17 +127,24 @@ __LABEL1: ; | | ; | (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) @@ -143,8 +155,10 @@ __LABEL1: ; 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 @@ -182,16 +196,25 @@ __LABEL1: ; | | ; | (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 @@ -199,6 +222,7 @@ __LABEL1: __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: @@ -208,36 +232,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -246,44 +280,57 @@ __MEM_INIT2: ; 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) @@ -292,8 +339,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -303,27 +352,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -333,68 +389,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -403,24 +482,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -430,26 +518,30 @@ __CONT2: 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) - 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" + ; vim: ts=4:et:sw=4: ; Copyleft (K) by Jose M. Rodriguez de la Rosa ; (a.k.a. Boriel) @@ -460,8 +552,10 @@ __FREE_STR: ; 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 @@ -499,24 +593,37 @@ __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 ; 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 @@ -529,6 +636,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -536,12 +645,16 @@ __ERROR: __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. @@ -553,32 +666,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -586,12 +706,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -617,11 +739,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -629,18 +754,24 @@ __MEM_SUBTRACT: 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 @@ -649,24 +780,30 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -674,6 +811,7 @@ __LOADSTR: ; __FASTCALL__ entry 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$) @@ -684,8 +822,12 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -696,8 +838,10 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -735,16 +879,28 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -767,19 +923,25 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -788,67 +950,89 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -857,16 +1041,20 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -882,6 +1070,7 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret + __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -889,105 +1078,141 @@ __NOTHING_TO_COPY: 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 54 "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. ; So we needn't call STRASSING to create a duplication ; HL = address of string memory variable ; DE = address of 2n string. It just copies DE into (HL) ; freeing (HL) previously. + + + __PISTORE_STR2: ; Indirect store temporary string at (IX + BC) push ix pop hl add hl, bc + __ISTORE_STR2: ld c, (hl) ; Dereferences HL inc hl ld h, (hl) ld l, c ; HL = *HL (real string variable address) + __STORE_STR2: push hl ld c, (hl) inc hl ld h, (hl) ld l, c ; HL = *HL (real string address) + push de call __MEM_FREE pop de + pop hl ld (hl), e inc hl ld (hl), d dec hl ; HL points to mem address variable. This might be useful in the future. + ret + #line 55 "strbase.bas" #line 1 "strslice.asm" + ; String slicing library ; HL = Str pointer ; DE = String start ; BC = String character end ; A register => 0 => the HL pointer wont' be freed from the HEAP ; e.g. a$(5 TO 10) => HL = a$; DE = 5; BC = 10 + ; This implements a$(X to Y) being X and Y first and ; last characters respectively. If X > Y, NULL is returned + ; Otherwise returns a pointer to a$ FROM X to Y (starting from 0) ; if Y > len(a$), then a$ will be padded with spaces (reallocating ; it in dynamic memory if needed). Returns pointer (HL) to resulting ; string. NULL (0) if no memory for padding. ; + #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 18 "strslice.asm" + + + __STRSLICE: ; Callee entry pop hl ; Return ADDRESS pop bc ; Last char pos pop de ; 1st char pos ex (sp), hl ; CALLEE. -> String start + __STRSLICE_FAST: ; __FASTCALL__ Entry PROC + LOCAL __CONT LOCAL __EMPTY LOCAL __FREE_ON_EXIT + push hl ; Stores original HL pointer to be recovered on exit ex af, af' ; Saves A register for later + push hl call __STRLEN inc bc ; Last character position + 1 (string starts from 0) @@ -998,16 +1223,19 @@ __STRSLICE_FAST: ; __FASTCALL__ Entry ld b, h ld c, l ; Copy to the end of str ccf ; Clears Carry flag for next subtraction + __CONT: ld h, b ld l, c ; HL = Last char position to copy (1 for char 0, 2 for char 1, etc) sbc hl, de ; HL = LEN(a$) - DE => Number of chars to copy jr z, __EMPTY ; 0 Chars to copy => Return HL = 0 (NULL STR) jr c, __EMPTY ; If Carry => Nothing to return (NULL STR) + ld b, h ld c, l ; BC = Number of chars to copy inc bc inc bc ; +2 bytes for string length number + push bc push de call __MEM_ALLOC @@ -1016,12 +1244,15 @@ __CONT: ld a, h or l jr z, __EMPTY ; Return if NULL (no memory) + dec bc dec bc ; Number of chars to copy (Len of slice) + ld (hl), c inc hl ld (hl), b inc hl ; Stores new string length + ex (sp), hl ; Pointer to A$ now in HL; Pointer to new string chars in Stack inc hl inc hl ; Skip string length @@ -1034,18 +1265,26 @@ __CONT: dec de ; Points to String LEN start ex de, hl ; Returns it in HL jr __FREE_ON_EXIT + __EMPTY: ; Return NULL (empty) string pop hl ld hl, 0 ; Return NULL + + __FREE_ON_EXIT: ex af, af' ; Recover original A register ex (sp), hl ; Original HL pointer + or a call nz, __MEM_FREE + pop hl ; Recover result ret + ENDP + #line 56 "strbase.bas" + ZXBASIC_USER_DATA: _a: DEFB 00, 00 diff --git a/tests/functional/strbase2.asm b/tests/functional/strbase2.asm index b2f4d3f83..bcb4e8c83 100644 --- a/tests/functional/strbase2.asm +++ b/tests/functional/strbase2.asm @@ -20,7 +20,7 @@ __START_PROGRAM: ld hl, __LABEL1 call __LOADSTR push hl - xor a + ld a, 1 push af ld hl, 0 push hl @@ -73,6 +73,7 @@ __LABEL1: DEFW 0001h DEFB 6Fh #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) @@ -81,7 +82,9 @@ __LABEL1: ; 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) @@ -92,8 +95,10 @@ __LABEL1: ; 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 @@ -131,17 +136,24 @@ __LABEL1: ; | | ; | (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) @@ -152,8 +164,10 @@ __LABEL1: ; 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 @@ -191,16 +205,25 @@ __LABEL1: ; | | ; | (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 @@ -208,6 +231,7 @@ __LABEL1: __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: @@ -217,36 +241,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -255,44 +289,57 @@ __MEM_INIT2: ; 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) @@ -301,8 +348,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -312,27 +361,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -342,68 +398,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -412,24 +491,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -439,26 +527,30 @@ __CONT2: 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) - 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) @@ -469,8 +561,10 @@ __FREE_STR: ; 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 @@ -508,24 +602,37 @@ __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 ; 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 @@ -538,6 +645,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -545,12 +654,16 @@ __ERROR: __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. @@ -562,32 +675,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -595,12 +715,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -626,11 +748,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -638,18 +763,24 @@ __MEM_SUBTRACT: 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 @@ -658,24 +789,30 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -683,16 +820,22 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 61 "strbase2.bas" #line 1 "print_eol_attr.asm" + ; Calls PRINT_EOL and then COPY_ATTR, so saves ; 3 bytes + #line 1 "print.asm" + ; vim:ts=4:sw=4:et: ; PRINT command routine ; Does not print attribute. Use PRINT_STR or PRINT_NUM for that + #line 1 "sposn.asm" + ; Printing positioning library. PROC LOCAL ECHO_E + __LOAD_S_POSN: ; Loads into DE current ROW, COL print position from S_POSN mem var. ld de, (S_POSN) ld hl, (MAXX) @@ -700,31 +843,46 @@ __LOAD_S_POSN: ; Loads into DE current ROW, COL print position from S_POSN mem sbc hl, de ex de, hl ret + + __SAVE_S_POSN: ; Saves ROW, COL from DE into S_POSN mem var. ld hl, (MAXX) or a sbc hl, de ld (S_POSN), hl ; saves it again ret + + ECHO_E EQU 23682 MAXX EQU ECHO_E ; Max X position + 1 MAXY EQU MAXX + 1 ; Max Y position + 1 + S_POSN EQU 23688 POSX EQU S_POSN ; Current POS X POSY EQU S_POSN + 1 ; Current POS Y + ENDP + #line 6 "print.asm" #line 1 "cls.asm" + ; JUMPS directly to spectrum CLS ; This routine does not clear lower screen + ;CLS EQU 0DAFh + ; Our faster implementation + + + CLS: PROC + LOCAL COORDS LOCAL __CLS_SCR LOCAL ATTR_P LOCAL SCREEN + ld hl, 0 ld (COORDS), hl ld hl, 1821h @@ -737,49 +895,67 @@ __CLS_SCR: inc de ld bc, 6144 ldir + ; Now clear attributes + ld a, (ATTR_P) ld (hl), a ld bc, 767 ldir ret + COORDS EQU 23677 SCREEN EQU 16384 ; Default start of the screen (can be changed) ATTR_P EQU 23693 ;you can poke (SCREEN_SCRADDR) to change CLS, DRAW & PRINTing address + SCREEN_ADDR EQU (__CLS_SCR + 1) ; Address used by print and other screen routines ; to get the start of the screen ENDP + #line 7 "print.asm" #line 1 "in_screen.asm" + + + + __IN_SCREEN: ; Returns NO carry if current coords (D, E) ; are OUT of the screen limits (MAXX, MAXY) + PROC LOCAL __IN_SCREEN_ERR + ld hl, MAXX ld a, e cp (hl) jr nc, __IN_SCREEN_ERR ; Do nothing and return if out of range + ld a, d inc hl cp (hl) ;; jr nc, __IN_SCREEN_ERR ; Do nothing and return if out of range ;; ret ret c ; Return if carry (OK) + __IN_SCREEN_ERR: __OUT_OF_SCREEN_ERR: ; Jumps here if out of screen ld a, ERROR_OutOfScreen jp __STOP ; Saves error code and exits + ENDP #line 8 "print.asm" #line 1 "table_jump.asm" + + JUMP_HL_PLUS_2A: ; Does JP (HL + A*2) Modifies DE. Modifies A add a, a + JUMP_HL_PLUS_A: ; Does JP (HL + A) Modifies DE ld e, a ld d, 0 + JUMP_HL_PLUS_DE: ; Does JP (HL + DE) add hl, de ld e, (hl) @@ -788,12 +964,17 @@ JUMP_HL_PLUS_DE: ; Does JP (HL + DE) ex de, hl CALL_HL: jp (hl) + #line 9 "print.asm" #line 1 "ink.asm" + ; Sets ink color in ATTR_P permanently ; Parameter: Paper color in A register + #line 1 "const.asm" + ; Global constants + P_FLAG EQU 23697 FLAGS2 EQU 23681 ATTR_P EQU 23693 ; permanet ATTRIBUTES @@ -801,20 +982,26 @@ CALL_HL: CHARS EQU 23606 ; Pointer to ROM/RAM Charset UDG EQU 23675 ; Pointer to UDG Charset MEM0 EQU 5C92h ; Temporary memory buffer used by ROM chars + #line 5 "ink.asm" + INK: PROC LOCAL __SET_INK LOCAL __SET_INK2 + ld de, ATTR_P + __SET_INK: cp 8 jr nz, __SET_INK2 + inc de ; Points DE to MASK_T or MASK_P ld a, (de) or 7 ; Set bits 0,1,2 to enable transparency ld (de), a ret + __SET_INK2: ; Another entry. This will set the ink color at location pointer by DE and 7 ; # Gets color mod 8 @@ -828,20 +1015,29 @@ __SET_INK2: and 0F8h ; Reset bits 0,1,2 sign to disable transparency ld (de), a ; Store new attr ret + ; Sets the INK color passed in A register in the ATTR_T variable INK_TMP: ld de, ATTR_T jp __SET_INK + ENDP + #line 10 "print.asm" #line 1 "paper.asm" + ; Sets paper color in ATTR_P permanently ; Parameter: Paper color in A register + + + PAPER: PROC LOCAL __SET_PAPER LOCAL __SET_PAPER2 + ld de, ATTR_P + __SET_PAPER: cp 8 jr nz, __SET_PAPER2 @@ -850,12 +1046,14 @@ __SET_PAPER: or 038h ld (de), a ret + ; Another entry. This will set the paper color at location pointer by DE __SET_PAPER2: and 7 ; # Remove rlca rlca rlca ; a *= 8 + ld b, a ; Saves the color ld a, (de) and 0C7h ; Clears previous value @@ -866,28 +1064,39 @@ __SET_PAPER2: and 0C7h ; Resets bits 3,4,5 ld (de), a ret + + ; Sets the PAPER color passed in A register in the ATTR_T variable PAPER_TMP: ld de, ATTR_T jp __SET_PAPER ENDP + #line 11 "print.asm" #line 1 "flash.asm" + ; Sets flash flag in ATTR_P permanently ; Parameter: Paper color in A register + + + FLASH: ld hl, ATTR_P + PROC LOCAL IS_TR LOCAL IS_ZERO + __SET_FLASH: ; Another entry. This will set the flash flag at location pointer by DE cp 8 jr z, IS_TR + ; # Convert to 0/1 or a jr z, IS_ZERO ld a, 0x80 + IS_ZERO: ld b, a ; Saves the color ld a, (hl) @@ -897,32 +1106,43 @@ IS_ZERO: inc hl res 7, (hl) ;Reset bit 7 to disable transparency ret + IS_TR: ; transparent inc hl ; Points DE to MASK_T or MASK_P set 7, (hl) ;Set bit 7 to enable transparency ret + ; Sets the FLASH flag passed in A register in the ATTR_T variable FLASH_TMP: ld hl, ATTR_T jr __SET_FLASH ENDP + #line 12 "print.asm" #line 1 "bright.asm" + ; Sets bright flag in ATTR_P permanently ; Parameter: Paper color in A register + + + BRIGHT: ld hl, ATTR_P + PROC LOCAL IS_TR LOCAL IS_ZERO + __SET_BRIGHT: ; Another entry. This will set the bright flag at location pointer by DE cp 8 jr z, IS_TR + ; # Convert to 0/1 or a jr z, IS_ZERO ld a, 0x40 + IS_ZERO: ld b, a ; Saves the color ld a, (hl) @@ -932,10 +1152,12 @@ IS_ZERO: inc hl res 6, (hl) ;Reset bit 6 to disable transparency ret + IS_TR: ; transparent inc hl ; Points DE to MASK_T or MASK_P set 6, (hl) ;Set bit 6 to enable transparency ret + ; Sets the BRIGHT flag passed in A register in the ATTR_T variable BRIGHT_TMP: ld hl, ATTR_T @@ -943,50 +1165,74 @@ BRIGHT_TMP: ENDP #line 13 "print.asm" #line 1 "over.asm" + ; Sets OVER flag in P_FLAG permanently ; Parameter: OVER flag in bit 0 of A register #line 1 "copy_attr.asm" -#line 4 "/zxbasic/library-asm/copy_attr.asm" + + + +#line 4 "/home/boriel/src/zxbasic/zxbasic/library-asm/copy_attr.asm" + + + COPY_ATTR: ; Just copies current permanent attribs to temporal attribs ; and sets print mode PROC + LOCAL INVERSE1 LOCAL __REFRESH_TMP + INVERSE1 EQU 02Fh + ld hl, (ATTR_P) ld (ATTR_T), hl + ld hl, FLAGS2 call __REFRESH_TMP + ld hl, P_FLAG call __REFRESH_TMP + + __SET_ATTR_MODE: ; Another entry to set print modes. A contains (P_FLAG) + + LOCAL TABLE LOCAL CONT2 + rra ; Over bit to carry ld a, (FLAGS2) rla ; Over bit in bit 1, Over2 bit in bit 2 and 3 ; Only bit 0 and 1 (OVER flag) + ld c, a ld b, 0 + ld hl, TABLE add hl, bc ld a, (hl) ld (PRINT_MODE), a + ld hl, (P_FLAG) xor a ; NOP -> INVERSE0 bit 2, l jr z, CONT2 ld a, INVERSE1 ; CPL -> INVERSE1 + CONT2: ld (INVERSE_MODE), a ret + TABLE: nop ; NORMAL MODE xor (hl) ; OVER 1 MODE and (hl) ; OVER 2 MODE or (hl) ; OVER 3 MODE -#line 65 "/zxbasic/library-asm/copy_attr.asm" + +#line 65 "/home/boriel/src/zxbasic/zxbasic/library-asm/copy_attr.asm" + __REFRESH_TMP: ld a, (hl) and 10101010b @@ -995,24 +1241,32 @@ __REFRESH_TMP: or c ld (hl), a ret + ENDP + #line 4 "over.asm" + + OVER: PROC + ld c, a ; saves it for later and 2 ld hl, FLAGS2 res 1, (HL) or (hl) ld (hl), a + ld a, c ; Recovers previous value and 1 ; # Convert to 0/1 add a, a; # Shift left 1 bit for permanent + ld hl, P_FLAG res 1, (hl) or (hl) ld (hl), a ret + ; Sets OVER flag in P_FLAG temporarily OVER_TMP: ld c, a ; saves it for later @@ -1022,6 +1276,7 @@ OVER_TMP: res 0, (hl) or (hl) ld (hl), a + ld a, c ; Recovers previous value and 1 ld hl, P_FLAG @@ -1029,13 +1284,20 @@ OVER_TMP: or (hl) ld (hl), a jp __SET_ATTR_MODE + ENDP + #line 14 "print.asm" #line 1 "inverse.asm" + ; Sets INVERSE flag in P_FLAG permanently ; Parameter: INVERSE flag in bit 0 of A register + + + INVERSE: PROC + and 1 ; # Convert to 0/1 add a, a; # Shift left 3 bits for permanent add a, a @@ -1045,6 +1307,7 @@ INVERSE: or (hl) ld (hl), a ret + ; Sets INVERSE flag in P_FLAG temporarily INVERSE_TMP: and 1 @@ -1055,13 +1318,19 @@ INVERSE_TMP: or (hl) ld (hl), a jp __SET_ATTR_MODE + ENDP + #line 15 "print.asm" #line 1 "bold.asm" + ; Sets BOLD flag in P_FLAG permanently ; Parameter: BOLD flag in bit 0 of A register + + BOLD: PROC + and 1 rlca rlca @@ -1071,6 +1340,7 @@ BOLD: or (hl) ld (hl), a ret + ; Sets BOLD flag in P_FLAG temporarily BOLD_TMP: and 1 @@ -1081,13 +1351,19 @@ BOLD_TMP: or (hl) ld (hl), a ret + ENDP + #line 16 "print.asm" #line 1 "italic.asm" + ; Sets ITALIC flag in P_FLAG permanently ; Parameter: ITALIC flag in bit 0 of A register + + ITALIC: PROC + and 1 rrca rrca @@ -1097,6 +1373,7 @@ ITALIC: or (hl) ld (hl), a ret + ; Sets ITALIC flag in P_FLAG temporarily ITALIC_TMP: and 1 @@ -1109,11 +1386,22 @@ ITALIC_TMP: or (hl) ld (hl), a ret + ENDP + #line 17 "print.asm" + #line 1 "attr.asm" + ; Attribute routines ; vim:ts=4:et:sw: + + + + + + + __ATTR_ADDR: ; calc start address in DE (as (32 * d) + e) ; Contributed by Santiago Romero at http://www.speccy.org @@ -1122,38 +1410,54 @@ __ATTR_ADDR: add a, a ; a * 2 ; 4 T-States add a, a ; a * 4 ; 4 T-States ld l, a ; HL = A * 4 ; 4 T-States + add hl, hl ; HL = A * 8 ; 15 T-States add hl, hl ; HL = A * 16 ; 15 T-States add hl, hl ; HL = A * 32 ; 15 T-States + ld d, 18h ; DE = 6144 + E. Note: 6144 is the screen size (before attr zone) add hl, de + ld de, (SCREEN_ADDR) ; Adds the screen address add hl, de + ; Return current screen address in HL ret + + ; Sets the attribute at a given screen coordinate (D, E). ; The attribute is taken from the ATTR_T memory variable ; Used by PRINT routines SET_ATTR: + ; Checks for valid coords call __IN_SCREEN ret nc + __SET_ATTR: ; Internal __FASTCALL__ Entry used by printing routines PROC + call __ATTR_ADDR + __SET_ATTR2: ; Sets attr from ATTR_T to (HL) which points to the scr address ld de, (ATTR_T) ; E = ATTR_T, D = MASK_T + ld a, d and (hl) ld c, a ; C = current screen color, masked + ld a, d cpl ; Negate mask and e ; Mask current attributes or c ; Mix them ld (hl), a ; Store result in screen + ret + ENDP + + ; Sets the attribute at a given screen pixel address in hl ; HL contains the address in RAM for a given pixel (not a coordinate) SET_PIXEL_ADDR_ATTR: @@ -1169,21 +1473,32 @@ SET_PIXEL_ADDR_ATTR: add hl, de ;; Final screen addr jp __SET_ATTR2 #line 19 "print.asm" + ; Putting a comment starting with @INIT
; will make the compiler to add a CALL to
; It is useful for initialization routines. + + __PRINT_INIT: ; To be called before program starts (initializes library) PROC + ld hl, __PRINT_START ld (PRINT_JUMP_STATE), hl + ld hl, 1821h ld (MAXX), hl ; Sets current maxX and maxY + xor a ld (FLAGS2), a + ret + + __PRINTCHAR: ; Print character store in accumulator (A register) ; Modifies H'L', B'C', A'F', D'E', A + LOCAL PO_GR_1 + LOCAL __PRCHAR LOCAL __PRINT_CONT LOCAL __PRINT_CONT2 @@ -1195,16 +1510,22 @@ __PRINTCHAR: ; Print character store in accumulator (A register) LOCAL __NO_SCROLL LOCAL __ROM_SCROLL_SCR LOCAL __TVFLAGS + __ROM_SCROLL_SCR EQU 0DFEh __TVFLAGS EQU 5C3Ch + PRINT_JUMP_STATE EQU __PRINT_JUMP + 1 + __PRINT_JUMP: jp __PRINT_START ; Where to jump. If we print 22 (AT), next two calls jumps to AT1 and AT2 respectively + __PRINT_START: cp ' ' jp c, __PRINT_SPECIAL ; Characters below ' ' are special ones + exx ; Switch to alternative registers ex af, af' ; Saves a value (char to print) for later + ld hl, __TVFLAGS bit 1, (hl) jp z, __NO_SCROLL @@ -1213,39 +1534,53 @@ __PRINT_START: res 1, (hl) __NO_SCROLL: call __LOAD_S_POSN + ; At this point we have the new coord ld hl, (SCREEN_ADDR) + ld a, d ld c, a ; Saves it for later + and 0F8h ; Masks 3 lower bit ; zy ld d, a + ld a, c ; Recovers it and 07h ; MOD 7 ; y1 rrca rrca rrca + or e ld e, a add hl, de ; HL = Screen address + DE ex de, hl ; DE = Screen address + ex af, af' + cp 80h ; Is it an UDG or a ? jp c, __SRCADDR + cp 90h jp nc, __PRINT_UDG + ; Print a 8 bit pattern (80h to 8Fh) + ld b, a call PO_GR_1 ; This ROM routine will generate the bit pattern at MEM0 ld hl, MEM0 jp __PRGRAPH + PO_GR_1 EQU 0B38h + __PRINT_UDG: sub 90h ; Sub ASC code ld bc, (UDG) jp __PRGRAPH0 + __SOURCEADDR EQU (__SRCADDR + 1) ; Address of the pointer to chars source __SRCADDR: ld bc, (CHARS) + __PRGRAPH0: add a, a ; A = a * 2 (since a < 80h) ; Thanks to Metalbrain at http://foro.speccy.org ld l, a @@ -1253,6 +1588,7 @@ __PRGRAPH0: add hl, hl add hl, hl ; HL = a * 8 add hl, bc ; HL = CHARS address + __PRGRAPH: ex de, hl ; HL = Write Address, DE = CHARS address bit 2, (iy + $47) @@ -1262,6 +1598,7 @@ __PRGRAPH: ld b, 8 ; 8 bytes per char __PRCHAR: ld a, (de) ; DE *must* be ALWAYS source, and HL destiny + PRINT_MODE: ; Which operation is used to write on the screen ; Set it with: ; LD A, @@ -1273,12 +1610,16 @@ PRINT_MODE: ; Which operation is used to write on the screen ; OR : B6h --> OR (HL) ; PUTSPRITE ; AND : A6h --> AND (HL) ; PUTMASK nop ; + INVERSE_MODE: ; 00 -> NOP -> INVERSE 0 nop ; 2F -> CPL -> INVERSE 1 + ld (hl), a + inc de inc h ; Next line djnz __PRCHAR + call __LOAD_S_POSN push de call __SET_ATTR @@ -1292,25 +1633,35 @@ INVERSE_MODE: ; 00 -> NOP -> INVERSE 0 call __PRINT_EOL1 exx ; counteracts __PRINT_EOL1 exx jp __PRINT_CONT2 + __PRINT_CONT: call __SAVE_S_POSN + __PRINT_CONT2: exx ret + ; ------------- SPECIAL CHARS (< 32) ----------------- + __PRINT_SPECIAL: ; Jumps here if it is a special char exx ld hl, __PRINT_TABLE jp JUMP_HL_PLUS_2A + + PRINT_EOL: ; Called WHENEVER there is no ";" at end of PRINT sentence exx + __PRINT_0Dh: ; Called WHEN printing CHR$(13) call __LOAD_S_POSN + __PRINT_EOL1: ; Another entry called from PRINT when next line required ld e, 0 + __PRINT_EOL2: ld a, d inc a + __PRINT_AT1_END: ld hl, (MAXY) cp l @@ -1318,12 +1669,15 @@ __PRINT_AT1_END: ld hl, __TVFLAGS set 1, (hl) ld a, d + __PRINT_EOL_END: ld d, a + __PRINT_AT2_END: call __SAVE_S_POSN exx ret + __PRINT_COM: exx push hl @@ -1334,14 +1688,17 @@ __PRINT_COM: pop de pop hl ret + __PRINT_TAB: ld hl, __PRINT_TAB1 jp __PRINT_SET_STATE + __PRINT_TAB1: ld (MEM0), a ld hl, __PRINT_TAB2 ld (PRINT_JUMP_STATE), hl ret + __PRINT_TAB2: ld a, (MEM0) ; Load tab code (ignore the current one) push hl @@ -1354,22 +1711,27 @@ __PRINT_TAB2: pop de pop hl ret + __PRINT_NOP: __PRINT_RESTART: ld hl, __PRINT_START jp __PRINT_SET_STATE + __PRINT_AT: ld hl, __PRINT_AT1 + __PRINT_SET_STATE: ld (PRINT_JUMP_STATE), hl ; Saves next entry call exx ret + __PRINT_AT1: ; Jumps here if waiting for 1st parameter exx ld hl, __PRINT_AT2 ld (PRINT_JUMP_STATE), hl ; Saves next entry call call __LOAD_S_POSN jp __PRINT_AT1_END + __PRINT_AT2: exx ld hl, __PRINT_START @@ -1380,6 +1742,7 @@ __PRINT_AT2: cp (hl) jr c, __PRINT_AT2_END jr __PRINT_EOL1 + __PRINT_DEL: call __LOAD_S_POSN ; Gets current screen position dec e @@ -1396,62 +1759,80 @@ __PRINT_DEL: ld d, h dec d jp __PRINT_AT2_END + __PRINT_INK: ld hl, __PRINT_INK2 jp __PRINT_SET_STATE + __PRINT_INK2: exx call INK_TMP jp __PRINT_RESTART + __PRINT_PAP: ld hl, __PRINT_PAP2 jp __PRINT_SET_STATE + __PRINT_PAP2: exx call PAPER_TMP jp __PRINT_RESTART + __PRINT_FLA: ld hl, __PRINT_FLA2 jp __PRINT_SET_STATE + __PRINT_FLA2: exx call FLASH_TMP jp __PRINT_RESTART + __PRINT_BRI: ld hl, __PRINT_BRI2 jp __PRINT_SET_STATE + __PRINT_BRI2: exx call BRIGHT_TMP jp __PRINT_RESTART + __PRINT_INV: ld hl, __PRINT_INV2 jp __PRINT_SET_STATE + __PRINT_INV2: exx call INVERSE_TMP jp __PRINT_RESTART + __PRINT_OVR: ld hl, __PRINT_OVR2 jp __PRINT_SET_STATE + __PRINT_OVR2: exx call OVER_TMP jp __PRINT_RESTART + __PRINT_BOLD: ld hl, __PRINT_BOLD2 jp __PRINT_SET_STATE + __PRINT_BOLD2: exx call BOLD_TMP jp __PRINT_RESTART + __PRINT_ITA: ld hl, __PRINT_ITA2 jp __PRINT_SET_STATE + __PRINT_ITA2: exx call ITALIC_TMP jp __PRINT_RESTART + + __BOLD: push hl ld hl, MEM0 @@ -1468,6 +1849,8 @@ __BOLD_LOOP: pop hl ld de, MEM0 ret + + __ITALIC: push hl ld hl, MEM0 @@ -1491,14 +1874,17 @@ __ITALIC: pop hl ld de, MEM0 ret + PRINT_COMMA: call __LOAD_S_POSN ld a, e and 16 add a, 16 + PRINT_TAB: PROC LOCAL LOOP, CONTINUE + inc a call __LOAD_S_POSN ; e = current row ld d, a @@ -1519,16 +1905,21 @@ LOOP: djnz LOOP ret ENDP + PRINT_AT: ; CHanges cursor to ROW, COL ; COL in A register ; ROW in stack + pop hl ; Ret address ex (sp), hl ; callee H = ROW ld l, a ex de, hl + call __IN_SCREEN ret nc ; Return if out of screen + jp __SAVE_S_POSN + LOCAL __PRINT_COM LOCAL __BOLD LOCAL __BOLD_LOOP @@ -1547,7 +1938,9 @@ PRINT_AT: ; CHanges cursor to ROW, COL LOCAL __PRINT_SET_STATE LOCAL __PRINT_TABLE LOCAL __PRINT_TAB, __PRINT_TAB1, __PRINT_TAB2 + __PRINT_TABLE: ; Jump table for 0 .. 22 codes + DW __PRINT_NOP ; 0 DW __PRINT_NOP ; 1 DW __PRINT_NOP ; 2 @@ -1572,44 +1965,64 @@ __PRINT_TABLE: ; Jump table for 0 .. 22 codes DW __PRINT_OVR ; 21 DW __PRINT_AT ; 22 AT DW __PRINT_TAB ; 23 TAB + ENDP + + #line 5 "print_eol_attr.asm" + + PRINT_EOL_ATTR: call PRINT_EOL jp COPY_ATTR #line 62 "strbase2.bas" #line 1 "printstr.asm" + + + + + + ; PRINT command routine ; Prints string pointed by HL + PRINT_STR: __PRINTSTR: ; __FASTCALL__ Entry to print_string PROC LOCAL __PRINT_STR_LOOP LOCAL __PRINT_STR_END + ld d, a ; Saves A reg (Flag) for later + ld a, h or l ret z ; Return if the pointer is NULL + push hl + ld c, (hl) inc hl ld b, (hl) inc hl ; BC = LEN(a$); HL = &a$ + __PRINT_STR_LOOP: ld a, b or c jr z, __PRINT_STR_END ; END if BC (counter = 0) + ld a, (hl) call __PRINTCHAR inc hl dec bc jp __PRINT_STR_LOOP + __PRINT_STR_END: pop hl ld a, d ; Recovers A flag or a ; If not 0 this is a temporary string. Free it ret z jp __MEM_FREE ; Frees str from heap and return from there + __PRINT_STR: ; Fastcall Entry ; It ONLY prints strings @@ -1618,9 +2031,12 @@ __PRINT_STR: push hl ; Push str address for later ld d, a ; Saves a FLAG jp __PRINT_STR_LOOP + ENDP + #line 63 "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 ; Returns DE = Address pointer (&a$) @@ -1631,8 +2047,12 @@ __PRINT_STR: ; ; 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) @@ -1643,8 +2063,10 @@ __PRINT_STR: ; 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 @@ -1682,16 +2104,28 @@ __PRINT_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 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_REALLOC ; Reallocates a block of memory in the heap. @@ -1714,19 +2148,25 @@ __PRINT_STR: ; --------------------------------------------------------------------- __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 @@ -1735,67 +2175,89 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -1804,16 +2266,20 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1829,6 +2295,7 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret + __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1836,105 +2303,141 @@ __NOTHING_TO_COPY: 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 64 "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. ; So we needn't call STRASSING to create a duplication ; HL = address of string memory variable ; DE = address of 2n string. It just copies DE into (HL) ; freeing (HL) previously. + + + __PISTORE_STR2: ; Indirect store temporary string at (IX + BC) push ix pop hl add hl, bc + __ISTORE_STR2: ld c, (hl) ; Dereferences HL inc hl ld h, (hl) ld l, c ; HL = *HL (real string variable address) + __STORE_STR2: push hl ld c, (hl) inc hl ld h, (hl) ld l, c ; HL = *HL (real string address) + push de call __MEM_FREE pop de + pop hl ld (hl), e inc hl ld (hl), d dec hl ; HL points to mem address variable. This might be useful in the future. + ret + #line 65 "strbase2.bas" #line 1 "strslice.asm" + ; String slicing library ; HL = Str pointer ; DE = String start ; BC = String character end ; A register => 0 => the HL pointer wont' be freed from the HEAP ; e.g. a$(5 TO 10) => HL = a$; DE = 5; BC = 10 + ; This implements a$(X to Y) being X and Y first and ; last characters respectively. If X > Y, NULL is returned + ; Otherwise returns a pointer to a$ FROM X to Y (starting from 0) ; if Y > len(a$), then a$ will be padded with spaces (reallocating ; it in dynamic memory if needed). Returns pointer (HL) to resulting ; string. NULL (0) if no memory for padding. ; + #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 18 "strslice.asm" + + + __STRSLICE: ; Callee entry pop hl ; Return ADDRESS pop bc ; Last char pos pop de ; 1st char pos ex (sp), hl ; CALLEE. -> String start + __STRSLICE_FAST: ; __FASTCALL__ Entry PROC + LOCAL __CONT LOCAL __EMPTY LOCAL __FREE_ON_EXIT + push hl ; Stores original HL pointer to be recovered on exit ex af, af' ; Saves A register for later + push hl call __STRLEN inc bc ; Last character position + 1 (string starts from 0) @@ -1945,16 +2448,19 @@ __STRSLICE_FAST: ; __FASTCALL__ Entry ld b, h ld c, l ; Copy to the end of str ccf ; Clears Carry flag for next subtraction + __CONT: ld h, b ld l, c ; HL = Last char position to copy (1 for char 0, 2 for char 1, etc) sbc hl, de ; HL = LEN(a$) - DE => Number of chars to copy jr z, __EMPTY ; 0 Chars to copy => Return HL = 0 (NULL STR) jr c, __EMPTY ; If Carry => Nothing to return (NULL STR) + ld b, h ld c, l ; BC = Number of chars to copy inc bc inc bc ; +2 bytes for string length number + push bc push de call __MEM_ALLOC @@ -1963,12 +2469,15 @@ __CONT: ld a, h or l jr z, __EMPTY ; Return if NULL (no memory) + dec bc dec bc ; Number of chars to copy (Len of slice) + ld (hl), c inc hl ld (hl), b inc hl ; Stores new string length + ex (sp), hl ; Pointer to A$ now in HL; Pointer to new string chars in Stack inc hl inc hl ; Skip string length @@ -1981,18 +2490,26 @@ __CONT: dec de ; Points to String LEN start ex de, hl ; Returns it in HL jr __FREE_ON_EXIT + __EMPTY: ; Return NULL (empty) string pop hl ld hl, 0 ; Return NULL + + __FREE_ON_EXIT: ex af, af' ; Recover original A register ex (sp), hl ; Original HL pointer + or a call nz, __MEM_FREE + pop hl ; Recover result ret + ENDP + #line 66 "strbase2.bas" + ZXBASIC_USER_DATA: _a: DEFB 00, 00 diff --git a/tests/functional/substrlval.asm b/tests/functional/substrlval.asm index 05976c04a..7b14c7db5 100644 --- a/tests/functional/substrlval.asm +++ b/tests/functional/substrlval.asm @@ -21,7 +21,7 @@ __LABEL__30: ld hl, __LABEL1 call __LOADSTR push hl - xor a + ld a, 1 push af ld hl, 3 push hl @@ -58,6 +58,7 @@ __LABEL1: DEFW 0001h DEFB 7Ah #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) @@ -66,7 +67,9 @@ __LABEL1: ; 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) @@ -77,8 +80,10 @@ __LABEL1: ; 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 @@ -116,17 +121,24 @@ __LABEL1: ; | | ; | (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) @@ -137,8 +149,10 @@ __LABEL1: ; 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 @@ -176,16 +190,25 @@ __LABEL1: ; | | ; | (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 @@ -193,6 +216,7 @@ __LABEL1: __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: @@ -202,36 +226,46 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -240,44 +274,57 @@ __MEM_INIT2: ; 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) @@ -286,8 +333,10 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -297,27 +346,34 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -327,68 +383,91 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed inc hl ld (hl), d ; Next saved ret + ENDP + #line 11 "letsubstr.asm" + __LETSUBSTR: PROC + LOCAL __CONT0 LOCAL __CONT1 LOCAL __CONT2 LOCAL __FREE_STR - LOCAL __FREE_STR0 + 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_STR0 ; Return if null + 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 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 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 subtring to copy + +__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 @@ -397,24 +476,33 @@ __CONT0: ; At this point DE = Length of subtring to copy 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 @@ -424,26 +512,30 @@ __CONT2: 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) - 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" + ; vim: ts=4:et:sw=4: ; Copyleft (K) by Jose M. Rodriguez de la Rosa ; (a.k.a. Boriel) @@ -454,8 +546,10 @@ __FREE_STR: ; 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 @@ -493,24 +587,37 @@ __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 ; 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 @@ -523,6 +630,8 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 + + ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -530,12 +639,16 @@ __ERROR: __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. @@ -547,32 +660,39 @@ __STOP: ; 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" +#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/zxbasic/library-asm/alloc.asm" +#line 113 "/home/boriel/src/zxbasic/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 @@ -580,12 +700,14 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -611,11 +733,14 @@ __MEM_DONE: ; A free block has been found. 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 @@ -623,18 +748,24 @@ __MEM_SUBTRACT: 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 @@ -643,24 +774,30 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -668,6 +805,7 @@ __LOADSTR: ; __FASTCALL__ entry 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$) @@ -678,8 +816,12 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -690,8 +832,10 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -729,16 +873,28 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -761,19 +917,25 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -782,67 +944,89 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -851,16 +1035,20 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -876,6 +1064,7 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret + __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -883,33 +1072,44 @@ __NOTHING_TO_COPY: 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 48 "substrlval.bas" + ZXBASIC_USER_DATA: _a: DEFB 00, 00 From f2309dae52ebf712546a8110ed7f367076608f5c Mon Sep 17 00:00:00 2001 From: Jose Rodriguez Date: Sat, 16 Nov 2019 17:03:00 +0100 Subject: [PATCH 2/3] Optimize substring assignation --- api/check.py | 6 + arch/zx48k/translator.py | 10 +- tests/functional/id_substr_eq_expr.asm | 371 +-------- tests/functional/let_array_substr.asm | 258 +------ tests/functional/let_array_substr1.asm | 258 +------ tests/functional/let_array_substr10.asm | 207 +---- tests/functional/let_array_substr11.asm | 207 +---- tests/functional/let_array_substr12.asm | 207 +---- tests/functional/let_array_substr13.asm | 207 +---- tests/functional/let_array_substr2.asm | 207 +---- tests/functional/let_array_substr3.asm | 207 +---- tests/functional/let_array_substr5.asm | 258 +------ tests/functional/let_array_substr7.asm | 207 +---- tests/functional/let_array_substr9.asm | 207 +---- tests/functional/llc.asm | 371 +-------- tests/functional/lvalsubstr_nolet.asm | 371 +-------- tests/functional/strbase.asm | 449 +++-------- tests/functional/strbase2.asm | 953 +++++------------------- tests/functional/substrlval.asm | 406 +++------- 19 files changed, 423 insertions(+), 4944 deletions(-) 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 6a45e243f..b9fa67242 100644 --- a/arch/zx48k/translator.py +++ b/arch/zx48k/translator.py @@ -314,8 +314,13 @@ def visit_LETARRAY(self, node): def visit_LETSUBSTR(self, node): yield node.children[3] - self.ic_param(TYPE.string, node.children[3].t) - self.ic_param(TYPE.ubyte, 1) + + 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] self.ic_param(gl.PTR_TYPE, node.children[1].t) @@ -333,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/tests/functional/id_substr_eq_expr.asm b/tests/functional/id_substr_eq_expr.asm index e4a397e81..09dc41aa3 100644 --- a/tests/functional/id_substr_eq_expr.asm +++ b/tests/functional/id_substr_eq_expr.asm @@ -14,9 +14,8 @@ __START_PROGRAM: ei call __MEM_INIT ld hl, __LABEL0 - call __LOADSTR push hl - ld a, 1 + xor a push af ld hl, 1 push hl @@ -48,7 +47,6 @@ __LABEL0: DEFB 6Ch DEFB 6Fh #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) @@ -57,9 +55,7 @@ __LABEL0: ; 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) @@ -70,10 +66,8 @@ __LABEL0: ; 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 @@ -111,24 +105,17 @@ __LABEL0: ; | | ; | (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) @@ -139,10 +126,8 @@ __LABEL0: ; 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 @@ -180,25 +165,16 @@ __LABEL0: ; | | ; | (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 @@ -206,7 +182,6 @@ __LABEL0: __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: @@ -216,46 +191,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -264,57 +229,44 @@ __MEM_INIT2: ; 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) @@ -323,10 +275,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -336,34 +286,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -373,91 +316,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -466,33 +386,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -502,299 +413,21 @@ __CONT2: 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 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" - ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/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 ce15f4369..4a984124a 100644 --- a/tests/functional/let_array_substr.asm +++ b/tests/functional/let_array_substr.asm @@ -73,7 +73,6 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #line 1 "array.asm" - ; vim: ts=4:et:sw=4: ; Copyleft (K) by Jose M. Rodriguez de la Rosa ; (a.k.a. Boriel) @@ -84,66 +83,48 @@ __LABEL1: ; HL = Start of array memory (First two bytes contains N-1 dimensions) ; Dimension values on the stack, (top of the stack, highest dimension) ; E.g. A(2, 4) -> PUSH <4>; PUSH <2> - ; For any array of N dimension A(aN-1, ..., a1, a0) ; and dimensions D[bN-1, ..., b1, b0], the offset is calculated as ; O = [a0 + b0 * (a1 + b1 * (a2 + ... bN-2(aN-1)))] ; What I will do here is to calculate the following sequence: ; ((aN-1 * bN-2) + aN-2) * bN-3 + ... - - #line 1 "mul16.asm" - __MUL16: ; Mutiplies HL with the last value stored into de stack ; Works for both signed and unsigned - PROC - LOCAL __MUL16LOOP LOCAL __MUL16NOADD - ex de, hl pop hl ; Return address ex (sp), hl ; CALLEE caller convention - __MUL16_FAST: ld b, 16 ld a, h ld c, l ld hl, 0 - __MUL16LOOP: add hl, hl ; hl << 1 sla c rla ; a,c << 1 jp nc, __MUL16NOADD add hl, de - __MUL16NOADD: djnz __MUL16LOOP - ret ; Result in hl (16 lower bits) - ENDP - #line 20 "array.asm" - -#line 24 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" - +#line 24 "/zxbasic/library-asm/array.asm" __ARRAY_PTR: ;; computes an array offset from a pointer ld c, (hl) inc hl ld h, (hl) ld l, c - __ARRAY: PROC - LOCAL LOOP LOCAL ARRAY_END LOCAL RET_ADDRESS ; Stores return address LOCAL TMP_ARR_PTR ; Stores pointer temporarily - ld e, (hl) inc hl ld d, (hl) @@ -152,7 +133,6 @@ __ARRAY: ex de, hl ex (sp), hl ; Return address in HL, array address in the stack ld (RET_ADDRESS + 1), hl ; Stores it for later - exx pop hl ; Will use H'L' as the pointer ld c, (hl) ; Loads Number of dimensions from (hl) @@ -160,22 +140,16 @@ __ARRAY: ld b, (hl) inc hl ; Ready exx - ld hl, 0 ; HL = Offset "accumulator" - LOOP: -#line 62 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" +#line 62 "/zxbasic/library-asm/array.asm" pop bc ; Get next index (Ai) from the stack - -#line 72 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" - +#line 72 "/zxbasic/library-asm/array.asm" add hl, bc ; Adds current index - exx ; Checks if B'C' = 0 ld a, b ; Which means we must exit (last element is not multiplied by anything) or c jr z, ARRAY_END ; if B'Ci == 0 we are done - ld e, (hl) ; Loads next dimension into D'E' inc hl ld d, (hl) @@ -184,26 +158,20 @@ LOOP: dec bc ; Decrements loop counter exx pop de ; DE = Max bound Number (i-th dimension) - call __FNMUL jp LOOP - ARRAY_END: ld a, (hl) exx - -#line 101 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" +#line 101 "/zxbasic/library-asm/array.asm" LOCAL ARRAY_SIZE_LOOP - ex de, hl ld hl, 0 ld b, a ARRAY_SIZE_LOOP: add hl, de djnz ARRAY_SIZE_LOOP - -#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" - +#line 111 "/zxbasic/library-asm/array.asm" ex de, hl ld hl, (TMP_ARR_PTR) ld a, (hl) @@ -211,39 +179,29 @@ ARRAY_SIZE_LOOP: ld h, (hl) ld l, a add hl, de ; Adds element start - RET_ADDRESS: jp 0 - ;; Performs a faster multiply for little 16bit numbs LOCAL __FNMUL, __FNMUL2 - __FNMUL: xor a or h jp nz, __MUL16_FAST or l ret z - cp 33 jp nc, __MUL16_FAST - ld b, l ld l, h ; HL = 0 - __FNMUL2: add hl, de djnz __FNMUL2 ret - TMP_ARR_PTR: DW 0 ; temporary storage for pointer to tables - ENDP - #line 61 "let_array_substr.bas" #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) @@ -252,9 +210,7 @@ TMP_ARR_PTR: ; 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) @@ -265,10 +221,8 @@ TMP_ARR_PTR: ; 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 @@ -306,24 +260,17 @@ TMP_ARR_PTR: ; | | ; | (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) @@ -334,10 +281,8 @@ TMP_ARR_PTR: ; 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 @@ -375,25 +320,16 @@ TMP_ARR_PTR: ; | | ; | (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 @@ -401,7 +337,6 @@ TMP_ARR_PTR: __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: @@ -411,46 +346,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -459,57 +384,44 @@ __MEM_INIT2: ; 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) @@ -518,10 +430,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -531,34 +441,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -568,91 +471,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -661,33 +541,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -697,30 +568,23 @@ __CONT2: 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 62 "let_array_substr.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) @@ -731,10 +595,8 @@ __FREE_STR: ; 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 @@ -772,37 +634,24 @@ __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 ; 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 @@ -815,8 +664,6 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 - - ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -824,16 +671,12 @@ __ERROR: __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. @@ -845,39 +688,32 @@ __STOP: ; 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#line 111 "/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#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 @@ -885,14 +721,12 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -918,14 +752,11 @@ __MEM_DONE: ; A free block has been found. 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 @@ -933,24 +764,18 @@ __MEM_SUBTRACT: 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 @@ -959,30 +784,24 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -990,7 +809,6 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 63 "let_array_substr.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$) @@ -1001,12 +819,8 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -1017,10 +831,8 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -1058,28 +870,16 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -1102,25 +902,19 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -1129,89 +923,67 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -1220,20 +992,16 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1249,7 +1017,6 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret - __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1257,44 +1024,33 @@ __NOTHING_TO_COPY: 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 64 "let_array_substr.bas" - ZXBASIC_USER_DATA: _b: DEFB 03h diff --git a/tests/functional/let_array_substr1.asm b/tests/functional/let_array_substr1.asm index 59b47decc..0054a0664 100644 --- a/tests/functional/let_array_substr1.asm +++ b/tests/functional/let_array_substr1.asm @@ -73,7 +73,6 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #line 1 "array.asm" - ; vim: ts=4:et:sw=4: ; Copyleft (K) by Jose M. Rodriguez de la Rosa ; (a.k.a. Boriel) @@ -84,66 +83,48 @@ __LABEL1: ; HL = Start of array memory (First two bytes contains N-1 dimensions) ; Dimension values on the stack, (top of the stack, highest dimension) ; E.g. A(2, 4) -> PUSH <4>; PUSH <2> - ; For any array of N dimension A(aN-1, ..., a1, a0) ; and dimensions D[bN-1, ..., b1, b0], the offset is calculated as ; O = [a0 + b0 * (a1 + b1 * (a2 + ... bN-2(aN-1)))] ; What I will do here is to calculate the following sequence: ; ((aN-1 * bN-2) + aN-2) * bN-3 + ... - - #line 1 "mul16.asm" - __MUL16: ; Mutiplies HL with the last value stored into de stack ; Works for both signed and unsigned - PROC - LOCAL __MUL16LOOP LOCAL __MUL16NOADD - ex de, hl pop hl ; Return address ex (sp), hl ; CALLEE caller convention - __MUL16_FAST: ld b, 16 ld a, h ld c, l ld hl, 0 - __MUL16LOOP: add hl, hl ; hl << 1 sla c rla ; a,c << 1 jp nc, __MUL16NOADD add hl, de - __MUL16NOADD: djnz __MUL16LOOP - ret ; Result in hl (16 lower bits) - ENDP - #line 20 "array.asm" - -#line 24 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" - +#line 24 "/zxbasic/library-asm/array.asm" __ARRAY_PTR: ;; computes an array offset from a pointer ld c, (hl) inc hl ld h, (hl) ld l, c - __ARRAY: PROC - LOCAL LOOP LOCAL ARRAY_END LOCAL RET_ADDRESS ; Stores return address LOCAL TMP_ARR_PTR ; Stores pointer temporarily - ld e, (hl) inc hl ld d, (hl) @@ -152,7 +133,6 @@ __ARRAY: ex de, hl ex (sp), hl ; Return address in HL, array address in the stack ld (RET_ADDRESS + 1), hl ; Stores it for later - exx pop hl ; Will use H'L' as the pointer ld c, (hl) ; Loads Number of dimensions from (hl) @@ -160,22 +140,16 @@ __ARRAY: ld b, (hl) inc hl ; Ready exx - ld hl, 0 ; HL = Offset "accumulator" - LOOP: -#line 62 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" +#line 62 "/zxbasic/library-asm/array.asm" pop bc ; Get next index (Ai) from the stack - -#line 72 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" - +#line 72 "/zxbasic/library-asm/array.asm" add hl, bc ; Adds current index - exx ; Checks if B'C' = 0 ld a, b ; Which means we must exit (last element is not multiplied by anything) or c jr z, ARRAY_END ; if B'Ci == 0 we are done - ld e, (hl) ; Loads next dimension into D'E' inc hl ld d, (hl) @@ -184,26 +158,20 @@ LOOP: dec bc ; Decrements loop counter exx pop de ; DE = Max bound Number (i-th dimension) - call __FNMUL jp LOOP - ARRAY_END: ld a, (hl) exx - -#line 101 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" +#line 101 "/zxbasic/library-asm/array.asm" LOCAL ARRAY_SIZE_LOOP - ex de, hl ld hl, 0 ld b, a ARRAY_SIZE_LOOP: add hl, de djnz ARRAY_SIZE_LOOP - -#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" - +#line 111 "/zxbasic/library-asm/array.asm" ex de, hl ld hl, (TMP_ARR_PTR) ld a, (hl) @@ -211,39 +179,29 @@ ARRAY_SIZE_LOOP: ld h, (hl) ld l, a add hl, de ; Adds element start - RET_ADDRESS: jp 0 - ;; Performs a faster multiply for little 16bit numbs LOCAL __FNMUL, __FNMUL2 - __FNMUL: xor a or h jp nz, __MUL16_FAST or l ret z - cp 33 jp nc, __MUL16_FAST - ld b, l ld l, h ; HL = 0 - __FNMUL2: add hl, de djnz __FNMUL2 ret - TMP_ARR_PTR: DW 0 ; temporary storage for pointer to tables - ENDP - #line 61 "let_array_substr1.bas" #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) @@ -252,9 +210,7 @@ TMP_ARR_PTR: ; 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) @@ -265,10 +221,8 @@ TMP_ARR_PTR: ; 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 @@ -306,24 +260,17 @@ TMP_ARR_PTR: ; | | ; | (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) @@ -334,10 +281,8 @@ TMP_ARR_PTR: ; 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 @@ -375,25 +320,16 @@ TMP_ARR_PTR: ; | | ; | (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 @@ -401,7 +337,6 @@ TMP_ARR_PTR: __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: @@ -411,46 +346,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -459,57 +384,44 @@ __MEM_INIT2: ; 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) @@ -518,10 +430,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -531,34 +441,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -568,91 +471,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -661,33 +541,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -697,30 +568,23 @@ __CONT2: 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 62 "let_array_substr1.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) @@ -731,10 +595,8 @@ __FREE_STR: ; 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 @@ -772,37 +634,24 @@ __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 ; 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 @@ -815,8 +664,6 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 - - ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -824,16 +671,12 @@ __ERROR: __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. @@ -845,39 +688,32 @@ __STOP: ; 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#line 111 "/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#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 @@ -885,14 +721,12 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -918,14 +752,11 @@ __MEM_DONE: ; A free block has been found. 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 @@ -933,24 +764,18 @@ __MEM_SUBTRACT: 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 @@ -959,30 +784,24 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -990,7 +809,6 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 63 "let_array_substr1.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$) @@ -1001,12 +819,8 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -1017,10 +831,8 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -1058,28 +870,16 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -1102,25 +902,19 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -1129,89 +923,67 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -1220,20 +992,16 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1249,7 +1017,6 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret - __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1257,44 +1024,33 @@ __NOTHING_TO_COPY: 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 64 "let_array_substr1.bas" - ZXBASIC_USER_DATA: _b: DEFB 03h diff --git a/tests/functional/let_array_substr10.asm b/tests/functional/let_array_substr10.asm index 1d04cdc45..e7c7b3a37 100644 --- a/tests/functional/let_array_substr10.asm +++ b/tests/functional/let_array_substr10.asm @@ -63,7 +63,6 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -72,9 +71,7 @@ __LABEL1: ; 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) @@ -85,10 +82,8 @@ __LABEL1: ; 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 @@ -126,24 +121,17 @@ __LABEL1: ; | | ; | (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) @@ -154,10 +142,8 @@ __LABEL1: ; 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 @@ -195,25 +181,16 @@ __LABEL1: ; | | ; | (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 @@ -221,7 +198,6 @@ __LABEL1: __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: @@ -231,46 +207,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -279,57 +245,44 @@ __MEM_INIT2: ; 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) @@ -338,10 +291,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -351,34 +302,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -388,91 +332,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -481,33 +402,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -517,30 +429,23 @@ __CONT2: 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 51 "let_array_substr10.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) @@ -551,10 +456,8 @@ __FREE_STR: ; 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 @@ -592,37 +495,24 @@ __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 ; 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 @@ -635,8 +525,6 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 - - ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -644,16 +532,12 @@ __ERROR: __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. @@ -665,39 +549,32 @@ __STOP: ; 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#line 111 "/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#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 @@ -705,14 +582,12 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -738,14 +613,11 @@ __MEM_DONE: ; A free block has been found. 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 @@ -753,24 +625,18 @@ __MEM_SUBTRACT: 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 @@ -779,30 +645,24 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -810,7 +670,6 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr10.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$) @@ -821,12 +680,8 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -837,10 +692,8 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -878,28 +731,16 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -922,25 +763,19 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -949,89 +784,67 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -1040,20 +853,16 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1069,7 +878,6 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret - __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1077,44 +885,33 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr10.bas" - ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/let_array_substr11.asm b/tests/functional/let_array_substr11.asm index 754a3c184..7608d9357 100644 --- a/tests/functional/let_array_substr11.asm +++ b/tests/functional/let_array_substr11.asm @@ -63,7 +63,6 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -72,9 +71,7 @@ __LABEL1: ; 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) @@ -85,10 +82,8 @@ __LABEL1: ; 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 @@ -126,24 +121,17 @@ __LABEL1: ; | | ; | (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) @@ -154,10 +142,8 @@ __LABEL1: ; 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 @@ -195,25 +181,16 @@ __LABEL1: ; | | ; | (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 @@ -221,7 +198,6 @@ __LABEL1: __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: @@ -231,46 +207,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -279,57 +245,44 @@ __MEM_INIT2: ; 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) @@ -338,10 +291,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -351,34 +302,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -388,91 +332,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -481,33 +402,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -517,30 +429,23 @@ __CONT2: 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 51 "let_array_substr11.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) @@ -551,10 +456,8 @@ __FREE_STR: ; 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 @@ -592,37 +495,24 @@ __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 ; 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 @@ -635,8 +525,6 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 - - ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -644,16 +532,12 @@ __ERROR: __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. @@ -665,39 +549,32 @@ __STOP: ; 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#line 111 "/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#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 @@ -705,14 +582,12 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -738,14 +613,11 @@ __MEM_DONE: ; A free block has been found. 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 @@ -753,24 +625,18 @@ __MEM_SUBTRACT: 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 @@ -779,30 +645,24 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -810,7 +670,6 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr11.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$) @@ -821,12 +680,8 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -837,10 +692,8 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -878,28 +731,16 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -922,25 +763,19 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -949,89 +784,67 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -1040,20 +853,16 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1069,7 +878,6 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret - __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1077,44 +885,33 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr11.bas" - ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/let_array_substr12.asm b/tests/functional/let_array_substr12.asm index adbbdf5a9..b960c6bd3 100644 --- a/tests/functional/let_array_substr12.asm +++ b/tests/functional/let_array_substr12.asm @@ -63,7 +63,6 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -72,9 +71,7 @@ __LABEL1: ; 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) @@ -85,10 +82,8 @@ __LABEL1: ; 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 @@ -126,24 +121,17 @@ __LABEL1: ; | | ; | (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) @@ -154,10 +142,8 @@ __LABEL1: ; 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 @@ -195,25 +181,16 @@ __LABEL1: ; | | ; | (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 @@ -221,7 +198,6 @@ __LABEL1: __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: @@ -231,46 +207,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -279,57 +245,44 @@ __MEM_INIT2: ; 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) @@ -338,10 +291,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -351,34 +302,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -388,91 +332,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -481,33 +402,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -517,30 +429,23 @@ __CONT2: 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 51 "let_array_substr12.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) @@ -551,10 +456,8 @@ __FREE_STR: ; 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 @@ -592,37 +495,24 @@ __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 ; 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 @@ -635,8 +525,6 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 - - ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -644,16 +532,12 @@ __ERROR: __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. @@ -665,39 +549,32 @@ __STOP: ; 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#line 111 "/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#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 @@ -705,14 +582,12 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -738,14 +613,11 @@ __MEM_DONE: ; A free block has been found. 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 @@ -753,24 +625,18 @@ __MEM_SUBTRACT: 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 @@ -779,30 +645,24 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -810,7 +670,6 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr12.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$) @@ -821,12 +680,8 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -837,10 +692,8 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -878,28 +731,16 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -922,25 +763,19 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -949,89 +784,67 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -1040,20 +853,16 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1069,7 +878,6 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret - __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1077,44 +885,33 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr12.bas" - ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/let_array_substr13.asm b/tests/functional/let_array_substr13.asm index 531852de7..77bc19dcb 100644 --- a/tests/functional/let_array_substr13.asm +++ b/tests/functional/let_array_substr13.asm @@ -63,7 +63,6 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -72,9 +71,7 @@ __LABEL1: ; 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) @@ -85,10 +82,8 @@ __LABEL1: ; 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 @@ -126,24 +121,17 @@ __LABEL1: ; | | ; | (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) @@ -154,10 +142,8 @@ __LABEL1: ; 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 @@ -195,25 +181,16 @@ __LABEL1: ; | | ; | (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 @@ -221,7 +198,6 @@ __LABEL1: __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: @@ -231,46 +207,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -279,57 +245,44 @@ __MEM_INIT2: ; 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) @@ -338,10 +291,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -351,34 +302,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -388,91 +332,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -481,33 +402,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -517,30 +429,23 @@ __CONT2: 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 51 "let_array_substr13.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) @@ -551,10 +456,8 @@ __FREE_STR: ; 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 @@ -592,37 +495,24 @@ __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 ; 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 @@ -635,8 +525,6 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 - - ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -644,16 +532,12 @@ __ERROR: __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. @@ -665,39 +549,32 @@ __STOP: ; 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#line 111 "/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#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 @@ -705,14 +582,12 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -738,14 +613,11 @@ __MEM_DONE: ; A free block has been found. 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 @@ -753,24 +625,18 @@ __MEM_SUBTRACT: 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 @@ -779,30 +645,24 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -810,7 +670,6 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr13.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$) @@ -821,12 +680,8 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -837,10 +692,8 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -878,28 +731,16 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -922,25 +763,19 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -949,89 +784,67 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -1040,20 +853,16 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1069,7 +878,6 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret - __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1077,44 +885,33 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr13.bas" - ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/let_array_substr2.asm b/tests/functional/let_array_substr2.asm index cf0b67a3f..31b143183 100644 --- a/tests/functional/let_array_substr2.asm +++ b/tests/functional/let_array_substr2.asm @@ -63,7 +63,6 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -72,9 +71,7 @@ __LABEL1: ; 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) @@ -85,10 +82,8 @@ __LABEL1: ; 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 @@ -126,24 +121,17 @@ __LABEL1: ; | | ; | (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) @@ -154,10 +142,8 @@ __LABEL1: ; 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 @@ -195,25 +181,16 @@ __LABEL1: ; | | ; | (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 @@ -221,7 +198,6 @@ __LABEL1: __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: @@ -231,46 +207,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -279,57 +245,44 @@ __MEM_INIT2: ; 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) @@ -338,10 +291,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -351,34 +302,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -388,91 +332,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -481,33 +402,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -517,30 +429,23 @@ __CONT2: 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 51 "let_array_substr2.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) @@ -551,10 +456,8 @@ __FREE_STR: ; 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 @@ -592,37 +495,24 @@ __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 ; 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 @@ -635,8 +525,6 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 - - ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -644,16 +532,12 @@ __ERROR: __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. @@ -665,39 +549,32 @@ __STOP: ; 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#line 111 "/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#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 @@ -705,14 +582,12 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -738,14 +613,11 @@ __MEM_DONE: ; A free block has been found. 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 @@ -753,24 +625,18 @@ __MEM_SUBTRACT: 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 @@ -779,30 +645,24 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -810,7 +670,6 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr2.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$) @@ -821,12 +680,8 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -837,10 +692,8 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -878,28 +731,16 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -922,25 +763,19 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -949,89 +784,67 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -1040,20 +853,16 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1069,7 +878,6 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret - __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1077,44 +885,33 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr2.bas" - ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/let_array_substr3.asm b/tests/functional/let_array_substr3.asm index 2f447ee9e..b02cdcc9e 100644 --- a/tests/functional/let_array_substr3.asm +++ b/tests/functional/let_array_substr3.asm @@ -63,7 +63,6 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -72,9 +71,7 @@ __LABEL1: ; 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) @@ -85,10 +82,8 @@ __LABEL1: ; 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 @@ -126,24 +121,17 @@ __LABEL1: ; | | ; | (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) @@ -154,10 +142,8 @@ __LABEL1: ; 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 @@ -195,25 +181,16 @@ __LABEL1: ; | | ; | (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 @@ -221,7 +198,6 @@ __LABEL1: __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: @@ -231,46 +207,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -279,57 +245,44 @@ __MEM_INIT2: ; 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) @@ -338,10 +291,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -351,34 +302,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -388,91 +332,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -481,33 +402,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -517,30 +429,23 @@ __CONT2: 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 51 "let_array_substr3.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) @@ -551,10 +456,8 @@ __FREE_STR: ; 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 @@ -592,37 +495,24 @@ __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 ; 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 @@ -635,8 +525,6 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 - - ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -644,16 +532,12 @@ __ERROR: __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. @@ -665,39 +549,32 @@ __STOP: ; 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#line 111 "/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#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 @@ -705,14 +582,12 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -738,14 +613,11 @@ __MEM_DONE: ; A free block has been found. 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 @@ -753,24 +625,18 @@ __MEM_SUBTRACT: 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 @@ -779,30 +645,24 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -810,7 +670,6 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr3.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$) @@ -821,12 +680,8 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -837,10 +692,8 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -878,28 +731,16 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -922,25 +763,19 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -949,89 +784,67 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -1040,20 +853,16 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1069,7 +878,6 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret - __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1077,44 +885,33 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr3.bas" - ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/let_array_substr5.asm b/tests/functional/let_array_substr5.asm index 1dc9e807a..6314a90e5 100644 --- a/tests/functional/let_array_substr5.asm +++ b/tests/functional/let_array_substr5.asm @@ -73,7 +73,6 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #line 1 "array.asm" - ; vim: ts=4:et:sw=4: ; Copyleft (K) by Jose M. Rodriguez de la Rosa ; (a.k.a. Boriel) @@ -84,66 +83,48 @@ __LABEL1: ; HL = Start of array memory (First two bytes contains N-1 dimensions) ; Dimension values on the stack, (top of the stack, highest dimension) ; E.g. A(2, 4) -> PUSH <4>; PUSH <2> - ; For any array of N dimension A(aN-1, ..., a1, a0) ; and dimensions D[bN-1, ..., b1, b0], the offset is calculated as ; O = [a0 + b0 * (a1 + b1 * (a2 + ... bN-2(aN-1)))] ; What I will do here is to calculate the following sequence: ; ((aN-1 * bN-2) + aN-2) * bN-3 + ... - - #line 1 "mul16.asm" - __MUL16: ; Mutiplies HL with the last value stored into de stack ; Works for both signed and unsigned - PROC - LOCAL __MUL16LOOP LOCAL __MUL16NOADD - ex de, hl pop hl ; Return address ex (sp), hl ; CALLEE caller convention - __MUL16_FAST: ld b, 16 ld a, h ld c, l ld hl, 0 - __MUL16LOOP: add hl, hl ; hl << 1 sla c rla ; a,c << 1 jp nc, __MUL16NOADD add hl, de - __MUL16NOADD: djnz __MUL16LOOP - ret ; Result in hl (16 lower bits) - ENDP - #line 20 "array.asm" - -#line 24 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" - +#line 24 "/zxbasic/library-asm/array.asm" __ARRAY_PTR: ;; computes an array offset from a pointer ld c, (hl) inc hl ld h, (hl) ld l, c - __ARRAY: PROC - LOCAL LOOP LOCAL ARRAY_END LOCAL RET_ADDRESS ; Stores return address LOCAL TMP_ARR_PTR ; Stores pointer temporarily - ld e, (hl) inc hl ld d, (hl) @@ -152,7 +133,6 @@ __ARRAY: ex de, hl ex (sp), hl ; Return address in HL, array address in the stack ld (RET_ADDRESS + 1), hl ; Stores it for later - exx pop hl ; Will use H'L' as the pointer ld c, (hl) ; Loads Number of dimensions from (hl) @@ -160,22 +140,16 @@ __ARRAY: ld b, (hl) inc hl ; Ready exx - ld hl, 0 ; HL = Offset "accumulator" - LOOP: -#line 62 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" +#line 62 "/zxbasic/library-asm/array.asm" pop bc ; Get next index (Ai) from the stack - -#line 72 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" - +#line 72 "/zxbasic/library-asm/array.asm" add hl, bc ; Adds current index - exx ; Checks if B'C' = 0 ld a, b ; Which means we must exit (last element is not multiplied by anything) or c jr z, ARRAY_END ; if B'Ci == 0 we are done - ld e, (hl) ; Loads next dimension into D'E' inc hl ld d, (hl) @@ -184,26 +158,20 @@ LOOP: dec bc ; Decrements loop counter exx pop de ; DE = Max bound Number (i-th dimension) - call __FNMUL jp LOOP - ARRAY_END: ld a, (hl) exx - -#line 101 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" +#line 101 "/zxbasic/library-asm/array.asm" LOCAL ARRAY_SIZE_LOOP - ex de, hl ld hl, 0 ld b, a ARRAY_SIZE_LOOP: add hl, de djnz ARRAY_SIZE_LOOP - -#line 111 "/home/boriel/src/zxbasic/zxbasic/library-asm/array.asm" - +#line 111 "/zxbasic/library-asm/array.asm" ex de, hl ld hl, (TMP_ARR_PTR) ld a, (hl) @@ -211,39 +179,29 @@ ARRAY_SIZE_LOOP: ld h, (hl) ld l, a add hl, de ; Adds element start - RET_ADDRESS: jp 0 - ;; Performs a faster multiply for little 16bit numbs LOCAL __FNMUL, __FNMUL2 - __FNMUL: xor a or h jp nz, __MUL16_FAST or l ret z - cp 33 jp nc, __MUL16_FAST - ld b, l ld l, h ; HL = 0 - __FNMUL2: add hl, de djnz __FNMUL2 ret - TMP_ARR_PTR: DW 0 ; temporary storage for pointer to tables - ENDP - #line 61 "let_array_substr5.bas" #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) @@ -252,9 +210,7 @@ TMP_ARR_PTR: ; 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) @@ -265,10 +221,8 @@ TMP_ARR_PTR: ; 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 @@ -306,24 +260,17 @@ TMP_ARR_PTR: ; | | ; | (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) @@ -334,10 +281,8 @@ TMP_ARR_PTR: ; 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 @@ -375,25 +320,16 @@ TMP_ARR_PTR: ; | | ; | (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 @@ -401,7 +337,6 @@ TMP_ARR_PTR: __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: @@ -411,46 +346,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -459,57 +384,44 @@ __MEM_INIT2: ; 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) @@ -518,10 +430,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -531,34 +441,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -568,91 +471,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -661,33 +541,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -697,30 +568,23 @@ __CONT2: 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 62 "let_array_substr5.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) @@ -731,10 +595,8 @@ __FREE_STR: ; 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 @@ -772,37 +634,24 @@ __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 ; 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 @@ -815,8 +664,6 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 - - ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -824,16 +671,12 @@ __ERROR: __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. @@ -845,39 +688,32 @@ __STOP: ; 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#line 111 "/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#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 @@ -885,14 +721,12 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -918,14 +752,11 @@ __MEM_DONE: ; A free block has been found. 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 @@ -933,24 +764,18 @@ __MEM_SUBTRACT: 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 @@ -959,30 +784,24 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -990,7 +809,6 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 63 "let_array_substr5.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$) @@ -1001,12 +819,8 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -1017,10 +831,8 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -1058,28 +870,16 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -1102,25 +902,19 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -1129,89 +923,67 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -1220,20 +992,16 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1249,7 +1017,6 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret - __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1257,44 +1024,33 @@ __NOTHING_TO_COPY: 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 64 "let_array_substr5.bas" - ZXBASIC_USER_DATA: _b: DEFB 03h diff --git a/tests/functional/let_array_substr7.asm b/tests/functional/let_array_substr7.asm index 8dd7c25e0..4b66284a3 100644 --- a/tests/functional/let_array_substr7.asm +++ b/tests/functional/let_array_substr7.asm @@ -63,7 +63,6 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -72,9 +71,7 @@ __LABEL1: ; 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) @@ -85,10 +82,8 @@ __LABEL1: ; 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 @@ -126,24 +121,17 @@ __LABEL1: ; | | ; | (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) @@ -154,10 +142,8 @@ __LABEL1: ; 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 @@ -195,25 +181,16 @@ __LABEL1: ; | | ; | (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 @@ -221,7 +198,6 @@ __LABEL1: __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: @@ -231,46 +207,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -279,57 +245,44 @@ __MEM_INIT2: ; 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) @@ -338,10 +291,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -351,34 +302,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -388,91 +332,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -481,33 +402,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -517,30 +429,23 @@ __CONT2: 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 51 "let_array_substr7.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) @@ -551,10 +456,8 @@ __FREE_STR: ; 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 @@ -592,37 +495,24 @@ __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 ; 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 @@ -635,8 +525,6 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 - - ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -644,16 +532,12 @@ __ERROR: __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. @@ -665,39 +549,32 @@ __STOP: ; 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#line 111 "/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#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 @@ -705,14 +582,12 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -738,14 +613,11 @@ __MEM_DONE: ; A free block has been found. 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 @@ -753,24 +625,18 @@ __MEM_SUBTRACT: 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 @@ -779,30 +645,24 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -810,7 +670,6 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr7.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$) @@ -821,12 +680,8 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -837,10 +692,8 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -878,28 +731,16 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -922,25 +763,19 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -949,89 +784,67 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -1040,20 +853,16 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1069,7 +878,6 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret - __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1077,44 +885,33 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr7.bas" - ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/let_array_substr9.asm b/tests/functional/let_array_substr9.asm index 01d44a952..75eb7aebd 100644 --- a/tests/functional/let_array_substr9.asm +++ b/tests/functional/let_array_substr9.asm @@ -63,7 +63,6 @@ __LABEL1: DEFB 4Ch DEFB 4Fh #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) @@ -72,9 +71,7 @@ __LABEL1: ; 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) @@ -85,10 +82,8 @@ __LABEL1: ; 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 @@ -126,24 +121,17 @@ __LABEL1: ; | | ; | (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) @@ -154,10 +142,8 @@ __LABEL1: ; 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 @@ -195,25 +181,16 @@ __LABEL1: ; | | ; | (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 @@ -221,7 +198,6 @@ __LABEL1: __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: @@ -231,46 +207,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -279,57 +245,44 @@ __MEM_INIT2: ; 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) @@ -338,10 +291,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -351,34 +302,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -388,91 +332,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -481,33 +402,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -517,30 +429,23 @@ __CONT2: 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 51 "let_array_substr9.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) @@ -551,10 +456,8 @@ __FREE_STR: ; 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 @@ -592,37 +495,24 @@ __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 ; 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 @@ -635,8 +525,6 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 - - ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -644,16 +532,12 @@ __ERROR: __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. @@ -665,39 +549,32 @@ __STOP: ; 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#line 111 "/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#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 @@ -705,14 +582,12 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -738,14 +613,11 @@ __MEM_DONE: ; A free block has been found. 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 @@ -753,24 +625,18 @@ __MEM_SUBTRACT: 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 @@ -779,30 +645,24 @@ __ILOADSTR: ; This is the indirect pointer entry HL = (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) @@ -810,7 +670,6 @@ __LOADSTR: ; __FASTCALL__ entry ret #line 52 "let_array_substr9.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$) @@ -821,12 +680,8 @@ __LOADSTR: ; __FASTCALL__ entry ; ; 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) @@ -837,10 +692,8 @@ __LOADSTR: ; __FASTCALL__ entry ; 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 @@ -878,28 +731,16 @@ __LOADSTR: ; __FASTCALL__ entry ; | | ; | (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_REALLOC ; Reallocates a block of memory in the heap. @@ -922,25 +763,19 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -949,89 +784,67 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -1040,20 +853,16 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1069,7 +878,6 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret - __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1077,44 +885,33 @@ __NOTHING_TO_COPY: 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 53 "let_array_substr9.bas" - ZXBASIC_USER_DATA: _a: DEFW __LABEL2 diff --git a/tests/functional/llc.asm b/tests/functional/llc.asm index 69f6a37d9..b9008f025 100644 --- a/tests/functional/llc.asm +++ b/tests/functional/llc.asm @@ -14,9 +14,8 @@ __START_PROGRAM: ei call __MEM_INIT ld hl, __LABEL0 - call __LOADSTR push hl - ld a, 1 + xor a push af ld hl, 1 push hl @@ -44,7 +43,6 @@ __LABEL0: DEFW 0001h DEFB 2Eh #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) @@ -53,9 +51,7 @@ __LABEL0: ; 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) @@ -66,10 +62,8 @@ __LABEL0: ; 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 @@ -107,24 +101,17 @@ __LABEL0: ; | | ; | (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) @@ -135,10 +122,8 @@ __LABEL0: ; 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 @@ -176,25 +161,16 @@ __LABEL0: ; | | ; | (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 @@ -202,7 +178,6 @@ __LABEL0: __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: @@ -212,46 +187,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -260,57 +225,44 @@ __MEM_INIT2: ; 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) @@ -319,10 +271,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -332,34 +282,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -369,91 +312,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -462,33 +382,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -498,299 +409,21 @@ __CONT2: 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 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" - ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/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 2925882fc..102e12a02 100644 --- a/tests/functional/lvalsubstr_nolet.asm +++ b/tests/functional/lvalsubstr_nolet.asm @@ -14,9 +14,8 @@ __START_PROGRAM: ei call __MEM_INIT ld hl, __LABEL0 - call __LOADSTR push hl - ld a, 1 + xor a push af ld hl, 1 push hl @@ -44,7 +43,6 @@ __LABEL0: DEFW 0001h DEFB 2Eh #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) @@ -53,9 +51,7 @@ __LABEL0: ; 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) @@ -66,10 +62,8 @@ __LABEL0: ; 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 @@ -107,24 +101,17 @@ __LABEL0: ; | | ; | (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) @@ -135,10 +122,8 @@ __LABEL0: ; 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 @@ -176,25 +161,16 @@ __LABEL0: ; | | ; | (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 @@ -202,7 +178,6 @@ __LABEL0: __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: @@ -212,46 +187,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -260,57 +225,44 @@ __MEM_INIT2: ; 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) @@ -319,10 +271,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -332,34 +282,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -369,91 +312,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -462,33 +382,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -498,299 +409,21 @@ __CONT2: 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 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" - ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/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 c477a4535..a172fbaa5 100644 --- a/tests/functional/strbase.asm +++ b/tests/functional/strbase.asm @@ -17,9 +17,8 @@ __START_PROGRAM: ld hl, _a call __STORE_STR ld hl, __LABEL1 - call __LOADSTR push hl - ld a, 1 + xor a push af ld hl, 0 push hl @@ -64,7 +63,6 @@ __LABEL1: DEFW 0001h DEFB 6Fh #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) @@ -73,9 +71,7 @@ __LABEL1: ; 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) @@ -86,10 +82,8 @@ __LABEL1: ; 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 @@ -127,24 +121,17 @@ __LABEL1: ; | | ; | (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) @@ -155,10 +142,8 @@ __LABEL1: ; 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 @@ -196,25 +181,16 @@ __LABEL1: ; | | ; | (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 @@ -222,7 +198,6 @@ __LABEL1: __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: @@ -232,46 +207,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -280,57 +245,44 @@ __MEM_INIT2: ; 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) @@ -339,10 +291,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -352,34 +302,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -389,91 +332,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -482,33 +402,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -518,44 +429,46 @@ __CONT2: 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 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: ; ; +----------------+ <-- HEAP START @@ -593,37 +506,24 @@ __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 ; ; 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 @@ -636,8 +536,6 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 - - ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -645,16 +543,72 @@ __ERROR: __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" - - - +#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. @@ -666,39 +620,32 @@ __STOP: ; 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#line 111 "/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#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 @@ -706,14 +653,12 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -739,14 +684,11 @@ __MEM_DONE: ; A free block has been found. 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 @@ -754,153 +696,15 @@ __MEM_SUBTRACT: 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 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. @@ -923,25 +727,19 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -950,89 +748,67 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -1041,20 +817,16 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1070,7 +842,6 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret - __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1078,141 +849,105 @@ __NOTHING_TO_COPY: 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 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. ; So we needn't call STRASSING to create a duplication ; HL = address of string memory variable ; DE = address of 2n string. It just copies DE into (HL) ; freeing (HL) previously. - - - __PISTORE_STR2: ; Indirect store temporary string at (IX + BC) push ix pop hl add hl, bc - __ISTORE_STR2: ld c, (hl) ; Dereferences HL inc hl ld h, (hl) ld l, c ; HL = *HL (real string variable address) - __STORE_STR2: push hl ld c, (hl) inc hl ld h, (hl) ld l, c ; HL = *HL (real string address) - push de call __MEM_FREE pop de - pop hl ld (hl), e inc hl 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 ; DE = String start ; BC = String character end ; A register => 0 => the HL pointer wont' be freed from the HEAP ; e.g. a$(5 TO 10) => HL = a$; DE = 5; BC = 10 - ; This implements a$(X to Y) being X and Y first and ; last characters respectively. If X > Y, NULL is returned - ; Otherwise returns a pointer to a$ FROM X to Y (starting from 0) ; if Y > len(a$), then a$ will be padded with spaces (reallocating ; it in dynamic memory if needed). Returns pointer (HL) to resulting ; string. NULL (0) if no memory for padding. ; - #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 18 "strslice.asm" - - - __STRSLICE: ; Callee entry pop hl ; Return ADDRESS pop bc ; Last char pos pop de ; 1st char pos ex (sp), hl ; CALLEE. -> String start - __STRSLICE_FAST: ; __FASTCALL__ Entry PROC - LOCAL __CONT LOCAL __EMPTY LOCAL __FREE_ON_EXIT - push hl ; Stores original HL pointer to be recovered on exit ex af, af' ; Saves A register for later - push hl call __STRLEN inc bc ; Last character position + 1 (string starts from 0) @@ -1223,19 +958,16 @@ __STRSLICE_FAST: ; __FASTCALL__ Entry ld b, h ld c, l ; Copy to the end of str ccf ; Clears Carry flag for next subtraction - __CONT: ld h, b ld l, c ; HL = Last char position to copy (1 for char 0, 2 for char 1, etc) sbc hl, de ; HL = LEN(a$) - DE => Number of chars to copy jr z, __EMPTY ; 0 Chars to copy => Return HL = 0 (NULL STR) jr c, __EMPTY ; If Carry => Nothing to return (NULL STR) - ld b, h ld c, l ; BC = Number of chars to copy inc bc inc bc ; +2 bytes for string length number - push bc push de call __MEM_ALLOC @@ -1244,15 +976,12 @@ __CONT: ld a, h or l jr z, __EMPTY ; Return if NULL (no memory) - dec bc dec bc ; Number of chars to copy (Len of slice) - ld (hl), c inc hl ld (hl), b inc hl ; Stores new string length - ex (sp), hl ; Pointer to A$ now in HL; Pointer to new string chars in Stack inc hl inc hl ; Skip string length @@ -1265,26 +994,18 @@ __CONT: dec de ; Points to String LEN start ex de, hl ; Returns it in HL jr __FREE_ON_EXIT - __EMPTY: ; Return NULL (empty) string pop hl ld hl, 0 ; Return NULL - - __FREE_ON_EXIT: ex af, af' ; Recover original A register ex (sp), hl ; Original HL pointer - or a call nz, __MEM_FREE - 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 bcb4e8c83..8cc3e581d 100644 --- a/tests/functional/strbase2.asm +++ b/tests/functional/strbase2.asm @@ -18,9 +18,8 @@ __START_PROGRAM: ld hl, _a call __STORE_STR ld hl, __LABEL1 - call __LOADSTR push hl - ld a, 1 + xor a push af ld hl, 0 push hl @@ -73,7 +72,6 @@ __LABEL1: DEFW 0001h DEFB 6Fh #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) @@ -82,9 +80,7 @@ __LABEL1: ; 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) @@ -95,10 +91,8 @@ __LABEL1: ; 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 @@ -136,24 +130,17 @@ __LABEL1: ; | | ; | (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) @@ -164,10 +151,8 @@ __LABEL1: ; 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 @@ -205,25 +190,16 @@ __LABEL1: ; | | ; | (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 @@ -231,7 +207,6 @@ __LABEL1: __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: @@ -241,46 +216,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -289,57 +254,44 @@ __MEM_INIT2: ; 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) @@ -348,10 +300,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -361,34 +311,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -398,91 +341,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -491,33 +411,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -527,315 +438,32 @@ __CONT2: 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 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" - ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/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 - #line 1 "print.asm" - ; vim:ts=4:sw=4:et: ; PRINT command routine ; Does not print attribute. Use PRINT_STR or PRINT_NUM for that - #line 1 "sposn.asm" - ; Printing positioning library. PROC LOCAL ECHO_E - __LOAD_S_POSN: ; Loads into DE current ROW, COL print position from S_POSN mem var. ld de, (S_POSN) ld hl, (MAXX) @@ -843,46 +471,31 @@ __LOAD_S_POSN: ; Loads into DE current ROW, COL print position from S_POSN mem sbc hl, de ex de, hl ret - - __SAVE_S_POSN: ; Saves ROW, COL from DE into S_POSN mem var. ld hl, (MAXX) or a sbc hl, de ld (S_POSN), hl ; saves it again ret - - ECHO_E EQU 23682 MAXX EQU ECHO_E ; Max X position + 1 MAXY EQU MAXX + 1 ; Max Y position + 1 - S_POSN EQU 23688 POSX EQU S_POSN ; Current POS X POSY EQU S_POSN + 1 ; Current POS Y - ENDP - #line 6 "print.asm" #line 1 "cls.asm" - ; JUMPS directly to spectrum CLS ; This routine does not clear lower screen - ;CLS EQU 0DAFh - ; Our faster implementation - - - CLS: PROC - LOCAL COORDS LOCAL __CLS_SCR LOCAL ATTR_P LOCAL SCREEN - ld hl, 0 ld (COORDS), hl ld hl, 1821h @@ -895,67 +508,82 @@ __CLS_SCR: inc de ld bc, 6144 ldir - ; Now clear attributes - ld a, (ATTR_P) ld (hl), a ld bc, 767 ldir ret - COORDS EQU 23677 SCREEN EQU 16384 ; Default start of the screen (can be changed) ATTR_P EQU 23693 ;you can poke (SCREEN_SCRADDR) to change CLS, DRAW & PRINTing address - SCREEN_ADDR EQU (__CLS_SCR + 1) ; Address used by print and other screen routines ; to get the start of the screen 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) - PROC LOCAL __IN_SCREEN_ERR - ld hl, MAXX ld a, e cp (hl) jr nc, __IN_SCREEN_ERR ; Do nothing and return if out of range - ld a, d inc hl cp (hl) ;; jr nc, __IN_SCREEN_ERR ; Do nothing and return if out of range ;; ret ret c ; Return if carry (OK) - __IN_SCREEN_ERR: __OUT_OF_SCREEN_ERR: ; Jumps here if out of screen ld a, ERROR_OutOfScreen jp __STOP ; Saves error code and exits - ENDP #line 8 "print.asm" #line 1 "table_jump.asm" - - JUMP_HL_PLUS_2A: ; Does JP (HL + A*2) Modifies DE. Modifies A add a, a - JUMP_HL_PLUS_A: ; Does JP (HL + A) Modifies DE ld e, a ld d, 0 - JUMP_HL_PLUS_DE: ; Does JP (HL + DE) add hl, de ld e, (hl) @@ -964,17 +592,12 @@ JUMP_HL_PLUS_DE: ; Does JP (HL + DE) ex de, hl CALL_HL: jp (hl) - #line 9 "print.asm" #line 1 "ink.asm" - ; Sets ink color in ATTR_P permanently ; Parameter: Paper color in A register - #line 1 "const.asm" - ; Global constants - P_FLAG EQU 23697 FLAGS2 EQU 23681 ATTR_P EQU 23693 ; permanet ATTRIBUTES @@ -982,26 +605,20 @@ CALL_HL: CHARS EQU 23606 ; Pointer to ROM/RAM Charset UDG EQU 23675 ; Pointer to UDG Charset MEM0 EQU 5C92h ; Temporary memory buffer used by ROM chars - #line 5 "ink.asm" - INK: PROC LOCAL __SET_INK LOCAL __SET_INK2 - ld de, ATTR_P - __SET_INK: cp 8 jr nz, __SET_INK2 - inc de ; Points DE to MASK_T or MASK_P ld a, (de) or 7 ; Set bits 0,1,2 to enable transparency ld (de), a ret - __SET_INK2: ; Another entry. This will set the ink color at location pointer by DE and 7 ; # Gets color mod 8 @@ -1015,29 +632,20 @@ __SET_INK2: and 0F8h ; Reset bits 0,1,2 sign to disable transparency ld (de), a ; Store new attr ret - ; Sets the INK color passed in A register in the ATTR_T variable INK_TMP: ld de, ATTR_T jp __SET_INK - ENDP - #line 10 "print.asm" #line 1 "paper.asm" - ; Sets paper color in ATTR_P permanently ; Parameter: Paper color in A register - - - PAPER: PROC LOCAL __SET_PAPER LOCAL __SET_PAPER2 - ld de, ATTR_P - __SET_PAPER: cp 8 jr nz, __SET_PAPER2 @@ -1046,14 +654,12 @@ __SET_PAPER: or 038h ld (de), a ret - ; Another entry. This will set the paper color at location pointer by DE __SET_PAPER2: and 7 ; # Remove rlca rlca rlca ; a *= 8 - ld b, a ; Saves the color ld a, (de) and 0C7h ; Clears previous value @@ -1064,39 +670,28 @@ __SET_PAPER2: and 0C7h ; Resets bits 3,4,5 ld (de), a ret - - ; Sets the PAPER color passed in A register in the ATTR_T variable PAPER_TMP: ld de, ATTR_T jp __SET_PAPER ENDP - #line 11 "print.asm" #line 1 "flash.asm" - ; Sets flash flag in ATTR_P permanently ; Parameter: Paper color in A register - - - FLASH: ld hl, ATTR_P - PROC LOCAL IS_TR LOCAL IS_ZERO - __SET_FLASH: ; Another entry. This will set the flash flag at location pointer by DE cp 8 jr z, IS_TR - ; # Convert to 0/1 or a jr z, IS_ZERO ld a, 0x80 - IS_ZERO: ld b, a ; Saves the color ld a, (hl) @@ -1106,43 +701,32 @@ IS_ZERO: inc hl res 7, (hl) ;Reset bit 7 to disable transparency ret - IS_TR: ; transparent inc hl ; Points DE to MASK_T or MASK_P set 7, (hl) ;Set bit 7 to enable transparency ret - ; Sets the FLASH flag passed in A register in the ATTR_T variable FLASH_TMP: ld hl, ATTR_T jr __SET_FLASH ENDP - #line 12 "print.asm" #line 1 "bright.asm" - ; Sets bright flag in ATTR_P permanently ; Parameter: Paper color in A register - - - BRIGHT: ld hl, ATTR_P - PROC LOCAL IS_TR LOCAL IS_ZERO - __SET_BRIGHT: ; Another entry. This will set the bright flag at location pointer by DE cp 8 jr z, IS_TR - ; # Convert to 0/1 or a jr z, IS_ZERO ld a, 0x40 - IS_ZERO: ld b, a ; Saves the color ld a, (hl) @@ -1152,12 +736,10 @@ IS_ZERO: inc hl res 6, (hl) ;Reset bit 6 to disable transparency ret - IS_TR: ; transparent inc hl ; Points DE to MASK_T or MASK_P set 6, (hl) ;Set bit 6 to enable transparency ret - ; Sets the BRIGHT flag passed in A register in the ATTR_T variable BRIGHT_TMP: ld hl, ATTR_T @@ -1165,74 +747,50 @@ BRIGHT_TMP: ENDP #line 13 "print.asm" #line 1 "over.asm" - ; Sets OVER flag in P_FLAG permanently ; Parameter: OVER flag in bit 0 of A register #line 1 "copy_attr.asm" - - - -#line 4 "/home/boriel/src/zxbasic/zxbasic/library-asm/copy_attr.asm" - - - +#line 4 "/zxbasic/library-asm/copy_attr.asm" COPY_ATTR: ; Just copies current permanent attribs to temporal attribs ; and sets print mode PROC - LOCAL INVERSE1 LOCAL __REFRESH_TMP - INVERSE1 EQU 02Fh - ld hl, (ATTR_P) ld (ATTR_T), hl - ld hl, FLAGS2 call __REFRESH_TMP - ld hl, P_FLAG call __REFRESH_TMP - - __SET_ATTR_MODE: ; Another entry to set print modes. A contains (P_FLAG) - - LOCAL TABLE LOCAL CONT2 - rra ; Over bit to carry ld a, (FLAGS2) rla ; Over bit in bit 1, Over2 bit in bit 2 and 3 ; Only bit 0 and 1 (OVER flag) - ld c, a ld b, 0 - ld hl, TABLE add hl, bc ld a, (hl) ld (PRINT_MODE), a - ld hl, (P_FLAG) xor a ; NOP -> INVERSE0 bit 2, l jr z, CONT2 ld a, INVERSE1 ; CPL -> INVERSE1 - CONT2: ld (INVERSE_MODE), a ret - TABLE: nop ; NORMAL MODE xor (hl) ; OVER 1 MODE and (hl) ; OVER 2 MODE or (hl) ; OVER 3 MODE - -#line 65 "/home/boriel/src/zxbasic/zxbasic/library-asm/copy_attr.asm" - +#line 65 "/zxbasic/library-asm/copy_attr.asm" __REFRESH_TMP: ld a, (hl) and 10101010b @@ -1241,32 +799,24 @@ __REFRESH_TMP: or c ld (hl), a ret - ENDP - #line 4 "over.asm" - - OVER: PROC - ld c, a ; saves it for later and 2 ld hl, FLAGS2 res 1, (HL) or (hl) ld (hl), a - ld a, c ; Recovers previous value and 1 ; # Convert to 0/1 add a, a; # Shift left 1 bit for permanent - ld hl, P_FLAG res 1, (hl) or (hl) ld (hl), a ret - ; Sets OVER flag in P_FLAG temporarily OVER_TMP: ld c, a ; saves it for later @@ -1276,7 +826,6 @@ OVER_TMP: res 0, (hl) or (hl) ld (hl), a - ld a, c ; Recovers previous value and 1 ld hl, P_FLAG @@ -1284,20 +833,13 @@ OVER_TMP: or (hl) ld (hl), a jp __SET_ATTR_MODE - ENDP - #line 14 "print.asm" #line 1 "inverse.asm" - ; Sets INVERSE flag in P_FLAG permanently ; Parameter: INVERSE flag in bit 0 of A register - - - INVERSE: PROC - and 1 ; # Convert to 0/1 add a, a; # Shift left 3 bits for permanent add a, a @@ -1307,7 +849,6 @@ INVERSE: or (hl) ld (hl), a ret - ; Sets INVERSE flag in P_FLAG temporarily INVERSE_TMP: and 1 @@ -1318,19 +859,13 @@ INVERSE_TMP: or (hl) ld (hl), a jp __SET_ATTR_MODE - ENDP - #line 15 "print.asm" #line 1 "bold.asm" - ; Sets BOLD flag in P_FLAG permanently ; Parameter: BOLD flag in bit 0 of A register - - BOLD: PROC - and 1 rlca rlca @@ -1340,7 +875,6 @@ BOLD: or (hl) ld (hl), a ret - ; Sets BOLD flag in P_FLAG temporarily BOLD_TMP: and 1 @@ -1351,19 +885,13 @@ BOLD_TMP: or (hl) ld (hl), a ret - ENDP - #line 16 "print.asm" #line 1 "italic.asm" - ; Sets ITALIC flag in P_FLAG permanently ; Parameter: ITALIC flag in bit 0 of A register - - ITALIC: PROC - and 1 rrca rrca @@ -1373,7 +901,6 @@ ITALIC: or (hl) ld (hl), a ret - ; Sets ITALIC flag in P_FLAG temporarily ITALIC_TMP: and 1 @@ -1386,22 +913,11 @@ ITALIC_TMP: or (hl) ld (hl), a ret - ENDP - #line 17 "print.asm" - #line 1 "attr.asm" - ; Attribute routines ; vim:ts=4:et:sw: - - - - - - - __ATTR_ADDR: ; calc start address in DE (as (32 * d) + e) ; Contributed by Santiago Romero at http://www.speccy.org @@ -1410,54 +926,38 @@ __ATTR_ADDR: add a, a ; a * 2 ; 4 T-States add a, a ; a * 4 ; 4 T-States ld l, a ; HL = A * 4 ; 4 T-States - add hl, hl ; HL = A * 8 ; 15 T-States add hl, hl ; HL = A * 16 ; 15 T-States add hl, hl ; HL = A * 32 ; 15 T-States - ld d, 18h ; DE = 6144 + E. Note: 6144 is the screen size (before attr zone) add hl, de - ld de, (SCREEN_ADDR) ; Adds the screen address add hl, de - ; Return current screen address in HL ret - - ; Sets the attribute at a given screen coordinate (D, E). ; The attribute is taken from the ATTR_T memory variable ; Used by PRINT routines SET_ATTR: - ; Checks for valid coords call __IN_SCREEN ret nc - __SET_ATTR: ; Internal __FASTCALL__ Entry used by printing routines PROC - call __ATTR_ADDR - __SET_ATTR2: ; Sets attr from ATTR_T to (HL) which points to the scr address ld de, (ATTR_T) ; E = ATTR_T, D = MASK_T - ld a, d and (hl) ld c, a ; C = current screen color, masked - ld a, d cpl ; Negate mask and e ; Mask current attributes or c ; Mix them ld (hl), a ; Store result in screen - ret - ENDP - - ; Sets the attribute at a given screen pixel address in hl ; HL contains the address in RAM for a given pixel (not a coordinate) SET_PIXEL_ADDR_ATTR: @@ -1473,32 +973,21 @@ SET_PIXEL_ADDR_ATTR: add hl, de ;; Final screen addr jp __SET_ATTR2 #line 19 "print.asm" - ; Putting a comment starting with @INIT
; will make the compiler to add a CALL to
; It is useful for initialization routines. - - __PRINT_INIT: ; To be called before program starts (initializes library) PROC - ld hl, __PRINT_START ld (PRINT_JUMP_STATE), hl - ld hl, 1821h ld (MAXX), hl ; Sets current maxX and maxY - xor a ld (FLAGS2), a - ret - - __PRINTCHAR: ; Print character store in accumulator (A register) ; Modifies H'L', B'C', A'F', D'E', A - LOCAL PO_GR_1 - LOCAL __PRCHAR LOCAL __PRINT_CONT LOCAL __PRINT_CONT2 @@ -1510,22 +999,16 @@ __PRINTCHAR: ; Print character store in accumulator (A register) LOCAL __NO_SCROLL LOCAL __ROM_SCROLL_SCR LOCAL __TVFLAGS - __ROM_SCROLL_SCR EQU 0DFEh __TVFLAGS EQU 5C3Ch - PRINT_JUMP_STATE EQU __PRINT_JUMP + 1 - __PRINT_JUMP: jp __PRINT_START ; Where to jump. If we print 22 (AT), next two calls jumps to AT1 and AT2 respectively - __PRINT_START: cp ' ' jp c, __PRINT_SPECIAL ; Characters below ' ' are special ones - exx ; Switch to alternative registers ex af, af' ; Saves a value (char to print) for later - ld hl, __TVFLAGS bit 1, (hl) jp z, __NO_SCROLL @@ -1534,53 +1017,39 @@ __PRINT_START: res 1, (hl) __NO_SCROLL: call __LOAD_S_POSN - ; At this point we have the new coord ld hl, (SCREEN_ADDR) - ld a, d ld c, a ; Saves it for later - and 0F8h ; Masks 3 lower bit ; zy ld d, a - ld a, c ; Recovers it and 07h ; MOD 7 ; y1 rrca rrca rrca - or e ld e, a add hl, de ; HL = Screen address + DE ex de, hl ; DE = Screen address - ex af, af' - cp 80h ; Is it an UDG or a ? jp c, __SRCADDR - cp 90h jp nc, __PRINT_UDG - ; Print a 8 bit pattern (80h to 8Fh) - ld b, a call PO_GR_1 ; This ROM routine will generate the bit pattern at MEM0 ld hl, MEM0 jp __PRGRAPH - PO_GR_1 EQU 0B38h - __PRINT_UDG: sub 90h ; Sub ASC code ld bc, (UDG) jp __PRGRAPH0 - __SOURCEADDR EQU (__SRCADDR + 1) ; Address of the pointer to chars source __SRCADDR: ld bc, (CHARS) - __PRGRAPH0: add a, a ; A = a * 2 (since a < 80h) ; Thanks to Metalbrain at http://foro.speccy.org ld l, a @@ -1588,7 +1057,6 @@ __PRGRAPH0: add hl, hl add hl, hl ; HL = a * 8 add hl, bc ; HL = CHARS address - __PRGRAPH: ex de, hl ; HL = Write Address, DE = CHARS address bit 2, (iy + $47) @@ -1598,7 +1066,6 @@ __PRGRAPH: ld b, 8 ; 8 bytes per char __PRCHAR: ld a, (de) ; DE *must* be ALWAYS source, and HL destiny - PRINT_MODE: ; Which operation is used to write on the screen ; Set it with: ; LD A, @@ -1610,16 +1077,12 @@ PRINT_MODE: ; Which operation is used to write on the screen ; OR : B6h --> OR (HL) ; PUTSPRITE ; AND : A6h --> AND (HL) ; PUTMASK nop ; - INVERSE_MODE: ; 00 -> NOP -> INVERSE 0 nop ; 2F -> CPL -> INVERSE 1 - ld (hl), a - inc de inc h ; Next line djnz __PRCHAR - call __LOAD_S_POSN push de call __SET_ATTR @@ -1633,35 +1096,25 @@ INVERSE_MODE: ; 00 -> NOP -> INVERSE 0 call __PRINT_EOL1 exx ; counteracts __PRINT_EOL1 exx jp __PRINT_CONT2 - __PRINT_CONT: call __SAVE_S_POSN - __PRINT_CONT2: exx ret - ; ------------- SPECIAL CHARS (< 32) ----------------- - __PRINT_SPECIAL: ; Jumps here if it is a special char exx ld hl, __PRINT_TABLE jp JUMP_HL_PLUS_2A - - PRINT_EOL: ; Called WHENEVER there is no ";" at end of PRINT sentence exx - __PRINT_0Dh: ; Called WHEN printing CHR$(13) call __LOAD_S_POSN - __PRINT_EOL1: ; Another entry called from PRINT when next line required ld e, 0 - __PRINT_EOL2: ld a, d inc a - __PRINT_AT1_END: ld hl, (MAXY) cp l @@ -1669,15 +1122,12 @@ __PRINT_AT1_END: ld hl, __TVFLAGS set 1, (hl) ld a, d - __PRINT_EOL_END: ld d, a - __PRINT_AT2_END: call __SAVE_S_POSN exx ret - __PRINT_COM: exx push hl @@ -1688,17 +1138,14 @@ __PRINT_COM: pop de pop hl ret - __PRINT_TAB: ld hl, __PRINT_TAB1 jp __PRINT_SET_STATE - __PRINT_TAB1: ld (MEM0), a ld hl, __PRINT_TAB2 ld (PRINT_JUMP_STATE), hl ret - __PRINT_TAB2: ld a, (MEM0) ; Load tab code (ignore the current one) push hl @@ -1711,27 +1158,22 @@ __PRINT_TAB2: pop de pop hl ret - __PRINT_NOP: __PRINT_RESTART: ld hl, __PRINT_START jp __PRINT_SET_STATE - __PRINT_AT: ld hl, __PRINT_AT1 - __PRINT_SET_STATE: ld (PRINT_JUMP_STATE), hl ; Saves next entry call exx ret - __PRINT_AT1: ; Jumps here if waiting for 1st parameter exx ld hl, __PRINT_AT2 ld (PRINT_JUMP_STATE), hl ; Saves next entry call call __LOAD_S_POSN jp __PRINT_AT1_END - __PRINT_AT2: exx ld hl, __PRINT_START @@ -1742,7 +1184,6 @@ __PRINT_AT2: cp (hl) jr c, __PRINT_AT2_END jr __PRINT_EOL1 - __PRINT_DEL: call __LOAD_S_POSN ; Gets current screen position dec e @@ -1759,80 +1200,62 @@ __PRINT_DEL: ld d, h dec d jp __PRINT_AT2_END - __PRINT_INK: ld hl, __PRINT_INK2 jp __PRINT_SET_STATE - __PRINT_INK2: exx call INK_TMP jp __PRINT_RESTART - __PRINT_PAP: ld hl, __PRINT_PAP2 jp __PRINT_SET_STATE - __PRINT_PAP2: exx call PAPER_TMP jp __PRINT_RESTART - __PRINT_FLA: ld hl, __PRINT_FLA2 jp __PRINT_SET_STATE - __PRINT_FLA2: exx call FLASH_TMP jp __PRINT_RESTART - __PRINT_BRI: ld hl, __PRINT_BRI2 jp __PRINT_SET_STATE - __PRINT_BRI2: exx call BRIGHT_TMP jp __PRINT_RESTART - __PRINT_INV: ld hl, __PRINT_INV2 jp __PRINT_SET_STATE - __PRINT_INV2: exx call INVERSE_TMP jp __PRINT_RESTART - __PRINT_OVR: ld hl, __PRINT_OVR2 jp __PRINT_SET_STATE - __PRINT_OVR2: exx call OVER_TMP jp __PRINT_RESTART - __PRINT_BOLD: ld hl, __PRINT_BOLD2 jp __PRINT_SET_STATE - __PRINT_BOLD2: exx call BOLD_TMP jp __PRINT_RESTART - __PRINT_ITA: ld hl, __PRINT_ITA2 jp __PRINT_SET_STATE - __PRINT_ITA2: exx call ITALIC_TMP jp __PRINT_RESTART - - __BOLD: push hl ld hl, MEM0 @@ -1849,8 +1272,6 @@ __BOLD_LOOP: pop hl ld de, MEM0 ret - - __ITALIC: push hl ld hl, MEM0 @@ -1874,17 +1295,14 @@ __ITALIC: pop hl ld de, MEM0 ret - PRINT_COMMA: call __LOAD_S_POSN ld a, e and 16 add a, 16 - PRINT_TAB: PROC LOCAL LOOP, CONTINUE - inc a call __LOAD_S_POSN ; e = current row ld d, a @@ -1905,21 +1323,16 @@ LOOP: djnz LOOP ret ENDP - PRINT_AT: ; CHanges cursor to ROW, COL ; COL in A register ; ROW in stack - pop hl ; Ret address ex (sp), hl ; callee H = ROW ld l, a ex de, hl - call __IN_SCREEN ret nc ; Return if out of screen - jp __SAVE_S_POSN - LOCAL __PRINT_COM LOCAL __BOLD LOCAL __BOLD_LOOP @@ -1938,9 +1351,7 @@ PRINT_AT: ; CHanges cursor to ROW, COL LOCAL __PRINT_SET_STATE LOCAL __PRINT_TABLE LOCAL __PRINT_TAB, __PRINT_TAB1, __PRINT_TAB2 - __PRINT_TABLE: ; Jump table for 0 .. 22 codes - DW __PRINT_NOP ; 0 DW __PRINT_NOP ; 1 DW __PRINT_NOP ; 2 @@ -1965,64 +1376,44 @@ __PRINT_TABLE: ; Jump table for 0 .. 22 codes DW __PRINT_OVR ; 21 DW __PRINT_AT ; 22 AT DW __PRINT_TAB ; 23 TAB - ENDP - - #line 5 "print_eol_attr.asm" - - 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 - PRINT_STR: __PRINTSTR: ; __FASTCALL__ Entry to print_string PROC LOCAL __PRINT_STR_LOOP LOCAL __PRINT_STR_END - ld d, a ; Saves A reg (Flag) for later - ld a, h or l ret z ; Return if the pointer is NULL - push hl - ld c, (hl) inc hl ld b, (hl) inc hl ; BC = LEN(a$); HL = &a$ - __PRINT_STR_LOOP: ld a, b or c jr z, __PRINT_STR_END ; END if BC (counter = 0) - ld a, (hl) call __PRINTCHAR inc hl dec bc jp __PRINT_STR_LOOP - __PRINT_STR_END: pop hl ld a, d ; Recovers A flag or a ; If not 0 this is a temporary string. Free it ret z jp __MEM_FREE ; Frees str from heap and return from there - __PRINT_STR: ; Fastcall Entry ; It ONLY prints strings @@ -2031,12 +1422,9 @@ __PRINT_STR: push hl ; Push str address for later 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 ; Returns DE = Address pointer (&a$) @@ -2047,12 +1435,8 @@ __PRINT_STR: ; ; 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) @@ -2063,10 +1447,8 @@ __PRINT_STR: ; 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 @@ -2104,28 +1486,172 @@ __PRINT_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 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 "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. @@ -2148,25 +1674,19 @@ __PRINT_STR: ; --------------------------------------------------------------------- __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 @@ -2175,89 +1695,67 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -2266,20 +1764,16 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -2295,7 +1789,6 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret - __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -2303,141 +1796,105 @@ __NOTHING_TO_COPY: 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 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. ; So we needn't call STRASSING to create a duplication ; HL = address of string memory variable ; DE = address of 2n string. It just copies DE into (HL) ; freeing (HL) previously. - - - __PISTORE_STR2: ; Indirect store temporary string at (IX + BC) push ix pop hl add hl, bc - __ISTORE_STR2: ld c, (hl) ; Dereferences HL inc hl ld h, (hl) ld l, c ; HL = *HL (real string variable address) - __STORE_STR2: push hl ld c, (hl) inc hl ld h, (hl) ld l, c ; HL = *HL (real string address) - push de call __MEM_FREE pop de - pop hl ld (hl), e inc hl 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 ; DE = String start ; BC = String character end ; A register => 0 => the HL pointer wont' be freed from the HEAP ; e.g. a$(5 TO 10) => HL = a$; DE = 5; BC = 10 - ; This implements a$(X to Y) being X and Y first and ; last characters respectively. If X > Y, NULL is returned - ; Otherwise returns a pointer to a$ FROM X to Y (starting from 0) ; if Y > len(a$), then a$ will be padded with spaces (reallocating ; it in dynamic memory if needed). Returns pointer (HL) to resulting ; string. NULL (0) if no memory for padding. ; - #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 18 "strslice.asm" - - - __STRSLICE: ; Callee entry pop hl ; Return ADDRESS pop bc ; Last char pos pop de ; 1st char pos ex (sp), hl ; CALLEE. -> String start - __STRSLICE_FAST: ; __FASTCALL__ Entry PROC - LOCAL __CONT LOCAL __EMPTY LOCAL __FREE_ON_EXIT - push hl ; Stores original HL pointer to be recovered on exit ex af, af' ; Saves A register for later - push hl call __STRLEN inc bc ; Last character position + 1 (string starts from 0) @@ -2448,19 +1905,16 @@ __STRSLICE_FAST: ; __FASTCALL__ Entry ld b, h ld c, l ; Copy to the end of str ccf ; Clears Carry flag for next subtraction - __CONT: ld h, b ld l, c ; HL = Last char position to copy (1 for char 0, 2 for char 1, etc) sbc hl, de ; HL = LEN(a$) - DE => Number of chars to copy jr z, __EMPTY ; 0 Chars to copy => Return HL = 0 (NULL STR) jr c, __EMPTY ; If Carry => Nothing to return (NULL STR) - ld b, h ld c, l ; BC = Number of chars to copy inc bc inc bc ; +2 bytes for string length number - push bc push de call __MEM_ALLOC @@ -2469,15 +1923,12 @@ __CONT: ld a, h or l jr z, __EMPTY ; Return if NULL (no memory) - dec bc dec bc ; Number of chars to copy (Len of slice) - ld (hl), c inc hl ld (hl), b inc hl ; Stores new string length - ex (sp), hl ; Pointer to A$ now in HL; Pointer to new string chars in Stack inc hl inc hl ; Skip string length @@ -2490,26 +1941,18 @@ __CONT: dec de ; Points to String LEN start ex de, hl ; Returns it in HL jr __FREE_ON_EXIT - __EMPTY: ; Return NULL (empty) string pop hl ld hl, 0 ; Return NULL - - __FREE_ON_EXIT: ex af, af' ; Recover original A register ex (sp), hl ; Original HL pointer - or a call nz, __MEM_FREE - 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 7b14c7db5..62c64efaa 100644 --- a/tests/functional/substrlval.asm +++ b/tests/functional/substrlval.asm @@ -19,9 +19,8 @@ __LABEL__10: call __STORE_STR __LABEL__30: ld hl, __LABEL1 - call __LOADSTR push hl - ld a, 1 + xor a push af ld hl, 3 push hl @@ -58,7 +57,6 @@ __LABEL1: DEFW 0001h DEFB 7Ah #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) @@ -67,9 +65,7 @@ __LABEL1: ; 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) @@ -80,10 +76,8 @@ __LABEL1: ; 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 @@ -121,24 +115,17 @@ __LABEL1: ; | | ; | (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) @@ -149,10 +136,8 @@ __LABEL1: ; 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 @@ -190,25 +175,16 @@ __LABEL1: ; | | ; | (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 @@ -216,7 +192,6 @@ __LABEL1: __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: @@ -226,46 +201,36 @@ __MEM_INIT: ; Initializes the library using (RAMTOP) as start, and __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 @@ -274,57 +239,44 @@ __MEM_INIT2: ; 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) @@ -333,10 +285,8 @@ __MEM_LINK_PREV: ; Link (DE) with BC, and BC->next with HL 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) @@ -346,34 +296,27 @@ __MEM_JOIN_TEST: ; Checks for fragmented contiguous blocks and joins them 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 @@ -383,91 +326,68 @@ __MEM_BLOCK_JOIN: ; Joins current block (pointed by HL) with next one (pointed 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 @@ -476,33 +396,24 @@ __CONT0: ; At this point DE = Length of substring to copy 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 @@ -512,44 +423,46 @@ __CONT2: 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 "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: ; ; +----------------+ <-- HEAP START @@ -587,37 +500,24 @@ __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 ; ; 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 @@ -630,8 +530,6 @@ __FREE_STR: ERROR_InvalidColour EQU 19 ERROR_BreakIntoProgram EQU 20 ERROR_TapeLoadingErr EQU 26 - - ; Raises error using RST #8 __ERROR: ld (__ERROR_CODE), a @@ -639,16 +537,72 @@ __ERROR: __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" - - - +#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. @@ -660,39 +614,32 @@ __STOP: ; 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 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#line 111 "/zxbasic/library-asm/alloc.asm" ret z ; NULL -#line 113 "/home/boriel/src/zxbasic/zxbasic/library-asm/alloc.asm" +#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 @@ -700,14 +647,12 @@ __MEM_LOOP: ; Loads lengh at (HL, HL+). If Lenght >= BC, jump to __MEM_DONE 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 @@ -733,14 +678,11 @@ __MEM_DONE: ; A free block has been found. 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 @@ -748,153 +690,15 @@ __MEM_SUBTRACT: 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 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. @@ -917,25 +721,19 @@ __LOADSTR: ; __FASTCALL__ entry ; --------------------------------------------------------------------- __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 @@ -944,89 +742,67 @@ __REALLOC: ; Reallocates block pointed by HL, with new length 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 @@ -1035,20 +811,16 @@ __B_IS_NULL: ; Jumps here if B$ pointer is NULL 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 @@ -1064,7 +836,6 @@ __STRCPY: ; Copies string pointed by HL into string pointed by DE ldir pop hl ret - __NOTHING_TO_COPY: ex de, hl ld (hl), e @@ -1072,44 +843,33 @@ __NOTHING_TO_COPY: 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 48 "substrlval.bas" - +#line 46 "substrlval.bas" ZXBASIC_USER_DATA: _a: DEFB 00, 00 From 12656faea5387e795ff2a5f72a13fd4856328ff6 Mon Sep 17 00:00:00 2001 From: Jose Rodriguez Date: Sat, 16 Nov 2019 17:12:16 +0100 Subject: [PATCH 3/3] Add extra tests for LETSUBSTR --- tests/functional/sys_letsubstr0.asm | 877 ++++++++++++++++++++++++ tests/functional/sys_letsubstr0.bas | 4 + tests/functional/sys_letsubstr1.asm | 882 ++++++++++++++++++++++++ tests/functional/sys_letsubstr1.bas | 5 + tests/functional/sys_letsubstr2.asm | 993 ++++++++++++++++++++++++++++ tests/functional/sys_letsubstr2.bas | 5 + 6 files changed, 2766 insertions(+) create mode 100644 tests/functional/sys_letsubstr0.asm create mode 100644 tests/functional/sys_letsubstr0.bas create mode 100644 tests/functional/sys_letsubstr1.asm create mode 100644 tests/functional/sys_letsubstr1.bas create mode 100644 tests/functional/sys_letsubstr2.asm create mode 100644 tests/functional/sys_letsubstr2.bas 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" +