ZX Review #11-12
26 ноября 1997

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

<b>Читатель-читателю</b> - Драйвер ввода в режимах последовательного и прямого доступа
 из файлов системы TR-DOS.
┌──────────────────────────────┐
│                              │
│     ЧИТАТЕЛЬ - ЧИТАТЕЛЮ      │
│                              │
└──────────────────────────────┘

Music by ZET and MITCHELL

Борис Курицын,
257010 Черкассы, а/я 1529

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

   Автор  написал  этот  драйвер
(вернее, его первую  часть)  как
базовый элемент компилятора язы-
ка высокого  уровня, разработкой
которого занимается инициативная
группа из Черкасс. Но он являет-
ся  совершенно  автономной прог-
раммой, которую  напрямую  могут
использовать  те, кто  пишет  на
ассемблере, а через  разработан-
ный расширитель Бейсика - и  те,
кто программирует на Бейсике.
   Кроме того, анализ ее  позна-
комит читателя с  программирова-
нием драйверов, работой  каналов
и потоков, принципами  написания
расширителей  Бейсика,  основами
синтаксического анализа, с мето-
дами расширения множества ошибок
Бейсика.
   В чем  цель  написания  этого
драйвера? Есть  класс  программ,
которые одновременно должны  ра-
ботать  с  большим   количеством
файлов (и по вводу, и по выводу)
и сами при этом занимают  доста-
точный объем памяти, так что  об
одновременной загрузке всех тре-
буемых файлов не  может  быть  и
речи. Открывая  системный  поток
на каждый файл, они могут  рабо-
тать через этот драйвер  даже  с
10 файлами длиной, скажем, по 60
Кб одновременно, при расходе па-
мяти около 290 байт на файл.  По
функциям драйвер близок к опера-
торам TR DOS для работы с файла-
ми прямого/последовательного до-
ступа, но работает с CODE-файлом
и с аналогичными файлами  с  не-
стандартными типами.
   Кроме того, как показал "отец
всех программистов" Никлаус Вирт
(автор  языков  Паскаль, Модула-
2), программа тогда проста, кор-
ректна и надежна, когда СТРУКТУ-
РА ПРОГРАММЫ СООТВЕТСТВУЕТ СТРУ-
КТУРЕ ДАННЫХ.
   Чтобы реализовать этот  прин-
цип, нужны соответствующие сред-
ства. Одним из  них  и  является
этот драйвер, поскольку он  поз-
воляет программе работать только
с "абстрактным" аспектом данных,
т.е. в терминах "символ", "пози-
ция считывания".
   Сначала о технических  момен-
тах. Автор  предпочитает  ассем-
блер ZX Turbo Assembler v.2.4, и
листинг приведен в его  формате.
Надеюсь, у пользователей  других
ассемблеров не возникнет проблем
с переносом текстов программ.
   Автор при написании  программ
использует  два  вспомогательных
модуля: модуль доступа к  TR DOS
и  модуль  перехвата   системных
ошибок.
   Модуль  TR DOS  очень  прост.
Это  аналог  системного   вызова
#3D13,  который  ошибки  TR  DOS
преобразует в исключения  (вызо-
вы RST 8) с кодами  100-112, что
избавляет от необходимости  каж-
дый  раз  проверять  код  ошибки
системы. Кроме  того, сам  вызов
делается через  вектор  (dosVec-
tor), что позволяет "навешивать"
на вызов  TR DOS  дополнительные
функции прямо  во  время  работы
программы заменой значения  dos-
Vector на адрес своего  дополни-
тельного обработчика.
140.
; ****************************************
; *                                      *
; * Вызов системных функций TR-DOS v5.0x *
; *                                      *
3; *  (c) Б. Курицын, июнь 1996           *
; *                                      *
; *  Файл: dosapi.a                      *
; *  Формат файла: ZX TASM               *
; *                                      *
; ****************************************

; исключение "ошибка 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)
        cp #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
2
   Интересующиеся обработкой ис-
