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