;       SCCSID = @(#)fcb.asm    6.6 91/06/17
;      SCCSID = @(#)fcb.asm     6.6 91/06/17
PAGE    ,132
TITLE   FCB - FCB parse calls for MSDOS
NAME    FCB

;
; Low level routines for parsing names into FCBs and analyzing
;    filename characters
;
;       MakeFcb
;       NameTrans
;       GetLet
;       NORMSCAN
;       DELIM
;
;   Modification history:
;
;       Created: ARR 30 March 1983
;
;       September 28, 1986  KCD
;               Added and enabled DBCS support.
;               Move TestKanj to DBCS.asm.  Now called TestDBCS.
;
;       October 12, 1986 KCD
;               Removed TableLook ifdef's assuming true.
;
;       BWS     12 Nov 86
;               Use optimized TestDBCS and ExmDBCS.
;       Madden  12/07/92        55848           Bx001
;               Validate parameters to MakeFcb.
;       MLM     11/27/95        72417
;               reduce fscrit serialization
;                 moved from PTDA to TCB:
;                 Name1, SpaceFlag, cMeta
;
; get the appropriate segment definitions
;
.xlist
include dosseg.inc

CONSTANTS       Segment
Zero    label byte
CONSTANTS       ENDS

.xcref
include basemaca.inc
include osmaca.inc
include oldmaca.inc
include dossymc.inc
include error.inc
include devsym.inc
include dbcs.inc
include meta.inc
include proc.inc
include filesys.inc                                                     ;BN001
include cpmfcb.inc                                                      ;BN001
include tkexport.inc                                                    ;72417
.cref
.list

        ModName         fcb

                                                ;72417 removed PTDA entries now
                                                ;      in TCB

        ReferTask       ProcType,BYTE
        ReferTask       CurrTCB,WORD                                    ;72417

        EXTRN           GetVisDrv:NEAR
        EXTRN           ScanPathChar:NEAR
        EXTRN           CompactExpression:NEAR
        EXTRN           UCase:NEAR
        EXTRNFAR        ProbeBuff                                       ;BN001
        EXTRNFAR        FuStrLen                                        ;BN001

        FARCODE HIGH2
HIGH2CODE  SEGMENT
    ASSUME  SS:TASKAREA

;***    MakeFcb -- Int 21 function 29h
;
; Inputs:
;       DS:SI Points to a command line
;       ES:DI Points to an empty FCB
;       Bit 0 of AL = 1 At most one leading separator scanned off
;                   = 0 Parse stops if separator encountered
;       Bit 1 of AL = 1 If drive field blank in command line - leave FCB
;                   = 0  "    "    "     "         "      "  - put 0 in FCB
;       Bit 2 of AL = 1 If filename field blank - leave FCB
;                   = 0  "       "      "       - put blanks in FCB
;       Bit 3 of AL = 1 If extension field blank - leave FCB
;                   = 0  "       "      "        - put blanks in FCB
; Function:
;       Parse command line into FCB
; Returns:
;       AL = 1 if '*' or '?' in filename or extension, 0 otherwise
;       DS:SI points to first character after filename
;

Procedure MakeFcb,<near,faronly,far16>
LocalVar    mf_fcbptr,DWORD     ; pointer to fcb

ScanSeparator   =   1
DRVBIT  EQU     2
NAMBIT  EQU     4
EXTBIT  EQU     8

;       PMONLY
        EnterProc  <es,di>      ; save pointer to FCB

        mov     bx,es           ; verify FCB is OK                      ;BN001 begin
        xchg    si,di           ; bx:si = FCB
        mov     dh,ACCTYPE_RW   ; check that we can write to the FCB
        mov     cx,size sys_fcb ; CX = length of FCB
        push    ax              ; save AX
        SaveReg <ds>            ; save DS
        CALLFAR ProbeBuff       ; is buffer valid?
        RestoreReg <ds>         ; restore DS
        jc      FCBBAD          ; no, exit

        mov     bx,ds           ; verify we can read command line
        SaveReg <di,ds,es>      ; save regs that FuStrLen will trash
        CALLFAR FuStrLen        ; BX:DI = command line
        RestoreReg <es,ds,di>   ; restore regs
FCBBAD:
        xchg    di,si           ; restore SI and DI
        pop     ax
        jnc     FCBOK           ; NC -> everything OK, continue
        jmp     NSEXIT          ; CY -> error, return
FCBOK:                                                                  ;BN001 end

        SetBP_CurrTCB           ; start 72417
        MOV     BYTE PTR [bp].TCBSpaceFlag,0
        UnSetBP_CurrTCB         ; end   72417
        XOR     DL,DL           ; Flag--not ambiguous file name
        TEST    AL,DRVBIT       ; Use current drive field if default?
        JNZ     DEFDRV
        MOV     BYTE PTR ES:[DI],0      ; No - use default drive
DEFDRV:
        INC     DI
        MOV     CX,8
        TEST    AL,NAMBIT       ; Use current name fields as default?
        XCHG    AX,BX           ; Save bits in BX
        MOV     AL," "
        JZ      FILLB           ; If not, go fill with blanks
        ADD     DI,CX
        XOR     CX,CX           ; Don't fill any
FILLB:
    REP STOSB
        MOV     CL,3
        TEST    BL,EXTBIT       ; Use current extension as default
        JZ      FILLB2
        ADD     DI,CX
        XOR     CX,CX
FILLB2:
    REP STOSB
        XCHG    AX,CX           ; Put zero in AX
        STOSW
        STOSW                   ; Initialize two words after to zero
        SUB     DI,16           ; Point back at start
        TEST    BL,ScanSeparator; Scan off separators if not zero
        JZ      SKPSPC
        CALL    SCANB           ; Peel off blanks and tabs
        CALL    DELIM           ; Is it a one-time-only delimiter?
        JNZ     NOSCAN
        INC     SI              ; Skip over the delimiter
SKPSPC:
        CALL    SCANB           ; Always kill preceding blanks and tabs
NOSCAN:
        CALL    GetLet
        JBE     NODRV           ; Quit if termination character
        CMP     BYTE PTR[SI],":"        ; Check for potential drive specifier
        JNZ     NODRV
        INC     SI              ; Skip over colon
        SUB     AL,"@"          ; Convert drive letter to drive number (A=1)
        JBE     BADDRV          ; Drive letter out of range

        PUSH    AX
        CALL    GetVisDrv
        POP     AX
        JNC     HAVDRV

BADDRV:
        MOV     DL,-1
HAVDRV:
        STOSB                   ; Put drive specifier in first byte
        INC     SI
        DEC     DI              ; Counteract next two instructions
NODRV:
        DEC     SI              ; Back up
        INC     DI              ; Skip drive byte

;**     NORMSCAN
;
;       ENTRY:  (DS:SI) = points to start of path element
;               (ES:DI) = destination of scaned path element
;               (DL)    = current ambiguous file name state
;
;       EXIT:   If SI still points to start, invalid first character
;               If carry set, (AX) = error code (protected mode task only)
;                       (DL) = 1 if '*' or '?' in pathname, 0 otherwise
;               If carry clear and SI doesn't point to start, no errors
;                       (AL,DL) = 1 if '*' or '?' in pathname, 0 otherwise
;

Entry   NORMSCAN,,LOCAL

        MOV     CX,8
        CALL    GETWORD         ; Get 8-letter file name
        JC      NSEXIT
        CMP     BYTE PTR [SI],"."
        JNZ     NODOT
        cmp     byte ptr [si+1],0 ; chars in extension?
        jnz     ns10            ; yes, go copy them
;
; the following section of code is to enable differentiation between '*s' and
; '*s.'  if the name has an explicit dot and no extension, the dot is stored
; in the FCB as the first char of the extension.  there is adequate room to
; store it there because there is no extension.  the last two bytes of the
; extension is the signature word.  it is put there to differentiate between
; system-created FCBs which have a '.' in the extension legally and user-created
; FCBs in which any dot in the name or extension is illegal.
;
        movsb                   ; copy '.' (there's room because no extension)
        mov     word ptr es:[di],FCBDOTSIG  ; put signature word in last two
                                            ; bytes of extension
        jmp short NODOT

ns10:   INC     SI              ; Skip over dot if present
        MOV     CX,3            ; Get 3-letter extension
        CALL    MUSTGETWORD
        JC      NSEXIT

;
; DL = 1 if there was a "?" or a "*" in the name.
; DL = -1 if there was an invalid drive specified
; DL = 0 otherwise.
;
NODOT:  MOV     AL,DL
        or      al,al           ; metachars in name?
        jz      NSEXIT          ; no, go return (NC)
        js      NSEXIT          ; Invalid drive. Return error (AL = 0FFH).
        les     di,mf_fcbptr    ; (ES:DI) -> FCB
        inc     di              ; (ES:DI) -> name in FCB
        call    Compatibility
        mov     al,1            ; indicate metas
        CLC

NSEXIT: LeaveProc
        RET

;**     GETWORD - checks first character for delimiter before
;                 copying the path element.
;
;       ENTRY:  (DS:SI) = points to start of path element
;               (ES:DI) = destination of parsed path element
;               (CX)    = number of characters to copy into destination
;               (DL)    = current ambiguous file name state
;
;       EXIT:   If SI still points to start, invalid first character
;               If carry set, (AX) = error code (protected mode task only)
;                       (DL) = 1 if '*' or '?' in pathname, 0 otherwise
;               If carry clear and SI doesn't point to start, no errors
;                       (DL) = 1 if '*' or '?' in pathname, 0 otherwise
;

Entry   GETWORD,,LOCAL

        CALL    GetLet          ; (AL) = character from path element
        JBE     NONAM           ; Exit if invalid character
        DEC     SI

;**     MUSTGETWORD - copys the path element no matter what the first
;                     character is.
;
;       ENTRY:  (DS:SI) = points to start of path element
;               (ES:DI) = destination of parsed path element
;               (CX)    = number of characters to copy into destination
;               (DL)    = current ambiguous file name state
;
;       EXIT:   (DL) = 1 if '*' or '?' in pathname, 0 otherwise
;               If carry set, (AX) = error code
;               If carry clear, no errors
;

Entry   MUSTGETWORD,,LOCAL

        CALL    GetLet
        JNZ     MustCheckCX
;
; If SpaceFlag is set then we allow spaces in a pathname. It is OK to check
; for this first since a space cannot be a lead DBCS byte.
;
        SetBP_CurrTCB           ; start 72417
        TEST    BYTE PTR [bp].TCBSpaceFlag,0FFh
        UnSetBP_CurrTCB         ; end   72417
        JZ      FILLNAM
        CMP     AL," "
        JNZ     FILLNAM
MustCheckCX:
        JCXZ    CHECKEND
        DEC     CX
        STOSB
        TestDBCS NOTDUAL3       ; double byte character?
        JNZ     NOTDUAL3        ; no - jump down
        JCXZ    BNDERR          ; Attempt to straddle boundary
        MOVSB                   ; Transfer second byte
        DEC     CX              ; account for trailing byte
        JMP     SHORT NOTDUAL3

BNDERR: MOV     BYTE PTR ES:[DI-1],' ' ; put space for first byte
        LODSB                          ; skip trailing byte
CHECKEND:
        CMP     ProcType,PT_REALMODE ; If 3xbox, silently truncate long names
        JE      MUSTGETWORD     ; jump if 3xbox
        MOV     AX,ERROR_FILENAME_EXCED_RANGE
        OR      DL,DL           ; If meta chars, return META_EXPANSION_TOO_LONG
        JNZ     HASMETA         ; jump if meta chars
        SetBP_CurrTCB           ; start 72417
        CMP     [bp].TCBcMeta,-1
        UnSetBP_CurrTCB         ; end   72417
        JE      ERREXIT         ; jump if no previous meta chars also
HASMETA:
        MOV     AX,ERROR_META_EXPANSION_TOO_LONG
ERREXIT:
        STC                     ; If protected mode task, EXIT with error
        RET

NOTDUAL3:
        CMP     AL,"?"
        jz      metafound
        cmp     al,'*'
        jnz     MUSTGETWORD
metafound:
        OR      DL,1            ; Flag ambiguous file name
        JMP     MUSTGETWORD
FILLNAM:
        MOV     AL," "
    REP STOSB
        DEC     SI
        JMP     SHORT EXITOK    ; jump to exit

;
; Exit because of invalid character
;
NONAM:  ADD     DI,CX
        DEC     SI
        JMP     SHORT EXITOK    ; jump to exit with SI unchanged

SCANB:
        LODSB
        CALL    SPCHK
        JZ      SCANB
        DEC     SI
EXITOK:
        CLC                     ; EXIT with no error
        RET
EndProc   MakeFcb

;***    Compatibility
;
;       ENTRY:  ES:DI -> name or extension in FCB
;
;       EXIT:   ES:DI -> one past end of name or extension (entry di+cx)
;
;       PSEUDOCODE
;       set the char count for name part processing
;start:    scan for '*'
;          if not found
;             go to endlabel
;          else
;             scan for char other than '*' and '?'
;             if found
;                go to done
;             else
;                fill in from '*' with ?s
;  done:   if (extn not processed) set up char count to 3 and
;               go to start;
;          else set di to end of fcb name
;               and return
;
;   endlabel: if (name being processed) go to done
;             else      /* extn being processed */
;                if (blank extn and we replaced '*' in name part)
;                       replace extn with '?'
;                go to done
;
;
Procedure Compatibility,near,local

        SaveReg <dx,di>
        mov     dx,2            ;
        mov     cx,8            ; name part
; find first '*'
; '*' is not a valid trailing byte, so this is dbcs correct.
; cx = 8 / 3  for name part or ext part
; dl = 2 for name part and 1 for ext part
; dh = 1 if we filled in '?' for '*' and 0 otherwise

com05:  mov     al,'*'      ; scan for "*"
        repne   scasb
        jne     com50       ; not found, so we're done

; look through string for non-meta
        SaveReg <cx,di>     ; remember offset of '*' and number of chars left
com10:  mov     al,es:[di]  ; lodsb with es:[di]
        inc     di
        cmp     al,' '
        je      com20
        cmp     al,'?'
        je      com20
        cmp     al,'*'
        jne     com40
com20:  loop    com10

; no non-metas after first '*'.  fill in with '?'s.

        RestoreReg <di,cx>  ; (ES:DI) -> one after '*', cx = number of chars left
        inc     cx          ; include '*'
        dec     di
        mov     al,'?'
        rep     stosb       ; fill in with '?'s
        inc     dh          ;
com30:  dec     dl          ;
        mov     cx,3       ; ready for parsing ext part
        jnz     com05      ; only name part over
        RestoreReg      <di,dx>
        add     di,11      ; es:di -> first byte after FCB name+ext
        ret

; non-meta found after first '*'.  make di point to one past end of name/ext

com40:  pop     di              ; instead of  RestoreReg <di,cx>
        pop     cx              ;
        add     di,cx
        jmp short com30

; no stars to replace in the current  portion (name/ext)

com50:  cmp     dl,1            ; ext part ?
        jne     com30           ; no,we are processing name part, go continue
        or      dh,dh           ; ext part; did we replaced '*' with '?' ?
        jz      com30           ; no go continue
;
        mov     al,' '          ; no extension at all ?
        mov     cx,3            ;
; di is past the end of the ext part in FCB
        sub     di,3            ; di = start of extn
        repe    scasb           ;

        jmp     com30           ; leave the extension alone

; b719558 if there is no extension, leave it as blanks!!!
;jnz    com30           ;
;mov    al,'?'          ; fill with '?'
;mov    cx,3            ;
;sub    di,3            ;
;rep    stosb           ;
;jmp    com30           ;


EndProc Compatibility

;***    NameTrans - scan off an element of a path.
;
;       NameTrans is used to scan off an element of a path and put
;       it into NAME1. We must allow spaces in pathnames.
;       If the name starts with 0E5H, it is changed to 05H. This is for
;       directory search routines where 0E5H is is a valid character in
;       a directory name, but is used to also mark a directory entry as
;       being deleted (i.e. 'free').
;       Once we are done copying the name, we check to make sure that we
;       are pointing to a path separator ('\' or NULL). If not, then it
;       is considered an error since that path must have had more than
;       one '.' in it, which is invalid in the FAT file system.
;
;       ENTRY:  DS:SI points to start of path element
;
;       EXIT:   Name1 has asciiz name (uppercased)
;               ES = TASKAREA
;               DS:SI advanced after name
;               half of implied dot processing done:
;                   if string contains no dots and no "*"s, a dot is appended
;               If SI still points to start, invalid first character
;               if 'CY' (AX) = error code
;                       (DL) = 1 if '*' or '?' in pathname, 0 otherwise
;               if 'NC' and SI doesn't point to start, no errors
;                       (AL,DL) = 1 if '*' or '?' in pathname, 0 otherwise
;                       (BL) = path separator
;
;       USES:   AX,BX,CX,DX,SI,DI,ES, flags
;
;       ALGORITHM:
;               find end of first component
;               CompactExpression
;               if (no error)
;                  if (no "*"s seen)
;                     if (no dot in string)
;                        put one on the end (for implied '.')
;                  if (Name1[0] == E5H)
;                      Name1[0] = 5
;               else
;                  process error
;
;
;       NOTE:   This routine is only used for the FAT file system (or
;               a file system that uses the 8.3 naming format) since it
;               assumes that names have to be in 8.3 format.
;
Procedure NameTrans,hybrid
    ASSUME  DS:NOTHING,ES:NOTHING

        cmp     byte ptr [si],0     ; null path?
        LJE     ntr45       ; yes, go return error
;
; If the first character of a name was previously translated from e5->5
; then improper DBCS handling will result here.  So we translate it back
; to e5.  It will be set properly to 5 in Name1 on exit.
; NOTE: this effects a 'permanent' translation in the caller's space.
;       (most likely the string pointed to by WFP_Start).
;
        cmp     byte ptr [si],5
        jne     ntr05
        mov     byte ptr [si],0e5h

ntr05:  push    si
ntr10:  mov     al,[si]     ; get char to test
        JPathChar <ntr20>   ; skip dbcs test if path separator
        or      al,al       ; or null
        jz      ntr20       ; (AL) = char
        inc     si          ; pathptr++
        TestDBCS <ntr10>    ; if (DBCS)
        jnz     ntr10       ;   pathptr++
        inc     si
        jmp short ntr10
;
; (AL) = path separator
;
ntr20:  mov     dx,si       ; (DS:DX) -> end of component
        mov     byte ptr [si],0 ; null terminate for CompactExpression

        pop     si          ; (DS:SI) -> beginning of component
        CMP     BYTE PTR [SI],'.'
        mov     bl,al
        JZ      ntr55
        SaveReg <ax,dx>
        call    CompactExpression
        RestoreReg <si,bx>  ; (BL) = previous path separator, (SI) -> path sep
        mov     [si],bl     ; restore previous path separator
        inc     si          ; (DS:SI) -> one past path separator
        jc      ntr50       ; go process errors

        mov     di,CurrTCB  ; start 72417
        lea     di,TASKAREA:[di].TCBName1 ; (ES:DI) -> Name1
                            ; end   72417
ntr22:  mov     al,es:[di]  ;    while (not end-of-string) &&
        or      al,al       ;      ('.' not seen)          &&
        jz      ntr23       ;      ('*' not seen)
        cmp     al,'.'      ; Look for the Dot, if found jump
        je      ntrx22      ; to check if it is the first char.      ;### PTR B721889
;### PTR B721889        je      ntr25       ;
        cmp     al,'*'      ;
        je      ntr25       ;
        inc     di          ; update string pointer
        TestDBCS <ntr22>
        jnz     ntr22
        inc     di
        jmp short ntr22
ntrx22:                     ;Load address of Name1 for check.         ;### PTR B721889
                            ; start 72417
        SetBP_CurrTCB
        lea     cx,[bp].TCBName1 ;if the dot appears as first character in ;### PTR B721889
        UnSetBP_CurrTCB
        cmp     cx,di       ;Name1 then name is invalid because of all;### PTR B721889
        je      ntr53       ;leading blanks.                          ;### PTR B721889
        jmp short ntr25     ;If not error, continue.                  ;### PTR B721889
ntr23:  mov     word ptr es:[di],002eh  ; if (end-of-string) append '.'
ntr25:  mov     al,dl       ; (AL) = 1 if metas seen
        SetBP_CurrTCB
        CMP     [bp].TCBName1,0E5H
        UnSetBP_CurrTCB
        JNZ     ntr30
        SetBP_CurrTCB
        MOV     [bp].TCBName1,5 ; Magic name translation
        UnSetBP_CurrTCB     ; end   72417
ntr30:  clc

ntr40:  RET

ntr45:  mov     ax,ERROR_PATH_NOT_FOUND
        stc
        jmp short ntr40

;   we found a '.' in the begin of component
;
; figure out which error code to return.
;
;    if (no more path chars)
;       ERROR_FILE_NOT_FOUND
;    else
;       ERROR_PATH_NOT_FOUND

ntr53:  xor     bx,bx           ; name begins with '.', therefore invalid
ntr55:  or      bl,bl           ; is next path char '\0' ?
        STC
        MOV     AX,ERROR_INVALID_NAME   ;### PTR B719957
;       MOV     AX,ERROR_FILE_NOT_FOUND ;### PTR B719957
        JZ      ntr40                   ; Bad file if no more path chars
        MOV     AX,ERROR_PATH_NOT_FOUND ; Set bad path error
        jmp short ntr40

; error from CompactExpression:
;
; if (char == '.')  /*  multiple '.'s  */
;    ERROR_INVALID_NAME
; else              /* name too long */
;    if (metas seen)
;       ERROR_META_EXPANSION_TOO_LONG
;    else
;       ERROR_FILENAME_EXCED_RANGE
;
ntr50:  cmp     al,'.'          ;
        jne     ntr60
        MOV     AX,ERROR_INVALID_NAME
        STC
        jmp short ntr40

ntr60:
        stc
;mov    ax,ERROR_META_EXPANSION_TOO_LONG b717336
;jnz    ntr40           ; yes, go return
        MOV     AX,ERROR_FILENAME_EXCED_RANGE
ntr70:  jmp short ntr40

EndProc   NameTrans


BREAK   <GetLet, DELIM -- CHECK CHARACTERS AND CONVERT>

CONSTANTS       SEGMENT

ChType  Macro   ch,bits
        ORG     CharType-Zero+ch
        db      bits
        ENDM

IFDEF DISABLENEWFILES

        PUBLIC  CharType
CharType    DB  256 dup (-1)
        ChType  ".", <LOW (NOT (     fChk))>
        ChType  '"', <LOW (NOT (fFCB+fChk))>
        ChType  "/", <LOW (NOT (fFCB+fChk))>
        ChType  "\", <LOW (NOT (fFCB+fChk))>
        ChType  "[", <LOW (NOT (fFCB+fChk))>
        ChType  "]", <LOW (NOT (fFCB+fChk))>
        ChType  ":", <LOW (NOT (fFCB+fChk+fDelim))>
        ChType  "<", <LOW (NOT (fFCB+fChk+fDelim))>
        ChType  "|", <LOW (NOT (fFCB+fChk+fDelim))>
        ChType  ">", <LOW (NOT (fFCB+fChk+fDelim))>
        ChType  "+", <LOW (NOT (fFCB+fChk+fDelim))>
        ChType  "=", <LOW (NOT (fFCB+fChk+fDelim))>
        ChType  ";", <LOW (NOT (fFCB+fChk+fDelim))>
        ChType  ",", <LOW (NOT (fFCB+fChk+fDelim))>
        ChType  0,   <LOW (NOT (fFCB+fChk))>           ; NUL
        ChType  1,   <LOW (NOT (fFCB+fChk))>           ; ^A
        ChType  2,   <LOW (NOT (fFCB+fChk))>           ; ^b
        ChType  3,   <LOW (NOT (fFCB+fChk))>           ; ^c
        ChType  4,   <LOW (NOT (fFCB+fChk))>           ; ^d
        ChType  5,   <LOW (NOT (fFCB+fChk))>           ; ^e
        ChType  6,   <LOW (NOT (fFCB+fChk))>           ; ^f
        ChType  7,   <LOW (NOT (fFCB+fChk))>           ; ^g
        ChType  8,   <LOW (NOT (fFCB+fChk))>           ; ^h
        ChType  9,   <LOW (NOT (fFCB+fChk+fDelim+fSpChk))> ; Tab
        ChType  10,  <LOW (NOT (fFCB+fChk))>           ; ^j
        ChType  11,  <LOW (NOT (fFCB+fChk))>           ; ^k
        ChType  12,  <LOW (NOT (fFCB+fChk))>           ; ^l
        ChType  13,  <LOW (NOT (fFCB+fChk))>           ; ^m
        ChType  14,  <LOW (NOT (fFCB+fChk))>           ; ^n
        ChType  15,  <LOW (NOT (fFCB+fChk))>           ; ^o
        ChType  16,  <LOW (NOT (fFCB+fChk))>           ; ^p
        ChType  17,  <LOW (NOT (fFCB+fChk))>           ; ^q
        ChType  18,  <LOW (NOT (fFCB+fChk))>           ; ^r
        ChType  19,  <LOW (NOT (fFCB+fChk))>           ; ^s
        ChType  20,  <LOW (NOT (fFCB+fChk))>           ; ^t
        ChType  21,  <LOW (NOT (fFCB+fChk))>           ; ^u
        ChType  22,  <LOW (NOT (fFCB+fChk))>           ; ^v
        ChType  23,  <LOW (NOT (fFCB+fChk))>           ; ^w
        ChType  24,  <LOW (NOT (fFCB+fChk))>           ; ^x
        ChType  25,  <LOW (NOT (fFCB+fChk))>           ; ^y
        ChType  26,  <LOW (NOT (fFCB+fChk))>           ; ^z
        ChType  27,  <LOW (NOT (fFCB+fChk))>           ; ^[
        ChType  28,  <LOW (NOT (fFCB+fChk))>           ; ^\
        ChType  29,  <LOW (NOT (fFCB+fChk))>           ; ^]
        ChType  30,  <LOW (NOT (fFCB+fChk))>           ; ^^
        ChType  31,  <LOW (NOT (fFCB+fChk))>           ; ^_
        ChType  " ", <LOW (NOT (     fChk+fDelim+fSpChk))>
        ChType  255, -1

ELSE
        PUBLIC  CharType
CharType    DB  256 dup (-1)
        ChType  ".", <LOW (NOT (     fChk))>
        ChType  '"', <LOW (NOT (fFCB+fChk))>
        ChType  "/", <LOW (NOT (fFCB+fChk))>
        ChType  "\", <LOW (NOT (fFCB+fChk))>
        ChType  "[", <LOW (NOT (     fChk       +fNewChar))>
        ChType  "]", <LOW (NOT (     fChk       +fNewChar))>
        ChType  ":", <LOW (NOT (fFCB+fChk+fDelim))>
        ChType  "<", <LOW (NOT (fFCB+fChk+fDelim))>
        ChType  "|", <LOW (NOT (fFCB+fChk+fDelim))>
        ChType  ">", <LOW (NOT (fFCB+fChk+fDelim))>
        ChType  "+", <LOW (NOT (     fChk+fDelim+fNewChar))>
        ChType  "=", <LOW (NOT (     fChk+fDelim+fNewChar))>
        ChType  ";", <LOW (NOT (     fChk+fDelim+fNewChar))>
        ChType  ",", <LOW (NOT (     fChk+fDelim+fNewChar))>
        ChType  0,   <LOW (NOT (fFCB+fChk))>           ; NUL
        ChType  1,   <LOW (NOT (fFCB+fChk))>           ; ^A
        ChType  2,   <LOW (NOT (fFCB+fChk))>           ; ^b
        ChType  3,   <LOW (NOT (fFCB+fChk))>           ; ^c
        ChType  4,   <LOW (NOT (fFCB+fChk))>           ; ^d
        ChType  5,   <LOW (NOT (fFCB+fChk))>           ; ^e
        ChType  6,   <LOW (NOT (fFCB+fChk))>           ; ^f
        ChType  7,   <LOW (NOT (fFCB+fChk))>           ; ^g
        ChType  8,   <LOW (NOT (fFCB+fChk))>           ; ^h
        ChType  9,   <LOW (NOT (fFCB+fChk+fDelim+fSpChk))> ; Tab
        ChType  10,  <LOW (NOT (fFCB+fChk))>           ; ^j
        ChType  11,  <LOW (NOT (fFCB+fChk))>           ; ^k
        ChType  12,  <LOW (NOT (fFCB+fChk))>           ; ^l
        ChType  13,  <LOW (NOT (fFCB+fChk))>           ; ^m
        ChType  14,  <LOW (NOT (fFCB+fChk))>           ; ^n
        ChType  15,  <LOW (NOT (fFCB+fChk))>           ; ^o
        ChType  16,  <LOW (NOT (fFCB+fChk))>           ; ^p
        ChType  17,  <LOW (NOT (fFCB+fChk))>           ; ^q
        ChType  18,  <LOW (NOT (fFCB+fChk))>           ; ^r
        ChType  19,  <LOW (NOT (fFCB+fChk))>           ; ^s
        ChType  20,  <LOW (NOT (fFCB+fChk))>           ; ^t
        ChType  21,  <LOW (NOT (fFCB+fChk))>           ; ^u
        ChType  22,  <LOW (NOT (fFCB+fChk))>           ; ^v
        ChType  23,  <LOW (NOT (fFCB+fChk))>           ; ^w
        ChType  24,  <LOW (NOT (fFCB+fChk))>           ; ^x
        ChType  25,  <LOW (NOT (fFCB+fChk))>           ; ^y
        ChType  26,  <LOW (NOT (fFCB+fChk))>           ; ^z
        ChType  27,  <LOW (NOT (fFCB+fChk))>           ; ^[
        ChType  28,  <LOW (NOT (fFCB+fChk))>           ; ^\
        ChType  29,  <LOW (NOT (fFCB+fChk))>           ; ^]
        ChType  30,  <LOW (NOT (fFCB+fChk))>           ; ^^
        ChType  31,  <LOW (NOT (fFCB+fChk))>           ; ^_
        ChType  " ", <LOW (NOT (     fChk+fDelim+fSpChk))>
        ChType  255, -1
ENDIF

CONSTANTS       ENDS

;
; Get a byte from [SI], convert it to upper case, and compare for delimiter.
; ZF set if a delimiter, CY set if a control character (other than TAB).
;

Procedure GetLet,NEAR,local
        LODSB

        TestDBCS gl10           ; DBCS? Go to gl10 if not.
        jnz     gl10
        cmp     byte ptr ds:[si],0  ; lead byte followed by zero?
        jne     gl05            ; No, everything's ok. NC and NZ set.
        stc
gl05:   ret

gl10:   call    UCase
        PUSH    AX
        MOV     AH,fChk

; TypChk        - check character type
;
; ENTRY
;       (SP)    Saved AX value
;       (SP+2)  Return address
;       AL      Character
;       AH      Type class
;
; EXIT
;       ZF      Clear if char is not of given type
;               Set if char is of given type
;       AX      Restored from stack
;
; USES  AX, flags

TypChk: ; Character type checking helper
        PUSH    BX
        MOV     BX,OFFSET CharTypeHIGH2
        XLAT    BYTE PTR CS:[BX]
        TEST    AL,AH
        POP     BX
        POP     AX                      ; Restore AX before returning
        RET

Entry   DELIM

        PUSH    AX
        MOV     AH,fDelim
        JMP     SHORT TypChk

Entry SPCHK
        PUSH    AX
        MOV     AH,fSpChk
        JMP     SHORT TypChk

EndProc GetLet

HIGH2CODE  ENDS
        END