ключений могут обратиться к ста-
тье  [1].  Здесь  же скажу крат-
ко: исключения - это чрезвычайно
удобная  технология   программи-
рования, которая позволяет  осу-
ществлять неявные переходы к об-
работке ошибок или  исключитель-
ных ситуаций  без  необходимости
постоянных проверок  флагов, ко-
дов возврата  и  т.п.  При  этом
обеспечивается сохранение состо-
яния некоторых элементов системы
и  программы  (как минимум, сте-
ка).  Приведенный  ниже   модуль
поддерживает многоуровневую  об-
работку исключений  с  возможно-
стью   динамического   изменения
множества  сохраняемых элементов
и обработчика исключений.
   Интересующиеся  могут  просто
проанализировать текст  програм-
мы. Кратко же можно сказать так:
если в начале  какой-либо  прог-
раммы вызвать try  (см. листинг)
с указанием  адреса  вашей  под-
программы  обработки ошибок, то,
в случае вызова RST  8  где-либо
внутри программы, управление пе-
редастся на ваш обработчик.  Об-
работчик  должен, первым  делом,
вызвать endTry для  восстановле-
ния стека и снятия себя же.  Тот
же вызов должен  делаться  перед
командой RET программы.
140.
; ****************************************
; *         Exceptions handle            *
; *         Обработка исключений         *
3; * (c) 1994-96 Б. Курицын               *
3; * версия:  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)
2
   Необходимо  оговориться,  что
те, кто не знает, что такое "ка-
нал" и "поток", перед  продолже-
нием чтения этой  статьи  должны
почитать главу по каналам и  по-
токам в  знаменитом  трехтомнике
Инфоркома по программированию  в
машинных кодах для  ZX  Spectrum
или  приложение   к   инструкции
пользователя по текстовому реда-
ктору BK Write, распостраняемому
автором.
   А теперь  о  самом  драйвере.
