; Copyright (c) 2011, Michael Alyn Miller <malyn@strangeGizmo.com>.
; All rights reserved.
;
; Redistribution and use in source and binary forms, with or without
; modification, are permitted provided that the following conditions are met:
;
; 1. Redistributions of source code must retain the above copyright notice
; unmodified, this list of conditions, and the following disclaimer.
; 2. Redistributions in binary form must reproduce the above copyright notice,
; this list of conditions and the following disclaimer in the documentation
; and/or other materials provided with the distribution.
; 3. Neither the name of Michael Alyn Miller nor the names of the contributors
; to this software may be used to endorse or promote products derived from
; this software without specific prior written permission.
;
; THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND ANY
; EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
; DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
; DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
; (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
; ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
; THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
; ======================================================================
; SEARCH-ORDER Words
; ======================================================================
; ----------------------------------------------------------------------
; DEFINITIONS [SEARCH] 16.6.1.1180 ( -- )
;
; Make the compilation word list the same as the first word list in the
; search order. Specifies that the names of subsequent definitions will
; be placed in the compilation word list. Subsequent changes in the
; search order will not affect the compilation word list.
;
; ---
; : DEFINITIONS ( -- ) CONTEXT @ SET-CURRENT ;
LINKTO(LINK_SEARCH,0,11,'S',"NOITINIFED")
DEFINITIONS:JMP ENTER
.WORD CONTEXT,FETCH,SETCURRENT,EXIT
; ----------------------------------------------------------------------
; FORTH-WORDLIST [SEARCH] 16.6.1.1595 ( -- wid )
;
; Return wid, the identifier of the word list that includes all standard
; words provided by the implementation. This word list is initially the
; compilation word list and is part of the initial search order.
LINKTO(DEFINITIONS,0,14,'T',"SILDROW-HTROF")
FORTHWORDLIST:JMP ENTER
.WORD LIT,FORTHWL,EXIT
; ----------------------------------------------------------------------
; GET-CURRENT [SEARCH] 16.6.1.1643 ( -- wid )
;
; Return wid, the identifier of the compilation word list.
LINKTO(FORTHWORDLIST,0,11,'T',"NERRUC-TEG")
GETCURRENT: JMP ENTER
.WORD CURRENT,FETCH,EXIT
; ----------------------------------------------------------------------
; GET-ORDER [SEARCH] 16.6.1.1647 ( -- widn ... wid1 n )
;
; Returns the number of word lists n in the search order and the word
; list identifiers widn ... wid1 identifying these word lists. wid1
; identifies the word list that is searched first, and widn the word
; list that is searched last. The search order is unaffected.
;
; ---
; : GET-ORDER ( -- widn ... wid1 n)
; 0 #SOES 1- DO SOESTART I CELLS + @ -1 +LOOP #SOES ;
LINKTO(GETCURRENT,0,9,'R',"EDRO-TEG")
GETORDER: JMP ENTER
.WORD ZERO,NUMSOES,ONEMINUS,pdo
_getorder1: .WORD LIT,SOESTART,I,CELLS,PLUS,FETCH,LIT,-1,pplusloop,_getorder1
.WORD NUMSOES
.WORD EXIT
; ----------------------------------------------------------------------
; SEARCH-WORDLIST [SEARCH] 16.1.2192 ( c-addr u wid -- 0 | xt 1 | xt -1 )
;
; Find the definition identified by the string c-addr u in the word list
; identified by wid. If the definition is not found, return zero. If
; the definition is found, return its execution token xt and one (1) if
; the definition is immediate, minus-one (-1) otherwise.
;
; ---
; This word traverses the dictionary's linked list until the traversal
; enters ROM. At that point (FIND) stops using the linked list and tries
; to locate the word using the perfect hash table generated at build
; time. This only happens if wid points to the FORTH word list, which
; is tracked by B (FORTH=0; other=-1). The _phash subroutine generates
; two hash values, H and L. The target word, if it exists in ROM, will
; be found at one of two locations: HL or LH. HL is the more likely
; location and is searched first. C maintains the state of the search
; location. -1 indicates that the search should use HL, 0 indicates
; that the search should use LH, and 1 indicates that the search failed.
LINKTO(GETORDER,0,15,'T',"SILDROW-HCRAES")
SEARCHWORDLIST:SAVEDE
SAVEBC
POP D ; Get wid from the stack,
MOV B,D ; ..copy wid into B
MOV C,E ; ..and C,
LXI H,FORTHWL ; ..get the FORTH wid in HL,
DSUB ; ..then compare wid to the FORTH wid;
JZ _swlFORTH ; ..jump to where we clear B if FORTH,
MVI B,-1 ; ..otherwise set B to -1
JMP _swlLATEST ; ..and load LATEST.
_swlFORTH: MVI B,0 ; FORTH wid, so clear B.
_swlLATEST: LHLX ; Get the latest word in wid
XCHG ; ..and put that value in DE.
MVI C,-1 ; Initialize our phash flag to -1.
POP H ; Pop the length of the string
SHLD HOLDD ; ..and cache the value.
POP H ; Pop the string pointer
SHLD HOLDH ; ..and cache the value.
_swlAGAIN:
#IFDEF PHASH
MOV A,D ; See if we are still in RAM (the
ANI 80h ; ..high bit of the addr is not zero) -- or
ORA B ; ..we are not in the FORTH word list -- and
JNZ _swlAGAIN1 ; ..keep traversing the linked list if so.
_swlPHASH: PUSH H ; Save the string pointer on the stack,
LDA HOLDD ; ..get the string length into A,
CALL _phash ; ..then hash the string.
MOV A,C ; Move our phash flag to A,
ORA A ; ..then check the state of the flag:
JM _swlPHASHH1 ; ..use H1 if the value is negative;
JZ _swlPHASHH2 ; ..use H2 if the value is zero;
POP H ; ..otherwise no match, pop the counted string
JMP _swlFAIL ; ..and fail.
_swlPHASHH2:MOV A,L ; Move H2 to A,
MOV L,H ; ..move H1 to L,
JMP _swlPHASH1 ; ..then continue.
_swlPHASHH1:MOV A,H ; Move H1 to A
_swlPHASH1: ANI PHASHMASK ; ..and mask off the high bits of H1.
MOV H,A ; Get the masked off bits of H1 back into H.
DAD H ; HL=HL<<1 to convert from hash to cell offset.
MOV A,H ; Move the high byte of the offset to A,
ADI PHASHTAB>>8 ; ..add the high byte of PHASHTAB to A,
MOV H,A ; ..and then put the PHASHTAB address into H.
MOV E,M ; Get the low byte of the hash cell in E,
INX H ; ..increment to the high byte,
MOV D,M ; ..then get the low byte into D.
POP H ; Restore the string pointer.
INR C ; Increment our phash flag.
MOV A,D ; Move D to A,
ORA E ; ..then OR A and E to see if the cell is zero;
JZ _swlPHASH ; ..try to phash again if so.
#ENDIF
_swlAGAIN1: LDAX D ; Get the name length into A.
ANI 01111111b ; Strip the immediate bit.
LXI H,HOLDD ; Point HL at the string length,
CMP M ; ..then compare the two lengths+smudge bits.
JNZ _swlNEXTWORD;Jump if not zero (not equal) to the next word.
PUSH D ; Save DE since we are about to scan through it.
DCX D ; Go to the first dictionary char (prev byte).
LHLD HOLDH ; Point HL at the first string character.
_swlNEXTCHAR:LDAX D ; Get the next dictionary value into A.
ANI 01111111b ; Strip the end-of-name bit.
CMP M ; Compare the two characters.
JZ _swlMATCHCHAR;Jump if zero (equal) to match.
XRI 00100000b ; Try switching the case
CMP M ; ..and then repeating the match.
JNZ _swlNEXTWORDDE;.Not a match if not zero (not equal).
ORI 00100000b ; Only a match if A-Z/a-z. Force to lower,
CPI 'a' ; ..then see if less than 'a'.
JM _swlNEXTWORDDE;.If so, this is not a match.
CPI 'z'+1 ; If greater than 'z'+1,
JP _swlNEXTWORDDE;.then this is also not a match.
_swlMATCHCHAR:LDAX D ; The strings are a match if this is the last
ANI 10000000b ; ..character in the name (high bit set).
JNZ _swlMATCH ; We're done if this is a match.
DCX D ; Go to the next dictionary char (prev byte).
INX H ; Go to the next string character.
JMP _swlNEXTCHAR;Evaluate the next character.
_swlMATCH: POP D ; Restore DE (which is now pointing at a char)
LDAX D ; Get the flags into A
ANI 10000000b ; ..and focus on just the immediate flag.
INXNFATOCFA(D) ; Skip ahead to the CFA (xt)
PUSH D ; ..and push xt to the stack.
JNZ _swlIMM ; Immediate gets a 1 pushed to the stack,
LXI H,0FFFFh ; ..non-immediate gets a -1
PUSH H ; ..pushed to the stack.
JMP _swlDONE ; We're done.
_swlIMM: LXI H,1 ; Immediate word, so push 1
PUSH H ; ..to the stack.
JMP _swlDONE ; We're done.
_swlNEXTWORDDE:POP D ; Restore DE (which is now pointing at a char).
_swlNEXTWORD:INXNFATOLFA(D) ; Move to the word's LFA,
LHLX ; ..get the LFA in HL,
XCHG ; ..put the LFA into DE,
LHLD HOLDH ; ..and restore HL.
#IFDEF PHASH
MOV A,D ; The phash routine ignores the LFA, so
ANI 80h ; ..see if we are in RAM -- or
ORA B ; ..we are not in the FORTH word list -- and
JNZ _swlNEXTWORD1;..keep traversing the linked list if so;
JMP _swlPHASH ; ..continue the phash process otherwise.
#ENDIF
_swlNEXTWORD1:MOV A,D ; Keep searching for a match
ORA E ; ..if the LFA
JNZ _swlAGAIN ; ..is not zero.
_swlFAIL: LXI H,0 ; Push false
PUSH H ; ..to the stack.
_swlDONE: RESTOREDE
RESTOREBC
NEXT
#IFDEF PHASH
; Entry: HL=c-addr A=u (all registers are used)
; Exit : HL=hash values (H1 in H, H2 in L)
_phash: PUSH B ; Save BC
PUSH D ; ..and DE.
LXI D,0 ; Clear the hash values,
PUSH D ; ..which are stored on the stack.
ORA A ; See if the string is zero-length;
JZ _phashDONE ; ..and exit if so.
MOV C,A ; Otherwise move the length to A.
_phashNEXT: MOV A,M ; Get the next character into A,
CPI 'a' ; ..then see if less than 'a';
JM _phashNEXT1 ; ..if so, don't uppercase.
CPI 'z'+1 ; If greater than 'z'+1,
JP _phashNEXT1 ; ..don't uppercase.
ANI 11011111b ; Convert uppercase to lowercase.
_phashNEXT1:XTHL ; Swap the string pos with the hashes.
MOV B,A ; Save a copy of the character.
XRA H ; XOR the character with the H1,
MOV E,A ; ..move the PHASHAUX offset into E,
MVI D,PHASHAUX1>>8;.put the PHASHAUX1 base offset into D,
LDAX D ; ..then lookup the new hash value,
MOV H,A ; ..and move the hash value to H.
MOV A,B ; Get the cached copy of the character.
XRA L ; XOR the character with the H2,
MOV E,A ; ..move the PHASHAUX offset into E,
MVI D,PHASHAUX2>>8;.put the PHASHAUX2 base offset into D,
LDAX D ; ..then lookup the new hash value,
MOV L,A ; ..and move the hash value to L.
XTHL ; Swap the hashes with the string pos.
INX H ; Increment to the next character,
DCR C ; ..decrement the count,
JNZ _phashNEXT ; ..and keep looping if we count is not zero.
_phashDONE: POP H ; Pop the hash values into HL.
POP D ; Restore DE
POP B ; ..and BC.
RET ; We're done.
#ENDIF
; ----------------------------------------------------------------------
; SET-CURRENT [SEARCH] 16.6.1.2195 ( wid -- )
;
; Set the compilation word list to the word list identified by wid.
LINKTO(SEARCHWORDLIST,0,11,'T',"NERRUC-TES")
SETCURRENT: JMP ENTER
.WORD CURRENT,STORE,EXIT
; ----------------------------------------------------------------------
; SET-ORDER [SEARCH] 16.6.1.2197 ( widn ... wid1 n -- )
;
; Set the search order to the word lists identified by widn ... wid1.
; Subsequently, word list wid1 will be searched first, and word list
; widn searched last. If n is zero, empty the search order. If n is
; minus one, set the search order to the implementation-defined minimum
; search order. The minimum search order shall include the words
; FORTH-WORDLIST and SET-ORDER. A system shall allow n to be at least
; eight.
;
; ---
; : SET-ORDER ( widn ... wid1 n --) 0 DO SOESTART I CELLS + ! LOOP ;
LINKTO(SETCURRENT,0,9,'R',"EDRO-TES")
SETORDER: JMP ENTER
.WORD ZERO,pdo
_setorder1: .WORD LIT,SOESTART,I,CELLS,PLUS,STORE,ploop,_setorder1
_setorder2: .WORD EXIT
; ----------------------------------------------------------------------
; WORDLIST [SEARCH] 16.6.1.2460 ( -- wid )
;
; Create a new empty word list, returning its word list identifier wid.
; The new word list may be returned from a pool of preallocated word
; lists or may be dynamically allocated in data space. A system shall
; allow the creation of at least 8 new word lists in addition to any
; provided as part of the system.
LINKTO(SETORDER,0,8,'T',"SILDROW")
WORDLIST: JMP ENTER
.WORD HERE,ZERO,COMMA,EXIT
; ======================================================================
; SEARCH Words (implementation details)
; ======================================================================
; ----------------------------------------------------------------------
; #SOES [MFORTH] "num-s-o-es" ( -- n )
;
; Returns the number of word lists n in the search order.
;
; ---
; : #SOES ( --n)
; CONTEXT DUP BEGIN DUP @ 0<> WHILE CELL+ REPEAT SWAP - 2/ ;
LINKTO(WORDLIST,0,5,'S',"EOS#")
NUMSOES: JMP ENTER
.WORD CONTEXT,DUP
_numsoes1: .WORD DUP,FETCH,ZERONOTEQUALS,zbranch,_numsoes2
.WORD CELLPLUS,branch,_numsoes1
_numsoes2: .WORD SWAP,MINUS,TWOSLASH,EXIT
; ----------------------------------------------------------------------
; CONTEXT [MFORTH] ( -- a-addr )
;
; a-addr is the address of a cell that contains a pointer to the first
; word list in the search order.
LINKTO(NUMSOES,0,7,'T',"XETNOC")
CONTEXT: LXI H,SOESTART
PUSH H
NEXT
; ----------------------------------------------------------------------
; CURRENT [MFORTH] ( -- a-addr )
;
; a-addr is the address of a cell that contains a pointer to the current
; compilation word list.
LINKTO(CONTEXT,0,7,'T',"NERRUC")
LAST_SEARCH:
CURRENT: LXI H,TICKCURRENT
PUSH H
NEXT