Борис Курицын, ассистент кафедры вычислительной техники Черкасского инженерно-технологического института, аспирант ──────────────────────────────────────── 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.