┌──────────────────────────────┐
│ │
│ ЧИТАТЕЛЬ - ЧИТАТЕЛЮ │
│ │
└──────────────────────────────┘
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.
* * *