Subversion Repositories SvarDOS

Rev

Rev 1493 | Blame | Compare with Previous | Last modification | View Log | RSS feed

;
; SvarDOS MORE
;
; Displays output one screen at a time
;
;  - multilingual (looks up the LANG env variable)
;  - TINY! (fits in a single disk sector)
;
; This program is part of the SvarDOS project <http://svardos.org>
;
; ****************************************************************************
; *** Distributed under the terms of the MIT LICENSE *************************
; ****************************************************************************
;
; Copyright (C) 2023 Mateusz Viste
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to
; deal in the Software without restriction, including without limitation the
; rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
; sell copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
; IN THE SOFTWARE.
; ****************************************************************************
;
; To be compiled with the A72 assembler:
; A72 MORE.ASM MORE.COM
;


; COM file always has a 100h origin
ORG     100h


; ****************************************************************************
; * Display a short help screen if any (non-empty) argument is provided.     *
; *                                                                          *
; * detect presence of arguments in the command's tail (PSP 81h), looking    *
; * at the first non-space character. If it's a CR then no argument (CR is   *
; * the tail's terminator).                                                  *
; ****************************************************************************
mov     ax, ds
mov     es, ax
mov     al, ' '
mov     cx, 0ffh
mov     di, 81h
repe    scasb                          ; compare AL with [ES:DI++]
mov     al, [di-1]

cmp     al, 0Dh
je      NO_ARGS

; some arg found: display help and quit
mov     ah, 9h
mov     dx, offset HELP
int     21h
int     20h

HELP db "SvarDOS MORE 2023.0", 13, 10
     db 10
     db "MORE < README.TXT", 13, 10
     db "TYPE README.TXT | MORE", 13, 10, '$'

NO_ARGS:


; ****************************************************************************
; * detect screen dimensions (max usable row and column)                     *
; ****************************************************************************
mov     ah, 0Fh                        ; GET CURRENT VIDEO MODE
int     10h
mov     byte ptr [MAXCOL], ah
mov     ax, 40h
mov     es, ax
mov     ah, [es:84h]                   ; 0040:0084
test    ah, ah                         ; ancient PCes do not know this
jz      SKIP_ROWS_DETECTION
mov     byte ptr [MAXROW], ah

SKIP_ROWS_DETECTION:


; ****************************************************************************
; * preset lang to EN                                                        *
; ****************************************************************************
mov     bp, LANGLIST+2


; ****************************************************************************
; * Scan the environement block looking for the LANG variable                *
; ****************************************************************************