Какой набор  функций  он  должен
выполнять? Во-первых, это откры-
тие файла: создание канала, свя-
занного с файлом, и  подключение
какого-либо потока к  этому  ка-
налу. Во-вторых, драйвер  должен
позволять считывать байты  файла
не только последовательно, но  и
переставлять указатель  считыва-
ния сразу  на  любую  позицию  в
файле.  В-третьих, это  закрытие
файла:  разрушение  канала, зак-
рытие  потока.  А  как  же, соб-
ственно, ввод  информации, спро-
сите Вы?  А  очень просто - сис-
темной процедурой WaitKey (адрес
#15D4). Далее мы рассмотрим ком-
ментированный   текст   драйвера
(примечание:  в  драйвере   есть
моменты, связанные с  выводом  в
файл - эта часть драйвера еще не
закончена, кроме  того, включить
ее в  статью  не  представляется
возможным  из-за большого  объе-
ма).
   Сразу же обратимся  к  основ-
ной идее драйвера: нужно  выдать
байт файла, смещение которого  в
файле определяется текущей пози-
цией. Позиция может быть  произ-
вольно изменена в любой  момент.
При считывании  байта  указатель
позиции перемещается на  следую-
щий байт.  При  попытке  считать
что-либо за концом файла генери-
руется соответствующее  исключе-
ние.
   Для реализации этого в ракор-
де  канала  предусмотрено  место
для одного сектора  файла. В лю-
бой момент в этот буфер загружен
сектор, соответствующий  текущей
позиции в файле. При любом изме-
нении позиции номер сектора кон-
тролируется, и, если  надо, сек-
тор перегружается.
140.
; ****************************************
; *                                      *
; *  DISK STREAMABLE I/O DRIVER v1.05    *
; *                                      *
; *  Драйвер потокового ввода/вывода     *
; *  в/из файлов TR-DOS.                 *
; *                                      *
3; *  (c) Б. Курицын, июнь 1996           *
; *                                      *
; *  Файл: stream.a                      *
; *  Формат файла: ZX Turbo 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

140.        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
2
   Итак, драйвер написан. А  как
его  использовать?  Элементарно.
Сначала какой-либо свободный по-
ток  (скажем, #5)  открываем  на
файл:
140.
          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
2
   Файл  открыт.  Теперь, напри-
мер, напечатаем его  на  экране.
Будем посимвольно считывать файл
и передавать байты  каналу  "S".
Поскольку процесс закончится ис-
ключением "конец  файла", перех-
ватываем исключения:
140.
          ld de, endOfPrint ; адрес обработчика
          call try
2
   Теперь можно выводить файл  в
"бесконечном" цикле.
140.
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
2
   По  окончании  файла  или  по
другому  исключению   управление
будет передано сюда:
140.
endOfPrint
          call endTry ; снимаем обработчик
          ld a, 7
          cp (iy) ; текущая ошибка - это "конец файла"?
          jp nz, contErr ; если нет - продолжаем ее
                           распостранение
; да - все в порядке, вывод закончен
          ld a, 5
          call close ; закрываем дисковый файл
          ret
2
   А как же с Бейсиком? Хотелось
бы иметь  возможность  открывать
файлы прямо операторами Бейсика.
   Нет ничего  проще.  Приведен-
ный  ниже  интерфейс  Бейсика  к
драйверу  реализует  это  и  еще
кое-что:  он  преобразует  новые
ДОСовские  исключения  в   новые
ошибки Бейсика, которые выводят-
ся так же, как и обычные.  Кроме
того, он выводит статус  потоков
системы на  экран  или  в  любой
другой  поток.  Новые  операторы
Бейсика выглядят так:

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

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

140.
           db 34,13+#80

3; Оператор 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

3;Оператор 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

3; Оператор 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
3           db "(12) DOS erro","r"+#80
           db "Verify erro","r"+#80

; Включение драйвера и обработки исключений
           include "stream.a"
           include "except.a"
; ***  end of basint.a  ***
2
   Ну вот и все. Думаю, для про-
верки интерфейса Вы сами  сможе-
те написать сколько угодно прог-
рамм. Надеюсь, что  мои  краткие
(из-за  большого  объема  ассем-
блерного текста) комментарии да-
ли хотя бы общее представление о
работе программы, а  сама  прог-
рамма будет полезна Вам.
   При возникновении проблем или
специальных вопросов  Вы  можете
написать автору по  адресу, ука-
занному выше.

           ЛИТЕРАТУРА

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

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

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

           *   *   *




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

Авторская разработка - С.Зонов, А.Ларченко. О контроллере SMUC (HDD и IBM периферия).

Компьютерная новелла - Воины Звезд (по игре Shadowfire).

Новые программы - Обзор Digital Studio v1.12, Digital Studio Compiler v1.01

Новые программы - Обзор Xas редактор-ассемблер 128К (v5.05).

Новые программы - Обзор Музыкального редактора Instrument v3.01

Новые программы - Обзор программ FASTzasm и @-zasm.

Новые программы - Обзор программы No Kempston.

Профессиональный подход - Алгоритмы построения и прохождения Лабиринтов.

Смех без причины... - Материалы из юмористического журнала SpectrofUn.

Советы экспертов - Игра FEUD.

Советы экспертов - Игра Killed Until Dead.

Советы экспертов - Игра War in Middle Earth.

Форум - Конверсия цветной спектрумовской картинки на IBM. Конверсия ч/б картинки с IBM на ZX Spectrum.

Форум - О русификации игровых программ.

Форум - Программа детекта эмулятора.

Форум - Процедура "цветные полосы на бордюре". Снижение шума FDD.

Форум - Процедура перевода числа в десятичный вид. Процедура - сканер пароля.

Форум - Снятие защиты Microprotector'а.

Форум - Эмуляторы, которые мы выбираем: 'UKV Spectrum Debugger', 'Z80TRDOS'.

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

Этюды - Графический эффект "плазма 2".

Этюды - Графический эффект "плазма 2".

Этюды - Графический эффект "плазма".

Этюды - Полезные советы. Быстрая переброска экрана.

Этюды - Ремейк процедур 93 года.

Этюды - Эффект "пламя".


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

Похожие статьи:
Пульс сцены - Репортаж с Assembly Party'99 (Финляндия).
Железо - для тех кто захочет подключить модем к Cпектруму.
Реклама - реклама и объявления.
Апперативчик - вступление от авторов.
РАссказ - Послушайте.

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