;      SCCSID = @(#)metachar.asm        6.6 91/10/17
;
; REVISION HISTORY:
;***********************************************************************;60281;
; 17Feb93 - RickGiroux,  Chg Team                                       ;60281;
; This fixes problem of                                                 ;60281;
; "rename long.file.name *.size.*" which would produce "long.file.size" ;60281;
; which now becomes "long.size.name" and other undesirable results,     ;60281;
; such as: given "long.size.file", rename *.*.file *.name.123, which    ;60281;
; would produce "long.size.name.123" result instead of long.name.123    ;60281;
;********************************************************************** ;60281;
;**********************************************************************;111754;
; 16Feb95 - Vincent Villamizar,  Chg Team                              ;111754;
;                                                                      ;111754;
; FindFirst on ????????.??? (or any combination of ?s) does not return ;111754;
; the Parent Dir (..) on a CD FileSystem.  Changes were made to        ;111754;
; MetaMatch().                                                         ;111754;
;**********************************************************************;111754;
; 11/27/95 - MLM defect 72417                                           ;72417;
;                 moved from PTDA to TCB: Name1                         ;72417;
;***********************************************************************;72417;
;
;***    metachar.asm - meta character processing routines
;
;       Global          Local
;       ------          -----
;       w_EditName
;                       MetaEdit
;       CompactExpression
;       CopyOneComponent
;       PackName
;       UnpackName
;       MetaMatch
;
; Meta characters have not one, but TWO sets of semantics.  The first
; is as search characters, where they are used to select which filenames
; are returned to the user.  The second is as edit characters, used to
; construct a new name given a source name and a target name specification.
; (The second use occurs in places like "copy *.txt *.old", and is not
; actually supported anywhere in the kernel except in FCB_rename, and
; some special FCB editing calls.)  The MetaEdit routine implements the
; second set of semantics and is available to apps through the DosEditName
; API.

include dosseg.inc
include basemaca.inc
include osmaca.inc
include oldmaca.inc
include curdir.inc
include dbcs.inc
include dossymc.inc
include error.inc
include meta.inc
include proc.inc
include tkexport.inc

        EXTRN       ScanPathChar:NEAR
        EXTRN       SuBuffz:NEAR
        EXTRN       Trunc8dot3:NEAR
        EXTRN       UCase:NEAR
        EXTRN       StrCpy:NEAR

                                            ;72417 removed PTDA entry now
                                            ;      in TCB
        ReferTask       ProcType,BYTE
        ReferTask       CurrTCB,WORD

        FARCODE HIGH2
HIGH2CODE SEGMENT
    ASSUME  SS:TASKAREA

;***    w_EditName - call MetaEdit
;
;       ENTER   (DS:SI) -> destination name
;               (ES:DI) -> source name
;               (AX:DX) -> user buffer
;               (CX) = ResultBufLen
;               (BX) = edit level
;
;       EXIT    'NC' - no error
;                   resulting name stored in buffer at (AX:BX)
;                   (CX) == length of resulting buf
;               'CY' - error
;
;       USES    AX, BX, CX, DX, DI, SI, DS, ES, flags
;
;       PSEUDOCODE
;          check edit level
;          check for path chars
;          allocate stack buffer for result (MAXPATHLEN)
;          MetaEdit
;          truncate result name to 8.3 if 3xbox
;          upper case result name
;          if (no error)
;             SuBuffz
Procedure w_EditName,<near,faronly,far16>
LocalVar we_resultbuf,MAXPATHLEN    ; buffer for new name

        EnterProc
        cmp     bx,EDITLEVEL1
        LJNE    wen40
;
; check for drivename in source and dest. return error if found.
; Check for path chars in source and dest.  Return error if found.
;
        cmp     byte ptr [si+1],':' ; check for "d:" in dest
        LJE     wen70           ; found it, go return error
        cmp     byte ptr es:[di+1],':' ; check for "d:" in source
        LJE     wen70           ; found it, go return error
        SaveReg <si,ax>
        call    ScanPathChar    ; check destination name for path chars
        LJZ     wen60           ; path char found, go return error
        mov     si,di
        SaveReg <ds,es>
        RestoreReg <ds>         ; (DS:SI) -> source
        call    ScanPathChar    ; check source name for path chars
        LJZ     wen50           ; path char found, go return error
        RestoreReg <ds,ax,si>   ; (DS:SI) -> destination

        lea     bx,we_resultbuf ; (SS:BX) -> stack buffer for result
        SaveReg <ax,bx,cx,dx>
        mov     cx,MAXPATHLEN   ; (CX) = length of buffer
        call    MetaEdit    ; (CX) = new name length
        RestoreReg <di,dx,si,bx> ; (BX:DI) -> user buffer, (DX) = buffer length
        LJC     wenx

        SaveReg <bx,si,di>
        TaskContext <ds,es>,di ; (DS:SI) -> stack buffer containing new name
        mov     di,si       ; (ES:DI) -> stack buffer containing new name
;
; truncate to 8.3 if 3xbox. it's ok to do this in place, since the resulting
; name will always be the same length or shorter than the starting name.
; only FCB_Rename calls this routine in real mode.
;
        cmp     ProcType,PT_REALMODE ;if (real mode)
        jne     wen10                ;  do 8.3 enforcement

        SaveReg <si,di>
        call    Trunc8dot3
        jnc     wen05
        jmp     wen45               ; >1 '.'s
wen05:
        mov     cx,di               ; (DS:CX) -> one past end of extension
        RestoreReg <di,si>          ; (DS:SI) -> beginning of name
        sub     cx,di               ; (CX) = length of name
        dec     cx
;
; (CX) == length of new name (not including NUL)
; (DX) == length of user buffer
;
wen10:  cmp     dx,cx               ; is new name longer than buffer
        ja      wen11               ; no
        jmp     wen80               ; yes, go return error
wen11:
        RestoreReg <di,si,bx> ; (DS:SI) -> stack buffer containing new name
                            ; (BX:DI) -> user buffer
        SaveReg <cx,bx,di>

        push    bx
        mov     bx, cx          ; bx -> new name length
        cmp     cx, 1             ; single char name ?
        jne     NotSingleCharName ; no
        cmp byte ptr ds:[si][bx-1], "."
        jne short FindDot
        pop     bx
        jmp short wen70              ; error invalid name

NotSingleCharName:
FindDot:
        cmp byte ptr ds:[si][bx-1], "."
        jne short FoundNonDot
        dec     bx
        jnz short FindDot
        jmp short wen70              ; error invalid name

FoundNonDot:
        cmp     bx, cx          ; was there any dot at the end?
        je short wen15          ; no
        inc     bx              ; bx -> first trailing dot
        mov     cx, bx          ; cx -> first trailing dot
        mov     byte ptr ds:[si][bx], 0  ; set null char

wen15:
        pop     bx
        inc     cx          ; copy NULL

wen20:
        call    SuBuffz     ; copy name into user space
        RestoreReg <di,bx,cx> ; (CX) = length of new name
        jc      wenx
    assume ds:nothing

; uppercase all names if NONEWFILES defined.

IFNDEF DISABLENEWFILES
        push    bx
        mov     bx,CurrTCB
        test    TASKAREA:[bx].TCBNewFlags,TCBNEWFILES ; newfiles?
        pop     bx
        jnz     wen25                   ; yes, go return
ENDIF

; need to uppercase name.  do it in the user buffer.  this is ok because we
; haven't blocked since we copied the name to the user buffer.

BeginNoBlock
        mov     si,di
        mov     ds,bx       ; ds:si -> beginning of user buffer
        mov     es,bx       ; es:di -> beginning of user buffer
        call    StrCpy      ; uppercase name

EndNoBlock

wen25:  xor     ax,ax

wenx:   xor     bx,bx
        cmp     bx,ax       ; set carry appropriately
wen30:  LeaveProc
        ret

wen40:  mov     ax,ERROR_INVALID_PARAMETER
        stc
        jmp short wen30

wen45:  add     sp,4
wen50:  pop     ax          ; clean stack
wen60:  add     sp,4        ; clean stack
wen70:  mov     ax,ERROR_INVALID_NAME
        stc
        jmp short wen30

wen80:  mov     ax,ERROR_BUFFER_OVERFLOW
        stc
        jmp short wen30

EndProc w_EditName

;***    MetaEdit - apply metaedit rules to source name using destination name
;
;       This routine is case insensitive and handles DBCS properly.
;
;       ENTER   (DS:SI) -> destination name
;               (ES:DI) -> source name
;               (SS:BX) -> resultbuffer
;               (CX) = ResultBufLen
;
;       EXIT    'NC' - no error
;                   resulting name stored in buffer at (SS:BX)
;                   (CX) = resulting name length
;               'CY' - error
;                   (AX) == ERROR_BUFFER_OVERFLOW
;
;       USES    AX, BX, CX, DX, DI, SI, flags
;
;       PSEUDOCODE
;           ResLen = 0;
;           pSrc = Source;
;           pEd = Editmask;
;           pRes = ResultBuf;
;           while (*pEd) {
;               if (ResLen < *ResBufLen) {
;                   if (DBCS(*pEd)          /*  this works because '*', '?' */
;                       goto default;       /*  and '.' are not valid lead bytes */
;                   switch (*pEd) {
;                   case '*':
;                       delimit = *(pEd+1);
;                       while ((ResLen < *ResBufLen) &&
;                           (*pSrc != '\0') && (*pSrc != delimit)) {
;                              if (DBCS(*pSrc)) {
;                                  *(pRes++) = *(pSrc++);
;                                  ResLen++;
;                              }
;                              if (ResLen < *ResBufLen) {
;                                  *(pRes++) = *(pSrc++);
;                                  ResLen++;
;                              }
;                              else
;                                  return(error)
;                       }
;                       break;
;                   case '?':
;                       if ((*pSrc != '.') && (*pSrc != '\0')) {
;                              if (DBCS(*pSrc)) {
;                                  *(pRes++) = *(pSrc++);
;                                  ResLen++;
;                              }
;                              if (ResLen < *ResBufLen) {
;                                  *(pRes++) = *(pSrc++);
;                                  ResLen++;
;                              }
;                              else
;                                  return(error)
;                       }
;                       break;
;                   case '.':
;                       while ((*pSrc != '.') && (*pSrc != '\0')) {
;                           if (DBCS(*pSrc))
;                               pSrc++;
;                           pSrc++;
;                       }
;                       *(pRes++) = '.';        /* from EditMask, even if src doesn't */
;                                               /* have one, so always put one.       */
;                       ResLen++;
;                       if (*pSrc)              /* point one past '.' */
;                           pSrc++;
;                       break;
;                   default:
;                       if ((*pSrc != '.') && (*pSrc != '\0')) {
;                           if (DBCS(*pSrc))
;                               pSrc++;
;                           pSrc++;
;                       }
;                       if (DBCS(*pEd)) {
;                           *(pRes++) = *(pEd++);
;                           ResLen++;
;                       }
;                       if (ResLen < *ResBufLen) {
;                           *(pRes++) = *pEd;
;                           ResLen++;
;                       }
;                       else
;                           return(error)
;                       break;
;                   }
;                   pEd++;
;               }
;               else {
;                  return(error)
;               }
;           }
;           if ((ResLen) < *ResBufLen) {
;               *pRes = '\0';
;               *ResBufLen = ++ResLen;
;               return(no error)
;           }
;           else
;               return(error);
;       }
;
Procedure MetaEdit,HYBRID
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
LocalVar me_ResBufLen,WORD  ; result buffer length
LocalVar hitdots,BYTE

        EnterProc <CX>

        xor     cx,cx           ; ResLen = 0
;
; (DS:SI) -> destination name
; (ES:DI) -> source name
; (SS:BX) -> result buffer
;
; strip trailing ' 's and '.'s off destination
; find end then go back
        mov     hitdots,cl
        mov     al,byte ptr ds:[si]     ; were no chars sent in?
        or      al,al
        jz      me10
        mov     dx,si
me1:
        lodsb
        or      al,al
        jne     me1
; we are at the '0'
; strip off all trailing ' 's
; and strip off all trailing '.'s except the last one
; at least one char was sent in
        dec     si
moreblanks:
        dec     si
        mov     al,byte ptr ds:[si]
        cmp     al,' '
        je      moreblanks

; strip off trailing '.'s except last one
moredots:
        cmp     al,'.'
        jne     putinzero
        inc     hitdots
        dec     si
        mov     al,byte ptr ds:[si]
        jmp     moredots

putinzero:
        mov     al,hitdots
        cmp     al,0
        jnz     leavedot
        mov     byte ptr ds:[si+1],0      ; chop off the trailing ones by ending
        jmp     me8                     ; it sooner
leavedot:
        mov     byte ptr ds:[si+2],0      ; chop off the trailing ones by ending

me8:    mov     si,dx                   ; restore ptr to front of string


me10:   lodsb                   ;
        or      al,al           ; while (*pEd)
        LJZ     me60            ;
        TestDBCS                ;       if (DBCS(*pEd)    ;### b728344 10-17-91
        LJZ     me40            ;          goto default   ;### b728344 10-17-91
        call    UCase
        cmp     me_ResBufLen,cx ;    if (ResLen <*ResBufLen)
        LJBE    me70            ;
;       TestDBCS                ;       if (DBCS(*pEd)    ;### b728344 10-17-91
;       LJZ     me40            ;          goto default   ;### b728344 10-17-91
        cmp     al,'*'          ;    switch (*pEd)
        LJNE    me20            ;    case '*': {
        mov     ax,WORD PTR [si] ;      delimit = *(pEd+1)
        call    UCase
        mov     dx,ax               ; dl = *pEd (ucased)
        mov     al,BYTE PTR es:[di] ; al = *pSrc
        call    UCase               ; al = *pSrc  (ucased)
me12:   cmp     me_ResBufLen,cx ;       while ((ResLen <*ResBufLen) &&
        LJBE    me70            ;
        cmp     al,0            ;             (*pSrc != '\0') &&
        je      me10
        cmp     al,dl           ;             (*pSrc != delimit)) {
        jne     me13

;       call    last_delimeter  ; b723512 look for another instance of ;60281;
;       jc      me13            ; the delimeter.                       ;60281;

        TestDBCS me10
        jnz     me10
        cmp     BYTE PTR [si+1],dh
        je      me10            ; DBCS and != delimit
        jmp     short me14      ;
me13:   TestDBCS me15           ;          if (DBCS(*pSrc)) {
        jnz     me15            ;
me14:   mov     BYTE PTR TASKAREA:[bx],al ;   *pRes = *pSrc
        inc     bx              ;             pRes++
        inc     di              ;             pSrc++
        inc     cx              ;             ResLen++ }
        cmp     me_ResBufLen,cx ;          if (ResLen <*ResBufLen) {
        LJBE    me70            ;
me15:   mov     al,BYTE PTR es:[di] ; get mixed-case *pSrc
        mov     BYTE PTR TASKAREA:[bx],al ;   *pRes = *pSrc
        inc     bx              ;             pRes++
        inc     di              ;             pSrc++
        inc     cx              ;             ResLen++ }
        mov     al,BYTE PTR es:[di] ;
        TestDBCS                ;       if (DBCS(*pEd)    ;### b728344 10-17-91
        LJZ     me12            ;          goto default   ;### b728344 10-17-91
                                ;    ??  GOTO DEFAULT is "LJZ  me40"    ;60281;
        call    UCase
        jmp short me12          ;       }
;
; (AL) = *pEd
;
me20:   cmp     al,'?'          ;    case '?': {
        jne     me30            ;
        mov     al,BYTE PTR es:[di] ; al = *pSrc
        cmp     al,'.'          ;       if ((*pSrc != '.') &&
        LJE     me10
        cmp     al,0            ;             (*pSrc != '\0'))
        LJE     me10
        TestDBCS me25           ;          if (DBCS(*pSrc)) {
        jnz     me25            ;
        mov     BYTE PTR TASKAREA:[bx],al ;   *pRes = *pSrc
        inc     bx              ;             pRes++
        inc     di              ;             pSrc++
        inc     cx              ;             ResLen++ }
        mov     al,BYTE PTR es:[di] ;
        cmp     me_ResBufLen,cx ;          if (ResLen <*ResBufLen) {
        LJBE    me70            ;
me25:   mov     BYTE PTR TASKAREA:[bx],al ; *pRes = *pSrc
        inc     bx              ;             pRes++
        inc     di              ;             pSrc++
        inc     cx              ;             ResLen++ }
        jmp     me10            ;      }
;
; (AL) = *pEd
;
me30:   cmp     al,'.'          ;    case '.': {
        jne     me40            ;
me32:   mov     al,BYTE PTR es:[di] ;
        cmp     al,'.'          ;       while ((*pSrc != '.') &&
        je      me37
        cmp     al,0            ;              (*pSrc != '\0')) {
        je      me37
        TestDBCS me35           ;          if (DBCS(*pSrc))
        jnz     me35
        inc     di              ;             pSrc++;
me35:   inc     di              ;          pSrc++
        jmp short me32          ;       }
me37:   mov     BYTE PTR TASKAREA:[bx],'.' ; *pRes = '.'
        inc     bx              ;       pRes++
        inc     cx              ;       ResLen++
        cmp     al,0            ;       if (*pSrc)
        LJE     me10            ;
        inc     di              ;          pSrc++
        jmp me10                ;       break;
;
; (AL) = *pEd
;
me40:   xchg    dx,ax           ;    default:
        mov     al,BYTE PTR es:[di] ;
        cmp     al,'.'          ;       if ((*pSrc != '.') &&
        je      me42
        cmp     al,0            ;           (*pSrc != '\0')) {
        je      me42
        TestDBCS me41           ;          if (DBCS(*pSrc))
        jnz     me41
        inc     di              ;             pSrc++;
me41:   inc     di              ;          pSrc++
me42:   xchg    dx,ax
        TestDBCS me45           ;       if (DBCS(*pEd)) {
        jnz     me45            ;
        mov     BYTE PTR TASKAREA:[bx],al ; *pRes = *pEd;
        inc     bx              ;          pRes++
        inc     cx              ;          ResLen++
        inc     si              ;          pEd++
        cmp     me_ResBufLen,cx ;       if (ResLen <*ResBufLen) {
        jbe     me70            ;
me45:   mov     al,BYTE PTR [si-1] ; al = *pEd (not ucased)
        mov     BYTE PTR TASKAREA:[bx],al ; *pRes = *pEd
        inc     bx              ;          pRes++
        inc     cx              ;          ResLen++ }
        jmp me10                ; }
                                ;   pEd++ done with lodsb at top

me60:   cmp     me_ResBufLen,cx ; if (ResLen < *ResBufLen)
        jbe     me70            ;
        mov     BYTE PTR TASKAREA:[bx],0 ; *pRes = '\0'
mex:    LeaveProc
        ret

me70:   stc
        mov     ax,ERROR_BUFFER_OVERFLOW
        jmp short mex

EndProc MetaEdit

;************************************************************************
; PTR B723512 "Move truncates file name"
; -----------------------------------------------------------------------
; FUNCTION: This procedure will look for a second instance of a
;           delimeter within the file name being scanned by MetaEdit
;
; ENTRY:  es:di <--- source filename
;         dl    <--- delimeter
;
; EXIT:   carry set - another delimeter was found
;
; Carlos A. Romero - CP Change Team - July 10, 1991
;-------------------------------------------------------------------------

Procedure last_delimeter, NEAR
          pusha                         ;save all registers
          push  es
          pop   ds
          mov   si, di                  ;use ds:si
          clc                           ;forward direction
          inc   si                      ;character after delimeter
mkr1:
          lodsb                         ;get current character
          cmp   al, 0                   ;
          je    not_found               ;reached end of string
          cmp   al, dl                  ;DL holds delimeter
          je    found_another           ;
          jmp   mkr1                    ;continue looking

found_another:
          stc                           ;carry on
not_found:
          popa
          ret
EndProc   last_delimeter

;***    CompactExpression - compact metachar expression
;
; All 8.3 expressions can be compacted to 24 bytes or less.  The maximum
; name is "*a*b*c*d*e*f*g*h.*a*b*c0."  The rules to compact expressions
; are described:
;
; '*' can match 0 characters and should be counted as 0.  '?' matches
; 1 character if it's one of the first 8 non '*' characters, and 0 after
; that, unless it's part of the extension:
;
;    a*?b?c?d*??.e?f
;
; All the '?'s in the above expression count as 1, except the one preceding
; the '.'.  The same thing applies to the extension.  The hard part arises
; when there's no '.' to divide the name and extension.  A '*' can match a
; '.' if it's in the right position:
;
;    a*?b?c?d*??*e?f
;
; We count the first 8 non '*' characters then look for a '.'.
; If we don't find one, we look for a '*' (after the first 8).  If we find one,
; we count the extension from there.  If we don't find one, any non-meta
; characters violate 8.3.
;
;              '.'
;
;               |
;               v
;    a*?b?c?d*??*e?f    8.3 legal
;    a*?b?c?d*??*e?fg   8.3 illegal
;   a*?b?c?d*?b?*e?f    8.3 illegal
;   a*?b?c?d*?b?.e?f    8.3 illegal
;    a*?b?c?d*??e?f     8.3 illegal
;
;       ENTRY   (DS:SI) -> expression
;
;       EXIT    (DL) = 1 if metas
;               'NC'
;                  [Name1] contains compacted expression
;                  (DI) -> one past path separator
;               'CY' - error
;                  not 8.3 and PM
;                  (AL) = violating character
;
;       USES    AX, BX, CX, DX, DI, SI, ES, flags
;
;
;       PSEUDOCODE
;           (ES:DI) = Name1
;           char = CopyOneComponent(8)
;           if (char == '\0')
;              return
;
;   /*  find out if name has extension  */
;
;           if (char != '.') {
;              look for '.'
;              if (found)
;                 look for real char in between (not ?,*,0) (DBCS)
;                 if (found && PM)
;                    return error
;                 CopyOneComponent(from '.')
;              else
;                 look for '*'      ; can match '.' (DBCS)
;                 if (found)
;                    look for real char in between
;                    if (found && PM)
;                       return error
;                    CopyOneComponent(from '*')
;                 else     /*  no extension  */
;                    look for real char (DBCS)
;                    if (found && PM)
;                       return error
;                    truncate with '\0'
;                    return(no error)
;           else
;              CopyOneComponent(3)
;
;   /* process chars after extension */
;
;           look for real char
;           if (found && PM)
;              return error
;           truncate with '\0'
Procedure CompactExpression,NEAR
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA
LocalVar ce_metas,BYTE          ; if metas seen


        EnterProc <0>           ; no metas seen
        TaskContext <ES>,di
                                ;72417 moved from PTDA to TCB
        mov     di,CurrTCB
        lea     di,TASKAREA:[di].TCBName1  ; (ES:DI) -> Name1
                                ;72417 end
        mov     cx,MAXFATNAMELEN ; (CX) = max chars in component
        call    CopyOneComponent ; (DS:SI) -> one past last char
        or      ce_metas,bh     ; (BH) = 1 if metas
        or      al,al           ; if (last char copied == '\0')
        LJZ     cex             ;    return(carry clear)
        cmp     al,'.'          ; if (last char copied != '.')
        jz      ce80
        push    si
ce10:   lodsb                   ;   look for '.'
        or      al,al
        jz      ce30
        TestDBCS ce12           ; if (DBCS) then not '.'
        jnz     ce12
        inc     si
        jmp short ce10

ce12:   cmp     al,'.'
        jne     ce10            ;

; found '.'
; process '.'
                                ;   if (found) {
ce15:   mov     cx,si           ;     (CX) -> one past '.'
        dec     cx              ;     (CX) -> '.'
        pop     si              ;     (SI) -> one past end of name
        sub     cx,si           ;     (CX) = number of chars to search
        call    SearchForChar   ;     look for non * or ? char
        jnc     ce20            ;     if (found)
        CMP     ProcType,PT_REALMODE ;  if (protect mode)
        jne     cerx            ;         return(error)

ce20:   or      ce_metas,bl
        add     si,cx           ;     (DS:SI) -> '.' or '*'
        movsb
        mov     cx,MAXFATEXTLEN ;     (DS:SI) -> one past end of name
        call    CopyOneComponent ;     CopyOneComponent(3)
        jmp short ce90          ;   }

ce30:   pop     si              ;   else
        push    si
ce40:   lodsb
        or      al,al           ;     look for '*'
        jz      ce50
        cmp     al,'*'
        jne     ce40

; found '*'

        jmp short ce15          ;     if (found)
                                ;       goto process '.'

ce50:   pop     si              ;     else
        mov     al,1            ;       indicate check for non-metas
        jmp short ce90          ;       goto process end-of-string

ce80:   mov     cx,MAXFATEXTLEN ; else
        call    CopyOneComponent ;   CopyOneComponent(3)

; process end-of-string

ce90:   or      ce_metas,bh
        cmp     al,'.'          ; multiple '.'s - error
        stc
        je      cex
        or      al,al
        jz      cex             ; '\0' copied - go exit
        mov     cx,00ffh        ; go till end of string
        call    SearchForChar   ; look for non * or ? char
        jnc     ce100           ; if (found)
        cmp     ProcType,PT_REALMODE ;if (protect mode)
        jne     cerx            ;       return(error)
ce100:  or      ce_metas,bl
        xor     ax,ax           ; no error
        stosb                   ; truncate

cex:    mov     dl,ce_metas
        LeaveProc
        ret

;
; Got an error.
; BL is intication of whether we saw any metas in the last call or not.
;
cerx:   or      ce_metas,bl
        stc
        jmp     short cex

EndProc CompactExpression

;***    SearchForChar - search for non * or ? char in string
;
;       Search CX chars or until end of string
;
;       ENTER   (DS:SI) -> ascii string to search
;               (CX) = length
;
;       EXIT    'NC' - non * or ? not found
;                   (BL) = 1 if meta found
;               'CY' - non * or ? found
;                   (AL) = found char
;
;       USES    AX, BX, flags
;
Procedure SearchForChar,NEAR,LOCAL
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

        push    si
        push    cx
        xor     bx,bx       ; (BX) = metas seen
        jcxz    sfcx        ; carry clear
sfc10:                      ; while (number searched < number to search) {
        lodsb               ;   (AL) = char
        or      al,al       ;
        jz      sfcx        ;   carry clear
        cmp     al,'*'      ;   if ((char == '*') ||
        je      sfc20
        cmp     al,'?'      ;       (char == '?') ||
        jne     sfc30
sfc20:  or      bx,1        ;     remember meta
        loop    sfc10       ; }

sfcx:   pop     cx
        pop     si
        ret

sfc30:  stc
        jmp short sfcx

EndProc SearchForChar

;***    CopyOneComponent - copy until max chars or '.' or '\0' is found
;
;       ENTRY   (CX) = maximum number of chars in component
;               (DS:SI) -> component
;               (ES:DI) -> destination buffer
;
;       EXIT    (AL) = last char copied (lead byte if DBCS)
;               (DI) -> one past end of component
;               (SI) -> one past end of source
;               (BH) = 1 if metas found
;                      0 if no metas
;
;       USES    AX, BX, CX, DX, DI, SI, flags
;
;       PSEUDOCODE
;           numchars = 0
;           last char = NULL
;           while (numchars < max)
;              get char
;              inc ptr
;              if (char == '*')
;                 if (lastchar != '*')
;                    copy '*'
;              else if ((char != '.') && (char != '\0')) {
;                 numchars += 1
;                 copy char
;                 if (DBCS(char))
;                    if (numchars < max) {
;                       numchars += 1
;                       copy byte
;                    }
;                    else
;                       dec component and dest ptrs
;              }
;              else {
;                 copy char
;                 return(char)
;              }
;              lastchar = char
;           }
;           return(char)
;
Procedure CopyOneComponent,NEAR,LOCAL
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

        xor     dx,dx       ; numchars = 0
        xor     bx,bx       ; last char = NULL, no metas found
coc10:  cmp     dx,cx       ; while (numchars < MAX)
        jae     coc40
coc12:  lodsb
        cmp     al,'*'      ;   if (char == '*')
        jne     coc20
        mov     bh,1
        cmp     bl,'*'      ;      if (lastchar != '*')
        je      coc12       ;
        stosb               ;         copy '*'
        jmp short coc50
coc20:  cmp     al,'.'      ;   else if ((char != '.') &&
        je      coc40       ;
        or      al,al       ;            (char != '\0')) {
        jz      coc40
        inc     dx          ;      numchars++
        call    UCase       ;      uppercase it
        stosb               ;      copy char
        cmp     al,'?'      ;      if (char == '?')
        jne     coc25
        mov     bh,1        ;         remember meta
        jmp short coc50     ;         can't be DBCS
coc25:  TestDBCS coc50      ;      if (DBCS(char))
        jnz     coc50       ;
        cmp     dx,cx       ;         if (numchars < max) {
        jae     coc30
        inc     dx          ;            numchars++
        movsb               ;            copy byte }
        jmp short coc50     ;         else {
coc30:  dec     di          ;            dest--
        dec     si          ;            char--
                            ;         }
coc50:  mov     bl,al       ; lastchar = char
        jmp short coc10     ; }

                            ;   }
coc40:  cmp     byte ptr es:[di-1],' '
                            ; is the previous char  blank ?
        jne coc60           ; trailing blanks shd be removed
        dec     di          ;
        jmp     coc40       ;

coc60:  cmp     dx,cx       ; if max no of chars copied, no need to store (AL)
        jae     coc70       ; once more
        stosb               ;   { copy char (either '.' or '\0'
                            ;      return(char)
coc70:  ret

EndProc CopyOneComponent

;***    PackName - convert file name from FCB/DIR-ENTRY to ASCIZ
;
;       NOTE    This routine correctly handles DBCS characters because
;               Blank, Dot and Null aren't legal trailing bytes.
;
;       ENTRY   DS:SI points to an 11 character FCB or dir entry name
;               ES:DI points to a destination area (13 bytes)
;
;       EXIT    Above pointers advanced; DI will be after NULL byte
;
;       USES    SI,DI,CX,AX
;

Procedure PackName,hybrid

        ASSUME  DS:NOTHING,ES:NOTHING

        MOV     CX,8                    ; Pack the name
    REP MOVSB                           ; Move all of it
main_kill_tail:
        CMP     BYTE PTR ES:[DI-1]," "
        JNZ     find_check_dot
        DEC     DI                      ; Back up over trailing space
        INC     CX
        CMP     CX,8
        JB      main_kill_tail
find_check_dot:
        CMP     WORD PTR [SI],(" " SHL 8) OR " "
        JNZ     got_ext                 ; Some chars in extension
        CMP     BYTE PTR [SI+2]," "
        JZ      find_done               ; No extension
got_ext:
        MOV     AL,"."
        STOSB
        MOV     CX,3
    REP MOVSB
ext_kill_tail:
        CMP     BYTE PTR ES:[DI-1]," "
        JNZ     find_done
        DEC     DI                      ; Back up over trailing space
        JMP     ext_kill_tail
find_done:
        XOR     AX,AX
        STOSB                           ; NUL terminate
        ret
EndProc PackName

;***    UnpackName - convert name from asciiz to dir entry form
;
;       This routine does not enforce 8.3 or uppercase.  This routine
;       accounts for DBCS because '.' and '\0' are not valid trailing bytes.
;
;       ENTRY       (DS:SI) -> asciiz string
;                   (ES:DI) -> location for dir entry
;
;       EXIT        (ES:DI) -> end of dir entry
;
;       USES        AX, CX, DI, SI, flags
;
;       PSEUDOCODE
;          count=8;
;          while ((*string != '.') && (*string != 0)) {
;             *entry++ = *string++;
;             count--;
;          }
;          while (count) {
;             *entry = ' ';
;             count--;
;          }
;          count = 3;
;          while (*string != 0) {
;             *entry++ = *string++;
;             count--;
;          }
;          while (count) {
;             *entry = ' ';
;             count--;
;          }
;
Procedure UnpackName,HYBRID
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

; Process name

        mov     cx,8        ; (CX) = number of chars in name
un1:    lodsb               ; (AL) = *string
        or      al,al       ; while (!end of string) &&
        jz      un2         ;
        cmp     al,'.'      ;    ((AL) != '.')
        je      un2
        stosb               ;    copy letter
        loop    un1
        inc     si          ; point past '.'
un2:    mov     al,' '      ; fill in with blanks
        rep     stosb
        mov     cx,3
        cmp     byte ptr [si-1],0 ; if (!end of string)
        je      un4
un3:    lodsb               ; while (!end of string)
        or      al,al
        jz      un4
        stosb               ;    copy letter
        loop    un3
un4:    mov     al,' '      ; fill in with blanks
        rep     stosb
        ret

EndProc UnpackName

;***    MetaMatch - determine if two strings match, one with wildcards
;
;       WARNING: This routine is case sensitive.
;
;       NOTE:   The order of tests is essential.  The test for '*' must
;               be before the test for '\0' in order to match the strings:
;               "foo*" and "foo".   The test for '\0' must be before the
;               test for (*exp == *string) because '\0's matching would
;               cause the comparison to continue beyond the end of the
;               strings.  The test for '?' before (*exp == *string) is just
;               for efficiency in manipulating the string and expression
;               pointers.
;
;       ENTRY   (DS:SI) -> string
;               (ES:DI) -> expression
;               BX      ->string length
;               CX      -> expression length
;
;       EXIT    NC if string and expression match
;               CY if no match
;                       es:di -> the character that didn't match in expression
;                       ds:si -> one char after the char that didn't match
;                                    in the given string.
;
;       USES    AX,CX,DX,DI,SI,Flags
;
;       PSEUDOCODE
;       while (1) {
;           if (*exp == '*') {
;              exp++
;              exp_index = exp;
;              string_index = string;
;           }
;           else if (*string == '\0')
;              if (*exp == ?)
;                 exp++;
;              else if (*exp != '\0')
;                 return(NO_MATCH);
;              else
;                 return(MATCH);
;           else if (*exp == '?')
;              exp++;
;              if (*string != '.') {
;                 string++;
;                 if (DBCS(*(string-1)))
;                    string++;
;              }
;           else if (*exp == *string) {
;              string++;
;              exp++;
;              if ((DBCS(*string-1))
;                 if (*exp == *string) {
;                    exp++;
;                    string++;
;                 }
;                 else
;                    chars don't match so goto label
;           }
;           else
;      label:  if (no '*'s seen)
;                 return(NO_MATCH)
;              else {
;                 exp = exp_index;
;                 string = string_index;
;                 string++;
;                 if (DBCS(*string-1))
;                    string++;
;                 string_index = string;
;           }
;
;
Procedure MetaMatch,hybrid
    ASSUME DS:NOTHING, ES:NOTHING, SS:TASKAREA

LocalVar        mm_exprLen,WORD         ; expression length
LocalVar        mm_laststrcount,WORD    ;
LocalVar        mm_lastexprcount,WORD   ;


        EnterProc       <cx>            ;

; optimize metamatch by checking for exp= "*" or "*.*" and returning match
; if true.

        cmp     byte ptr es:[di],'*'    ; does exp begin with "*"?
        jne     mm0                     ; no, go do regular match
        cmp     byte ptr es:[di+1],0    ; exp = "*"?
        je      mmx                     ; yes, go return match
        cmp     word ptr es:[di+1],"*." ; exp = "*.*"?
        jne     mm0                     ; no, go do regular match
        cmp     byte ptr es:[di+3],0
        je      mmx                     ; yes, go return match

mm0:    xor     cx,cx                   ; (CX) -> char after last *
;
;   In order to ensure that the first char is processed properly, we have
;   to translate 5->e5 in both source and destination.  The second byte of a
;   possible DBCS pair will thus be interpreted properly.
;   (NOTE: it is not translated back).
;
        cmp     byte ptr es:[di],5
        jne     mm0a
        mov     byte ptr es:[di],0e5h
mm0a:   cmp     byte ptr ds:[si],5
        jne     mm1
        mov     byte ptr ds:[si],0e5h
;
;   (DS:SI) -> string
;   (ES:DI) -> expression
;   (CX)  -> character after last * (exp_index)
;   (DX) -> character after last character matched to * (string_index)
;
;   If we have a '*', point to next char in expression and save the
;   pointer in exp_index.  Save a pointer to the character after last char
;   matched to the *.  In this case, it's the current string pointer, since
;   we first try to match the '*' to {}.
;
mm1:    cmp     mm_exprLen,0            ;
        je      mm7                     ;
        mov     ah,byte ptr es:[di]     ; (AH) = *exp
        cmp     ah,'*'                  ; if (*exp == '*') {
        jne     mm2                     ;
        inc     di                      ;   exp++;
        dec     mm_exprLen              ;
        mov     cx,mm_exprLen           ;
        mov     mm_lastexprcount,cx     ;
        mov     cx,di                   ;   exp_index = exp;
        mov     dx,si                   ;   string_index = string;
        mov     mm_laststrcount,bx      ;
        jmp short mm1                   ; }
;
;   Else if end of string, if we're at the end of the expression, we have a
;   match.  Otherwise, we don't.
;
;   (AH) = *exp
;   (es:di) -> exp
;   (ds:si) -> string
;
mm2:    cmp     bx,0            ;
        ja      mm3                     ;
                                        ; else if (*string == '\0')
        cmp     ah,'?'                  ;   if (*exp == '?')
        jne     mm2_5
        inc     di                      ;      exp++
        dec     mm_exprLen              ;
        jmp short mm1

mm2_5:  or      ah,ah                   ; else (*exp != '\0')
        jz      mmx                     ;    return (NO_MATCH)
                                        ; else Return(MATCH)
mmerr:  stc
mmx:    LeaveProc                       ;
        ret
;
mm7:    cmp     bx,0            ;
        jle     mmx                     ; string and expression matched.
; string has not ended ; but expression has ended;
        xor     ah,ah                   ; ah = null to indicate end of expr.
;   Else if we have a '?', increment both pointers, accounting for DBCS.
;   (AH) = *exp
;   (es:di) -> exp
;   (ds:si) -> string+1
;
mm3:    lodsb                           ; (AL) = *string
        dec     bx                      ;
        cmp     ah,'?'                  ; else if (*exp == '?') {
        jne     mm4
        inc     di                      ;    exp++;
        dec     mm_exprLen              ;
        cmp     al,'.'                  ;    if (*string != '.') {
;        je      mm3_5                                                     ;111754
        je      mm3_3                                                      ;111754
        TestDBCS mm1                    ;       if (DBCS(AL))
        jnz     mm1
        inc     si                      ;          string++;
        dec     bx                      ;
        jmp short mm1                   ;    }
;                                                                          ;111754
; We need to be able to match a '..' entry.  So we check if there are at   ;111754
; least 2 consecutive '?' entries. If so, then we let the first '.' match. ;111754
; The second '.' will match as it always did.                              ;111754
;                                                                          ;111754
mm3_3:  cmp     byte ptr es:[di],'?'    ;    else if( *exp    == '?'  &&   ;111754
        jnz     mm3_5                                                      ;111754
        or      bx,bx                   ;             *string != '\0' &&   ;111754
        jz      mm3_5                   ;                                  ;111754
        cmp     byte ptr ds:[si],'.'    ;              string == ".." )  { ;111754
        jnz     mm3_5                   ;                                  ;111754
        mov     ah,'.'                  ;          *(exp-1) = '.';         ;111754
        dec     di                      ;          exp--;                  ;111754
        inc     mm_exprLen              ;                                  ;111754
        jmp short mm4                   ;          goto mm4;               ;111754
                                        ;    }                             ;111754
mm3_5:  dec     si                      ;    else
        inc     bx                      ;
        jmp     mm1                   ;       counteract lodsb             ;111754
;        jmp short mm1                   ;       counteract lodsb          ;111754

;
;   Else if string and expression match, increment both pointers, accounting
;   for DBCS.
;   (AH) = *exp
;   (AL) = *string
;   (es:di) -> exp
;   (ds:si) -> string+1
;
mm4:    cmp     ah,al                   ; else if (*exp == *string) {
        jne     mm5
        TestDBCS mm4_1                  ;    if (DBCS(AL))
        jnz     mm4_1                   ;
        inc     di                      ;    exp++;
        dec     mm_exprLen              ;
        lodsb                           ;       (AL)  = *string++
        dec     bx                      ;
        cmp     byte ptr es:[di],al     ;       if (*exp == (AL))
        jne     mm5                     ;
mm4_1:  inc     di                      ;           exp++
        dec     mm_exprLen              ;
        jmp     mm1                     ; }
;
;   Else we have a matching failure.  If we haven't seen any '*'s, return no
;   match.  Otherwise, set expression ptr to the character after the last '*'
;   and the string ptr to two characters after the last character matched to
;   a '*' and save the new pointer.  In effect, this means matching one more
;   character to the '*'.
;
                                        ; else {
mm5:                                    ;     if (no '*'s seen)
        jcxz    mmerr                   ;        return (NO_MATCH)
        mov     di,mm_lastexprcount     ;
        mov     mm_exprLen,di           ;
        mov     di,cx                   ;     else { exp = exp_index;
        mov     si,dx                   ;        string = string_index
        mov     bx,mm_laststrcount      ;
        lodsb                           ;        (AL) = *string++
        dec     bx                      ;
        TestDBCS mm6                    ;        if (DBCS(AL))
        jnz     mm6                     ;
        inc     si                      ;           string++
        dec     bx                      ;
mm6:    mov     dx,si                   ;        string_index = string
        mov     mm_laststrcount,bx      ;
        jmp mm1                         ;     }
EndProc MetaMatch


HIGH2CODE    ENDS
        END