; set ES to point at the environment segment (PSP's offset 2Ch)
mov     es, [2Ch]

; compare DS:[SI] with ES:[DI], up to 5 bytes ("LANG=")
cld
mov     di, 0
CMP_NEXT_VAR:

; if it points at a nul already then it's the end of the env block
mov     al, [es:di]
test    al, al
jz      LANG_DONE

mov     si, LANG
mov     cx, 5
repe    cmpsb

; if CX == 0 then found a LANG= match
jcxz    FOUND_LANG

; otherwise jump to next var (look for nearest nul terminator)
xor     al, al
mov     ch, 0ffh
repne   scasb                          ; compare AL with [ES:DI++]
jmp     short CMP_NEXT_VAR

; FOUND THE LANG VARIABLE (at ES:DI)
FOUND_LANG:

mov     bx, [es:di]                    ; load the LANG ID to BX and make
and     bx, 0DFDFh                     ; sure it is always upper case


; ****************************************************************************
; * Now I have a LANG ID in BX and I have to match it for something I know.  *
; ****************************************************************************

mov     di, LANGLIST
mov     ax, ds
mov     es, ax

NEXTLANG:
cmp     byte ptr [di], 0               ; look for the end of list terminator
je      LANG_DONE
cmp     [di], bx                       ; look for a LANG ID match
je      LANGIDOK

; skip string (look for its $ terminator) and repeat
SKIPLANG:
mov     al, '$'
mov     ch, 0ffh
repne   scasb                          ; compare AL with [ES:DI++]
jmp     short NEXTLANG

LANGIDOK:
mov     bp, di                         ; ptr to localized msg is always in BP
inc     bp
inc     bp


LANG_DONE:


; ****************************************************************************
; * DUPLICATING HANDLES: here I duplicate stdin into a new handle so I can   *
; * safely close original stdin (file handle 0) and then duplicate stderr    *
; * into stdin. The purpose of these shenanigans is to be able to cope with  *
; * situations when stdin is redirected (eg. with CTTY)                      *
; ****************************************************************************

; duplicate stdin and keep the new file handle in FHANDLE
xor     bx, bx
mov     ah, 45h
int     21h
mov     [FHANDLE], ax

; I can close stdin now
mov     ah, 3Eh
int     21h

; duplicate stderr to stdin
; bx = file handle / cx = file handle to become duplicate of bx
mov     ah, 46h
mov     bx, 2
xor     cx, cx
int     21h
; ****************************************************************************


; make sure cursor is on column 0
mov ah, 2h                             ; write character in DL to stdout
mov dl, 0Dh                            ; carriage return
int 21h

; reset BX - from now on bh = cur row and bl = cur col
xor     bx, bx

; consume stdin bytes by loading them into buffer
RELOADBUF:
xchg    di, bx                         ; save BX to DI (contains cur row+col)
mov     ah, 3Fh                        ; DOS 2+ - read from file or device
mov     bx, [FHANDLE]                  ; duplicated stdin was saved in FHANDLE
mov     cx, 1024
mov     dx, offset BUFFER
int     21h

; abort on error
jc EXIT

; restore bx
xchg    bx, di

; did I get any bytes? 0 bytes read means "EOF"
test    ax, ax
jnz     PREPLOOP

EXIT:
int     20h

; prepare the LODSB loop
PREPLOOP:
mov     cx, ax
mov     si, dx

NEXTCHAR:

; AL = DS:[SI++]
cld
lodsb

; EOF char? (check this first so a short jump to exit is still possible)
cmp     al, 1Ah
jz      EXIT

; [7h] BELL?
cmp     al, 7h
je      OUTPUT_CHAR

; [8h] BACKSPACE? moves the cursor back by one column, no effect if it is on
; first column already. it does not blank the characters it passes over.
cmp     al, 8h
jne     NOTBS
test    bl, bl                         ; am I on on column 0? (bl = cur col)
jz      OUTPUT_CHAR
dec     bl
jmp     short OUTPUT_CHAR
NOTBS:

; [9h] TAB?
cmp     al, 9h
jne     NOTTAB
add     bl, 8                          ; bl = cur col
and     bl, 248
jmp     short OUTPUT_CHAR
NOTTAB:

; [0Ah] LF?
cmp     al, 0Ah
jne     NOTLF
inc     bh                             ; bh = cur row
jmp     short OUTPUT_CHAR
NOTLF:

; [0Dh] CR?
cmp     al, 0Dh
jnz     NOTCR
xor     bl, bl                         ; bl = cur col
jmp     short OUTPUT_CHAR
NOTCR:

; otherwise: increment cur column, and then maybe cur row
inc     bl                             ; bl = cur col
cmp     bl, [MAXCOL]
jb      OUTPUT_CHAR
inc     bh                             ; bh = cur row
xor     bl, bl                         ; bl = cur col

OUTPUT_CHAR:
mov     dl, al
mov     ah, 2h
int     21h
cmp     bh, [MAXROW]                   ; bh = cur row
jae     PRESSANYKEY

CHARLOOP:
loop    NEXTCHAR                       ; dec cx and jmp to NEXTCHAR if cx > 0
jmp     RELOADBUF


; display "--- More ---"
PRESSANYKEY:
mov     ah, 9h                         ; disp $-termin. string pointed by DX
mov     dx, offset SEPAR1
int     21h
mov     dx, bp
int     21h
mov     dx, offset SEPAR2
int     21h

; wait for a keypress
mov     ah, 08h                        ; read char from stdin, no echo
int     21h
; read again if an extended key was pressed
test    al, al
jnz     GETKEY_DONE
int     21h
GETKEY_DONE:

; output a CR/LF pair and reset counters
mov     dx, offset CRLF
mov     ah, 9h
int     21h
xor     bx, bx                         ; bh = cur row, bl = cur col
jmp     CHARLOOP


; ****************************************************************************
; * DATA                                                                     *
; ****************************************************************************

MAXROW  db      24                     ; maximum *addressable* row (not total)
MAXCOL  db      80                     ; total available columns

CRLF DB 13, 10, '$'
LANG DB "LANG="
SEPAR1 DB "--- $"
SEPAR2 DB " ---$"

LANGLIST:
DB "EN",  "MORE$"                      ; EN must be 1st to be used as default
; non-EN strings follow
DB "DE",  "WEITER$"
DB "DK",  "MERE$"
DB "ES",  "M", 0B5h, "S$"              ; "MAS" with an A-acute (CP 850)
DB "FI",  "LIS", 8Eh, 8Eh, "$"         ; "LISAA" - AA with diaeresis (CP 850)
DB "FR",  "PLUS$"
DB "HU",  "T", 99h, "BB$"              ; TOBB with O-diaeresis (CP 852)
DB "IT",  "PI", 0EBh, "$"              ; "PIU" with U-acute (CP 850)
DB "LV",  "T", 0A0h, "L", 0A0h, "K$"   ; "TALAK" with A-makron (CP 775)
DB "NL",  "MEER$"
DB "NO",  "MER$"
DB "PL",  "DALEJ$"
DB "RU",  84h,80h,8Bh,85h,85h,'$'      ; "DALEE" (CP 866)
DB "SL",  "VE", 0ACh, "$"              ; "VEC" with C-caron (CP 852)
DB "SV",  "MERA$"
DB "TR",  "DAHA FAZLA$"
DB, 0                                  ; LANGLIST terminator

; uninitialized area (must be defined last)

FHANDLE dw      ?                      ; file handle I read from (DUPed stdin)

BUFFER: