ZX Club #06
31 декабря 1997

Soft group - Драйвер ввода в режимах последовательного и прямого доступа из файлов системы TR-DOS. Как использовать драйвер.

<b>Soft group</b> - Драйвер ввода в режимах последовательного и прямого доступа из файлов системы TR-DOS. Как использовать
 драйвер.
Борис Курицын,
ассистент кафедры вычислительной техники
Черкасского инженерно-технологического
института, аспирант
────────────────────────────────────────
257010 Черкассы, а/я 1529


         ДРАЙВЕР ВВОДА В РЕЖИМАХ
   ПОСЛЕДОВАТЕЛЬНОГО И ПРЯМОГО ДОСТУПА
         ИЗ ФАЙЛОВ СИСТЕМЫ TR-DOS


                СОДЕРЖАНИЕ

Введение..................................
Вызов системных функций TR-DOS............
Обработка исключений......................
Драйвер потокового ввода/вывода
в/из файлов TR-DOS........................
Использование драйвера из Assembler.......
Интерфейс Basic...........................
Литература................................


     Автор написал этот  драйвер  (вернее,
его первую часть) как базовый элемент ком-
пилятора языка высокого уровня.  Но он яв-
ляется совершенно  автономной  программой,
которую напрямую  могут  использовать  те,
кто пишет на ассемблере, а через  разрабо-
танный расширитель Бейсика --  и  те,  кто
программирует на Бейсике.

     Кроме того, анализ ее познакомит  чи-
тателя с программированием драйверов,  ра-
ботой каналов и потоков, принципами  напи-
сания расширителей Бейсика, основами  син-
таксического анализа, с методами  расшире-
ния множества ошибок Бейсика.

     В чем цель написания этого  драйвера?
Есть класс программ,  которые  одновремено
должны работать с большим количеством фай-
лов (и по вводу, и по выводу) и  сами  при
этом занимают  достаточный  объем  памяти,
так что  об  одновременной  загрузке  всех
требуемых файлов не  может  быть  и  речи.
Открывая системный поток на  каждый  файл,
они могут работать через этот драйвер даже
с 10 файлами длиной, скажем, по 60 КБ  од-
новременно, при расходе памяти  около  290
байт на файл. По функциям драйвер близок к
операторам TR-DOS  для  работы  с  файлами
прямого/последовательного доступа, но  ра-
ботает с CODE-файлам и с аналогичными фай-
лами с нестандартными типами.

     Кроме того, как  показал  "отец  всех
программистов" Никлаус Вирт (автор  языков
Паскаль, Модула-2), программа тогда  прос-
та, корректна и надежна,  когда  СТРУКТУРА
ПРОГРАММЫ СООТВЕТСТВУЕТ СТРУКТУРЕ  ДАННЫХ.
Чтобы  реализовать  этот  принцип,   нужны
соответствующие  средства.  Одним  из  них
есть этот драйвер, поскольку он  позволяет
программе работать только с  "абстрактным"
аспектом данных, т. е.  в  терминах  "сим-
вол", "позиция считывания".

     Сначала о технических моментах. Автор
предпочитает ассемблер ZX Turbo  Assembler
v2.4 и листинг  приведен  в  его  формате.
Надеюсь, у пользователей других  ассембле-
ров не возникнет проблем с переносом  тек-
стов программ.

     Автор, при  написании  программ,  ис-
пользует два вспомогательных модуля:

         модуль доступа к TR-DOS
и
    модуль перехвата системных ошибок.

     Модуль TR-DOS очень прост. Это аналог
системного вызова  #3D13,  который  ошибки
TR-DOS преобразует  в  исключения  (вызовы
RST 8) с кодами 100-112, что избавляет  от
необходимости  каждый  раз  проверять  код
ошибки системы.  Кроме того, сам вызов де-
лается через вектор (dosVector), что  поз-
воляет "навешивать" на вызов TR-DOS допол-
нительные функции прямо  во  время  работы
программы заменой  значения  dosVector  на
адрес своего дополнительного обработчика.


; ┌────────────────────────────────────┐
; │Вызов системных функций TR-DOS v5.0x│
; │                                    │
; │ (c) Б. Курицын, июнь 1996          │
; │                                    │
; │ Файл: dosapi.a                     │
; │ Формат файла: ZX ТASM              │
; └────────────────────────────────────┘


; исключение "ошибка TR-DOS"
excDosError equ 99

; ошибки TR-DOS
dNoFile     equ 1
dFileExists equ 2
dNoSpace    equ 3
dDirFull    equ 4
dRecOF      equ 5
dNoDisk     equ 6
dStrmOpened equ 10
dNotDskFile equ 11
dVfyError   equ 13

dos     db #c3 ; это jp (dosVector)
dosVector
        dw dosIntr

; вызов интерпретатора функций DOS
dosIntr push af
        ld a,c
        ld (_fncNo),a
        xor a
        ld (23823),a
        ld (23824),a
        pop af
        call #3d13
        push af
        ld a,(_fncNo)
        сp #a ;если исполнялась функция 10
              ;(поиск файла),
        jr z,dosOk ; код возврата - это не
                   ; код ошибки
        ld a,(23823)
        or a
        jr z,dosOk ;если была ошибка, ини-
                   ;циировать исключение
; инициация исключения DOS
dosError
        add a,excDosError
        ld (_errNo),a
        rst 8
_errNo  db 0
_fncNo  db 0
dosOk   pop af
        ret

     Интересующиеся обработкой  исключений
могут обратиться к статье  [1].  Здесь  же
скажу кратко: исключения - это чрезвычайно
удобная технология программирования, кото-
рая позволяет осуществлять неявные перехо-
ды к обработке ошибок  или  исключительных
ситуаций без необходимости постоянных про-
верок флагов, кодов возврата  и  т.п.  При
этом обеспечивается  сохранение  состояния
некоторых элементов  системы  и  программы
(как минимум, стека). Приведенный ниже мо-
дуль поддерживает многоуровненую обработку
исключений  с  возможностью  динамического
изменения множества сохраняемых  элементов
и обработчика  исключений.  Интересующиеся
могут просто проанализировать текст  прог-
раммы. Кратко же можно сказать так: если в
начале какой-либо  программы  вызвать  try
(см. листинг)  с  указанием  адреса  Вашей
подпрограммы обработки ошибок, то, в  слу-
чае вызова RST 8 где-либо внутри  програм-
мы, управление передастся на Ваш  обработ-
чик.  Обработчик  должен первым делом выз-
вать endTry  для  восстановления  стека  и
снятия себя же.  Тот же  вызов  должен де-
латься перед командой RET программы.

;     ┌────────────────────────────┐
;     │        Exceptions handle   │
;     │        Обработка исключений│
;     │(c) 1994-96 Б. Курицын      │
;     │версия:  2.0 от 18/06/96    │
;     │файл:    except.a           │
;     └────────────────────────────┘

err_SP    equ 23613

;           Внешние обращения
try       db #C3 ;это JP (tryAd)
tryAd     dw mark

endTry    db #C3
eTryAd    dw unmark

;Продолжить текущее исключение
;(вызов только из  обработчика  исключения
;командой JP)

;новое исключение, код в A
putErr    ld (iy),a

;то же исключение
contErr   ld sp,(err_SP)
          ret

;Изменить маркировку стека
;вход: @HL-новый обработчик Try, @DE-новый
;обработчик EndTry
;выход:@HL-старый обработчик Try, @DE-ста-
;рый обработчик EndTry

chMarking ld bc,(tryAd)
          ld (tryAd),hl
          ld h,b
          ld l,c
          ld bc,(eTryAd)
          ld (eTryAd),de
          ld d,b
          ld e,c
          ret

;               РЕАЛИЗАЦИЯ
;Маркировать  стек,  вход:  @DE-обработчик
;исключений
;разрушаются: HL,IX

mark   POP IX
       LD HL,(err_SP)
       PUSH HL
       PUSH DE
       LD (err_SP),SP
       JP (IX)

;Демаркировать стек, разрушаются: HL,IX

unmark POP IX
       LD SP,(err_SP)
       POP HL
       POP HL
       LD (err_SP),HL
       JP (IX)

     Необходимо оговориться, что  те,  кто
не знает, что такое "канал" и "поток", пе-
ред продолжением чтения этой статьи должны
почитать главу по каналам и потокам в зна-
менитом трехтомнике Инфоркома по  програм-
мированию для ZX Spectrum или приложение к
инструкции пользователя по текстовому  ре-
дактору BK Write,  распостраняемому  авто-
ром.

     А теперь о самом драйвере.  Какой на-
бор функций он должен  выполнять?

     Во-первых, это открытие файла: созда-
ние канала, связанного с файлом и  подклю-
чение какого-либо потока к  этому  каналу.

     Во-вторых,  драйвер  дожен  позволять
считывать байты файла не только последова-
тельно, но и переставлять указатель считы-
вания сразу  на  любую  позицию  в  файле.

     В-третьих, это закрытие файла: разру-
шение канала, закрытие потока.

     А как же, собственно,  ввод  информа-
ции, спросите Вы?

     А очень просто - системной процедурой
WaitKey (адрес #15D4).

     Далее мы рассмотрим  комментированный
текст  драйвера  (примечание:  в  драйвере
есть моменты, связанные с выводом в файл -
эта часть драйвера еще не закончена, кроме
того, включить ее в  статью  не  представ-
ляется возможным из-за большого объема).

     Сразу же обратимся  к  основной  идее
драйвера: нужно выдать байт файла,  смеще-
ние которого в файле определяется  текущей
позицией.  Позиция может быть  произвольно
изменена в любой  момент.  При  считывании
байта указатель  позиции  перемещается  на
следующий  байт.  При   попытке    считать
что-либо  за  концом  файла   генерируется
соответствующее исключение.

     Для реализации этого в ракорде канала
предусмотрено  место  для  одного  сектора
файла.  В любой момент в этот буфер загру-
жен сектор, соответствующий текущей  пози-
ции в файле.  При любом изменении  позиции
номер сектора контролируется, и, если  на-
до, сектор перегружается.
; ****************************************
; *                                      *
; *  DISK STREAMABLE I/O DRIVER v1.05    *
; *                                      *
; *  Драйвер потокового ввода/вывода     *
; *  в/из файлов TR-DOS.                 *
; *                                      *
; *  (c) Б. Курицын, июнь 1996           *
; *                                      *
; *  Файл: stream.a                      *
; *  Формат файла: ZX Тurbo Assembler    *
; ****************************************

            include "dosapi.a"

; длина сектора
sectLen   equ 256

; системные переменные
chans     equ 23631 ; указатель на область
                    ; каналов
curChl    equ 23633 ; указатель на текущий
                    ; канал
prog      equ 23635 ; указатель на прог-
;рамму (за конец области каналов)

; системные переменные TR-DOS
dfDrive   equ #5d19 ;дисковод по умолчанию
searchCh  equ 23814 ;кол-во символов для
                    ;поиска имени

; системные прoцедуры,
; подробности относительно процедур
;смотри в [2]
makeRoom       equ #1655
reclaim2       equ #19E8
strData        equ #171E+3
chOpen         equ #1601
callJp         equ #162C

; Канал ввода и вывода имеет имя "F",
;чтобы их можно было  различать,
;устанавливаются сигнатуры ракордов:
readSign       equ #5678
writeSign      equ #6789

; генерация исключений
invStream
        rst 8
        db  23 ; "неверно используется
               ; поток"
invFileName
        rst 8
        db 14 ; "неправильное имя файла"
invDevice
        rst 8
        db 18 ; "неправильное устройство"
endOfFile
        rst 8
        db 7 ; "конец файла"

; смещения в ракорде;  относительно
; аналогов  структуры  ракорда смотри [3]
oPrint  equ 0 ; адрес процедуры вывода
oInput  equ 2 ; адрес процедуры ввода
oName   equ 4 ; имя канала
oSign   equ 5 ; сигнатура канала
oClose  equ 7 ; адрес процедуры закрытия
              ; файла
oLength equ 9 ; длина ракорда
oIntern equ 11 ; дополнительная информация

; образ ракорда канала для чтения
readRecord
        DW invStream
        DW inputIn
        DB "F"
        DW readSign, noOp
        DW 30+sectLen; длина ракорда
        DB 0; устройство
            ; (смещение oIntern+0)
        DB "        C"
        DS 7; элемент каталога (...+1)
        DW #FFFF; позиция в файле (...+17)
;здесь: DS sectLen - буфер сектора
;файла (...+19)
rdRecLen equ $-readRecord+sectLen

; Процедура ввода символа из файла;
; очередной байт файла  возвращается в A.
; Байт возвращается всегда, поэтому
; состояния INKEY$#5 = "" не бывает.
inputIn ld ix,(curChl) ; адрес ракорда
        ld l,(ix+oIntern+17) ; текущая
                         ; позиция в файле
        ld h,(ix+oIntern+18)
        ld c,l
        ld b,0
        add ix,bc
        ld a,(ix+oIntern+19) ; выборка
; байта из текущего сектора
        scf ; флаг: байт получен
            ; с устройства
        push af
        inc hl
        call _seekRead ; переход на
                       ; следующую позицию
        pop af
noOp    ret

;Позиционирование в текущем файле
;для чтения
;вход: HL-позиция
;---внешняя версия; проверяет, что текущий
; канал -- F и он открыт для чтения, если
; нет - неверный поток)
seekRead
        push hl
        call testChl
        ld l,(ix+oSign)
        ld h,(ix+oSign+1)
        ld bc,readSign
        and a
        sbc hl,bc
        jp nz,invStream
        pop hl
;---внутренняя версия
_seekRead
        ld ix,(curChl)
;проверить, не достигнут ли конец файла
        ld c,l
        ld b,h
        ld e,(ix+oIntern+12) ;сравнить с
                             ;длиной файла
        ld d,(ix+oIntern+13)
        and a
        sbc hl,de
;позиция точно за последним байтом файла,
;буфер не перегружается
        jr z,atEOF
        jp nc,endOfFile
;установить новую позицию
        ld a,b
        cp (ix+oIntern+18) ; в этом же
                           ; секторе?
atEOF   ld (ix+oIntern+17),c
        ld (ix+oIntern+18),b
        ret z ; да - заканчиваем
;нет - новая позиция соответствует байту
;из другого сектора файла, загрузить новый
;сектор в буфер
        ld l,(ix+oIntern+15)
        ld h,(ix+oIntern+16) ; позиция
                   ; начала файла на диске
        call makeLog ; преобразование
                     ; в логический сектор
        ld e,b
        ld d,0
        add hl,de ;это логический номер
                  ;сектора,
                  ;который нужно загрузить
        call makePhis ;обратное
                      ;преобразование
        ex de,hl
        push ix
        pop hl
        ld bc,oIntern+19
        add hl,bc
        push hl
        push de
        ld a,(ix+oIntern)
        ld c,1
        call dos ; выбор дисковода
        pop de
        pop hl
        ld bc,#105 ; загрузка сектора
        jp dos

; Преобразовать параметры
; "сектор L - дорожка  H"
; в  "логический сектор HL"
; ЗАМЕЧАНИЕ: преобразование
; в "логический сектор" позволяет легко
; рассчитывать положение сектора на диске,
; так как логические сектора можно
; складывать и отнимать как обычные числа.
; Формула преобразования:
; лог. сектор = дорожка * 16 + сектор
makeLog xor a
        srl h
        rr a
        srl h
        rr a
        srl h
        rr a
        srl h
        rr a
        or l
        ld l,a
        ret

; Преобразовать параметры
; "логический сектор HL"
; в  "сектор L - дорожка H"
makePhis
        ld a,l
        sla a
        rl h
        sla a
        rl h
        sla a
        rl h
        sla a
        rl h
        ld a,#F
        and l
        ld l,a
        ret

; Тип файла для открытия; изменяя это
; значение перед вызовом openRead
; можно открывать нестандартные файлы
fileType db "C"

; Открытие файла для чтения;
; вход:
; A-номер потока, @DE/BC- строка имени
; (как обычно: адрес DE, длина BC)
; Указанный поток будет открыт на файл.
openRead
        exx
        ld (stream),a ; проверяем, что
                  ; указанный поток закрыт
        call strData
        ld a,b
        or c
        ld a,dStrmOpened ; если нет -
                         ; ошибка DOS
                      ; "поток уже открыт"
        jp nz,dosError
        call newRdRecord ; создаем ракорд
                         ; канала
        ld (record),hl
        push hl
        exx
        pop hl
        call parseName ; синтаксический
                       ; разбор имени
        ld hl,(record)
        inc hl
        ld bc,(chans) ; находим смещение
                    ; ракорда в (Chans)...
        and a
        sbc hl,bc
        push hl
        ld a,(stream)
        call strData
        pop bc
        ld (hl),c ; ...и открываем
            ; затребованный поток на канал
        inc hl
        ld (hl),b
        ld ix,(record)
        ld a,(ix+oIntern)
        ld c,1
        call dos ; выбор дисковода
        ld c,#18
        call dos ; настройка на дискету
        ld hl,(record)
        ld bc,oIntern+1
        add hl,bc
        push hl
        ld c,#13
        call dos
        ld hl,searchCh
        ld (hl),9
        ld c,#a
        call dos ; поиск файла, который
                 ; открываем
        bit 7,c
        jr nz,noFile ; если файла нет -
; все вернуть в исходное состояние
        ld a,c
        ld c,8
        call dos ; если файл есть,
; получаем его элемент каталога
        pop hl
        ld c,#14
        call dos ; и копируем его в ракорд
                 ; канала
        ld a,(stream)
        call chOpen
        ld hl,0
        jp _seekRead ; устанавливаем
; позицию на начало файла
; при отсутствии файла все возвращается в
; начальное состояние
noFile  ld ix,(record)
        call freeRecord ; удаляем ракорд
                        ; канала
        ld a,(stream)
        call strData
        ld (hl),0 ; закрываем поток
        inc hl
        ld (hl),0
        ld a,dNoFile ; выдаем ошибку DOS
                     ; "нет такого файла"
        jp dosError
stream  db 0
record  dw 0

;Синтаксический анализ имени файла
;в виде "[дисковод:]имя".  Если дисковод
;не указан, берется дисковод по умолчанию.
;Имя и дисковод переносятся в область
;oIntern ракорда.
;вход: HL-адрес ракорда открываемого
;канала, @DE/BC- строка имени
parseName
        push hl
        pop ix
        ld a,b ; пустая строка имени -
               ; ошибка
        or c
        jp z,invFileName
        ld a,(dfDrive) ; пока что дисковод
                       ; по умолчанию
        ld (ix+oIntern),a
        ld a,(fileType) ; устанавливаем
                        ; тип файла
        ld (ix+oIntern+9),a
        ld hl,-3 ; имя длинее
                 ; трех символов?
        and a
        adc hl,bc
        jp m,nameOnly ; нет - значит,
; дисковод в имени точно не указан
        inc de ; проверяем относительно
               ; дисковода...
        ld a,(de)
        dec de
        cp ":" ; если второй символ -
               ; двоеточие...
        jr nz,nameOnly
        ld a,(de) ;...то первый - дисковод
        res 5,a ; приводим к верхнему
                ; регистру
        sub "A" ; отделяем правильные
                ; имена: A,B,C,D.
        jp c,invDevice
        cp 4
        jp nc,invDevice
        ld (ix+oIntern),a ; дисковод
; найден и установлен в ракорде
        inc de ; смещаемся
               ; к имени... (+2 символа)
        inc de
        dec bc
        dec bc
nameOnly
        ld a,b
        or a
        jp nz,invFileName
        ld a,c ; если имя длинее
               ; 8 символов - ошибка
        cp 9
        jp nc,invFileName
        ld b,c ; если все в порядке -
               ; копируем имя в ракорд
copyName
        ld a,(de)
        inc de
        ld (ix+oIntern+1),a
        inc ix
        djnz copyName
        ret

; Создание ракорда канала чтения
; выход: HL-адрес созданного ракорда
newRdRecord
      ld hl,(prog) ; в конце области
                   ; Chans...
      dec hl
      ld bc,rdRecLen ; ...выделяем память
                     ; для ракорда...
      call makeRoom
      inc hl
      push hl
      ex de,hl
      ld hl,readRecord ;...и копируем
                       ;туда образ ракорда
      ld bc,rdRecLen-sectLen
      ldir
      pop hl
      ret

; Удаление ракорда канала чтения/записи
; вход: IX-адрес ракорда
freeRecord
       ld c,(ix+oLength) ; из ракорда
                   ; получаем его длину...
       ld b,(ix+oLength+1)
       ld (recLen),bc
       push ix
       pop hl
       jp reclaim2 ;...и освобождаем
                   ;область, им занимаемую
recLen dw 0

; Проверить, что текущий канал -
; это канал "F"
; выход: IX=(curChl)
testChl
      ld ix,(curChl) ; если имя текущего
                     ; канала...
      ld a,"F"
      cp (ix+oName) ; ...не "F"...
      ld a,dNotDskFile
      jp nz,dosError ; ...то ошибка DOS
                     ; "не дисковый файл"
      ret

; Закрыть файл чтения/записи потока A
close push af
      call chOpen ; делаем канал текущим
      call testChl ; проверим, что этот
            ; поток связан с нашим каналом
; выполнить процедуру закрытия канала
      ld l,(ix+oClose)
      ld h,(ix+oClose+1)
      call callJp ; переход к исполнению
;освободить память
      ld ix,(curChl)
      call freeRecord ; удаляем текущий
                      ; канал из памяти
;закрыть поток
      pop af
      call strData
      ld (hl),0 ; значение 0 - поток закрыт
      inc hl
      ld (hl),0
; Скорректировать переменные  Streams  для
; потоков 0-15.
; Дело в том, что после открытия этого ка-
; нала могли быть открыты и другие. Их ра-
; корды расположены в памяти после  нашего
; ракорда.
; С удалением нашего  ракорда  смещения  в
; области Streams на указанные каналы ста-
; ли недействительными, и их нужно скор-
; ректировать на значение длины удаленного
; ракорда.
      ld d,b ; смещение на уже удаленный
             ; ракорд
      ld e,c
      ld b,16 ; просматриваем 16 потоков
strmLoop
      push bc
      ld a,b
      dec a
      call strData ; выбираем значение для
                   ; потока
      push hl
      pop ix
      ld h,d
      ld l,e
      and a
      sbc hl,bc ; если его смещение
                ; не больше нашего...
      jr nc,noCorrt ; ...корректировка
                    ; не требуется
      ld h,b
      ld l,c
      ld bc,(recLen) ; иначе уменьшаем
               ; смещение на длину ракорда
      and a
      sbc hl,bc
      ld (ix),l ; и сохраняем
      ld (ix+1),h
noCorrt
      pop bc
      djnz strmLoop ;цикл для всех потоков
      ret

     Итак, драйвер написан.  А как его ис-
пользовать? Элементарно. Сначала какой-ли-
бо свободный поток (скажем, #5)  открываем
на файл:

          org 60000
          jp start
          include "stream.a" ; включение
                             ; драйвера...
          include "except.a" ; ...и обра-
                        ; ботки исключений

name      db "textfile" ;определение имени
                        ;файла...
nameLen   equ $-name ; ...и его длины

start     ld de, name
          ld bc, nameLen
          ld a, 5
          call openRead

     Файл открыт.  Теперь, например, напе-
чатаем его на  экране.  Будем  посимвольно
считывать файл и передавать  байты  каналу
"S". Поскольку процесс закончится исключе-
нием "конец файла", перехватываем исключе-
ния:

          ld de, endOfPrint ; адрес
                            ; обработчика
          call try

     Теперь можно выводить файл в  "беско-
нечном" цикле.

waitKey   equ #15D4
process   ld a, 5 ; поток файла
          call chOpen ; chOpen = #1601
          call waitKey ; вводим символ
          push af
          ld a,2 ; поток экрана
          call chOpen
          pop af
          rst 16 ; выводим символ
          jr process

     По окончании файла или по другому ис-
ключению, управление будет передано сюда:

endOfPrint
          call endTry ; снимаем обработчик
          ld a, 7
          cp (iy) ; текущая ошибка -
                  ; это "конец файла"?
          jp nz, contErr ; если нет -
; продолжаем ее распостранение, если да -
; все в порядке, вывод закончен
          ld a, 5
          call close ; закрываем
                     ; дисковый файл
          ret

    А как же с Бейсиком? Хотелось бы иметь
возможность открывать файлы прямо операто-
рами Бейсика.

    Нет  ничего  проще.  Приведенный  ниже
интерфейс Бейсика к драйверу реализует это
и еще кое-что: он преобразует новые ДОСов-
ские исключения в  новые  ошибки  Бейсика,
которые выводятся так же, как  и  обычные.
Кроме того, он выводит статус потоков сис-
темы на экран или в  любой  другой  поток.
Новые операторы Бейсика выглядят так:

 LET d=64000
 REM открытие <потока> на файл <имя_файла>
 RANDOMIZE USR d: OPEN #поток, имя_файла$

 REM закрытие дискового файла, связанного
     с <потоком>
 RANDOMIZE USR d: CLOSE #поток

 REM переход к <позиции> в файле,
     связанном с <потоком>
 PRINT #поток; : RANDOMIZE USR d
 GO TO позиция

 REM вывод информации
     о состоянии потоков в <поток>
 REM для вывода на экран поток = 2
 RANDOMIZE USR d: LIST #поток

    Небольшое замечание  относительно опе-
ратора GO TO: оператор PRINT,  который  Вы
видите впереди, только делает поток  теку-
щим, так как GO TO работает с текущим  по-
током.  Обратите внимание на ";"  в  конце
оператора PRINT.

    Оператор LIST выводит  список всех от-
крытых потоков с указанием каналов, на ко-
торые они открыты.

    Непосредственно ввод из файла осущест-
вляется обычным образом: оператором

 INPUT #поток; симв_переменная$

для ввода строки символов, или оператором

 LET симв_переменная$ = INKEY$ #поток

для ввода одного символа.

    Второй  способ  предпочтительнее,  так
как в системе ZX Spectrum есть такая  осо-
бенность: предполагается,  что  оператором
INPUT данные вводятся только  из  потоков,
связанных с клавиатурой, и звук клавиатуры
обрабатывается в операторе INPUT.  Поэтому
при вводе из файла Вы будете слышать  пос-
ледовательности писков "нажатия на  клави-
шу" при вводе каждого символа. Кроме того,
ввод строки завершится только по получении
символа с кодом клавиши  <Enter>  (#0D)  и
сам этот символ в строку включен не будет.

    В тексте программы хорошо  видно,  как
расширитель Бейсика  не  только  выполняет
действия, но и анализирует синтасис  прог-
раммы.

;*****************************************
;*                                       *
;*  BASIC INTERFACE TO                   *
;*  DISK STREAMABLE I/O DRIVER, v1.05    *
;*                                       *
;*  Интерфейс Бейсика к                  *
;*  Драйверу потокового ввода/вывода     *
;*  в/из файлов TR-DOS.                  *
;*                                       *
;*  (c) Б. Курицын, июнь 1996            *
;*                                       *
;*  Файл: basint.a                       *
;*  Формат файла: ZX Тurbo Assembler     *
;*****************************************

; коды токенов
t_open     equ #d3
t_close    equ #d4
t_goto     equ #ec
t_list     equ #f0

; системные переменные (или смещения)
oFlags     equ 1
oTVFlag    equ 2
oFlags2    equ 48
oFlagX     equ 55
oXPtrHi    equ 37
defAdd     equ 23563
strms_6    equ 23574

; Используемые п/п ПЗУ.
; Подробнее смотрите в [2].
class06    equ #1C82 ; синтакс. анализатор
                     ; - должно быть
                     ; числовое выражение
class0A    equ #1C8C ; синтакс. анализатор
                     ; - должна быть
                     ; строка символов
separator  equ #1B6F ; синтакс. анализатор
                     ; - должна быть
                     ; лексема
stkToA     equ #1E94 ; стек калькулятора -
                     ; в A
stkToBC    equ #1E99 ; стек калькулятора -
                     ; в BC
stkFetch   equ #2BF1 ; стек калькулятора -
                     ; в A/BC/DE
clsLower   equ #0D6E
setMin     equ #16B0
copyBuff   equ #0ECD
stackA     equ #2D28 ; A - на стек
                     ; калькулятора
printFP    equ #2DE3 ; напечатать число со
                     ; стека калькулятора
po_mess    equ #0C0A ; вывести сообщение A
                     ; из таблицы (DE)

           org 64000
; точка входа
startUp    ld de,newErrors ; новый
                  ; обработчик иссключений
           call try
           ld c,":" ; это разделитель
                    ; после USR 64000
; Separator проверяет, что текущий  интер-
; претируенмый символ именно таков.
; Если нет - ошибка "нонсенс в Бейсике"
           call separator
           ld c,a
           rst #20 ;берем имя оператора...
           ld hl,statms ; ...и ищем его
                        ; в таблице Statms
           jr compare
nextSearch inc hl
           inc hl
compare    ld a,(hl)
           inc hl
           or a
           jr nz,cont_comp ; если таблица
                      ; кончилась - ошибка
           rst 8
           db #b; Nonsense in Basic
cont_comp  cp c
           jr nz,nextSearch
           ld a,(hl) ; найдено
; выбираем адрес обработчика...
           inc hl
           ld h,(hl)
           ld l,a
           call callJp ;...и запускаем его
           call endTry ;снимаем обработчик
                       ; исключений
           ret

; Таблица описания операторов.
; Формат: токен, адрес обработчика,
; токен, адрес обработчика,...,0.
statms     db t_open
           dw _open
           db t_close
           dw _close
           db t_goto
           dw _goto
           db t_list
           dw _list
           db 0

listStrm   db 0
; Тексты для опреатора LIST
listMess   db #80,"Streams status:",13+#80
           db ": opened to ",34+#80
           db 34,13+#80

; Оператор LIST #stream
_list      ld c,"#" ; проверка символа
                    ; потока
           call separator
           call class06 ; должно следовать
                      ; числовое выражение
           call stkToA ; его -
                       ; в аккумулятор
           call chOpen ; открываем канал
                       ; для вывода
           ld de,listMess
           sub a
           ld (listStrm),a
           call po_mess ; вывод заголовка
listNext   ld a,(listStrm) ; для потока...
           call strData ;... выбираются
                        ;данные из Streams
           ld a,b
           or c
           jr z,listCont ; если закрыт -
                         ; следующий поток
           ld a,"#" ; печать символа
                    ; потока...
           push bc
           rst 16
           ld a,(listStrm)
           call stackA
           call printFP ; ... и его номера
           pop bc
           ld hl,(chans)
           add hl,bc
           inc hl
           inc hl
           inc hl ;определение адреса
                  ;смещения oName в канале
           push hl
           ld a,1
           ld de,listMess
           call po_mess ;сообщение
                        ;"открыт на...
           pop hl
           ld a,(hl)
           rst 16 ; ...такой-то канал"
           ld a,2
           ld de,listMess
           call po_mess
listCont   ld hl,listStrm
           inc (hl) ; следующий поток
           ld a,16
           cp (hl)
           jr nz,listNext ; переход к
                       ; следующему потоку
           ret

;Оператор OPEN #stream, name$ -
;без комментариев
_open      exx
           push hl
           call openParams
           call openRead
           pop hl
           exx
           ret

; синтаксический анализ оператора OPEN
openParams call class06 ;должен быть номер
                        ;потока - на стек
                        ; калькулятора
           ld c,","
           call separator ; потом запятая
           call class0A ; затем символьное
                        ; выражение -
                        ; имя файла
           rst #28 ; обменять имя и поток
                   ; местами на стеке
           db 1,#38; exchange
           call stkToA ; поток -
                       ; в аккумулятор
           push af
           call stkFetch ; имя - в DE/BC
           pop af
           ret

; Оператор CLOSE #stream
_close     call class06 ;должен быть номер
                        ;потока - на стек
                        ; калькулятора
           call stkToA ; поток -
                       ; в аккумулятор
           jp close

; Оператор GO TO pos
_goto      call class06 ;число - позиция
                        ;в файле
           call stkToBC ;со стека
                        ;калькулятора в BC
           ld h,b
           ld l,c
           jp seekRead

; Обработчик ошибок:
; визуализирует новые ошибки
newErrors  call endTry ; снять обработчик
           halt
           ld a,(iy) ; код ошибки
           cp excDosError+dNoFile
           jp m,contErr ; обычные ошибки
                  ; распостраняются дальше
;Эта часть программы практически повторяет
;действия стандартного обработчика  ошибок
;из ПЗУ до вывода сообщения об ошибке.
;Смотрите [3] с адреса #1303
           res 5,(iy+oFlags)
           bit 1,(iy+oFlags2)
           call nz,copyBuff
           ld hl,0
           ld (iy+oFlagX),h
           ld (iy+oXPtrHi),h
           ld (defAdd),hl
           ld hl,1
           ld (strms_6),hl
           call setMin
           res 5,(iy+oFlagX)
           call clsLower
           set 5,(iy+oTVFlag)
           ld a,(iy)
           sub excDosError+dNoFile
           ld b,a
           add a,"a" ;новые коды сообщения
           rst 16
           ld a," " ; пробел
           rst 16
           ld a,b
           ld de,messages ;новые сообщения
                          ;в этой таблице
           ld (iy),#FF
           jp #1346 ; дальнейшая обработка
                    ; стандартная - в ПЗУ

;Таблица текстов сообщений о новых ошибках
;(в формате процедуры PO_MESS)
messages   db #80,"No fil","e"+#80
           db "File exist","s"+#80
           db "Disk ful","l"+#80
           db "Dir ful","l"+#80
           db "RecNo overflo","w"+#80
           db "No dis","k"+#80
           db "(7) DOS erro","r"+#80
           db "(8) DOS erro","r"+#80
           db "(9) DOS erro","r"+#80
           db "Stream opene","d"+#80
           db "Not disk fil","e"+#80
           db "(12) DOS erro","r"+#80
           db "Verify erro","r"+#80

;Включение драйвера и обработки исключений
           include "stream.a"
           include "except.a"
; ***  end of basint.a  ***


     Надеюсь, что мои краткие (из-за боль-
шого объема ассемблерного текста)  коммен-
тарии дали хотя бы общее  представление  о
работе программы, а сама  программа  будет
полезна Вам.

     При возникновении  проблем  или  спе-
циальных вопросов, Вы можете написать  ав-
тору по адресу, указанному выше.


                ЛИТЕРАТУРА

1.  Б. Курицын. Перехват системных  ошибок
при  программировании  на  ассемблере  для
компьютеров "ZX Spectrum".: Радиолюбитель,
10'1994; стр. 12 - Минск, 1994.

2.  Логан, о'Хара. Полное описание ПЗУ  ZX
Spectrum.: "Программ-Асс" - Харьков, 1992.

3.  К.Курылович, Д.Мадей, К.Марасек. Путе-
водитель по ZX Spectrum.: "Программ-Асс" -
Харьков, 1992.



Другие статьи номера:

От редакции - ZX-CLUB развивается и формируется.

Soft group - Драйвер ввода в режимах последовательного и прямого доступа из файлов системы TR-DOS. Как использовать драйвер.

Hard group - ZS Scorpion 2000 - о GMX-контроллере.

Users group - Компрессия экранных файлов: Обзор ПО. Дискография. Анализ результатов компрессии.

Users group - Компрессия кодовых блоков - работа с HRUM v3.5.

News - Барнаульская олимпиада по информатике 1997 года.

News - Барнаульская фирма Komel приняла решение о поддержкеавторских программ.

News - конкурс на лучший вирус продолжается.

Досье - О деятельности барнаульских программистов: Кротов Олег , Маяцкий Виталий , Ростов Александр , Ковалев Роман (DJ RUSH), Командир Нортон (NC).

ZX-Поппури - Письма читателей из Магадана и Коврова , Воронежа и Чебоксар.

Enjoy - Как выйти замуж за программиста.

Фантастика - Повесть А.Питерского "Четырнадцатое измерение".

Toys - Новелла к игре "BISMARK".

Toys - описание к игре "BISMARK".

Toys - словарь к игре "BISMARK".


Темы: Игры, Программное обеспечение, Пресса, Аппаратное обеспечение, Сеть, Демосцена, Люди, Программирование

Похожие статьи:
От авторов - Содержание номера "хромает".
Программистам - Теория: Биты и байты.
Юмор - Приколы.
Реклама - Реклама и объявления ...
Вопрос - почему никто не сказал о глюке в ACEdit?

В этот день...   21 ноября