Здесь приведён исходный текст процедуры определения кодировки текста,
написанной на ассемблере Z80. Процедура проходит по тексту, пока в
;---------------------------------------
;Процедура DEF_CODE определяет кодировку
;текста.
;
;Вход: HL - адрес начала текста,
; BC - длина (>0).
;
;Выход: A=0 - ALT,
; 1 - WIN,
; 2 - KOI.
DEF_CODE EXX
;Заполняем BUF_1-BUF_3:
LD HL,BUF_1
LD DE,BUF_1+1
LD BC,0+(128*3)-1
LD (HL),#FF
LDIR
; В альтернативных регистрах хранятся:
; H - BAD_1', L - ALL_1
; D - BAD_2'
; B - BAD_3', C - ALL_3
; E - константа #FF, она пригодится в
; дальнейшем, при коррекции данных.
;
;ALL_2 не подсчитывается, т.к.
;ALL_2=ALL_3.
;Сейчас BC=0 после LDIR!
DEC B ;BC=#FF00
LD H,B
LD L,C ;HL=#FF00
LD D,B ;D=#FF
LD E,B ;E=#FF
EXX
;HL - адрес начала текста, BC - длина.
;Получаем в BC количество сочетаний
;символов - на 1 меньше длины текста:
DEC BC
;Если 0 - сразу выходим, считая, что
;кодировка - ALT:
LD A,B
OR C
RET Z ;A=0
;Главный цикл:
DC_1 PUSH BC ;Счётчик.
;Обрабатываем текущее сочетание для
;каждого из трёх вариантов:
LD DE,RET_
LD BC,BUF_1
CALL WORK_2S
EXX
JR Z,DC_2
DEC H
DC_2 ADD A,L
LD L,A
EXX
LD DE,WIN2ALT
LD BC,BUF_2
CALL WORK_2S
JR Z,DC_3
EXX
DEC D
EXX
DC_3 LD DE,KOI2ALT
LD BC,BUF_3
CALL WORK_2S
POP BC
EXX
JR Z,DC_4
DEC B
DC_4 ADD A,C
LD C,A
;Проверка: может быть, в одном из
;вариантов уже набралось 128 сочетаний?
OR L ;A=C!
RLA
JR C,DC_9
;Иначе - проходим по тексту дальше, пока
;он не кончится:
EXX
INC HL
DEC BC
LD A,B
OR C
JR NZ,DC_1
;Данные собраны. Теперь, если
;в каком-либо из вариантов
;недопустимых сочетаний не больше 1/32
;от общего их числа, то считаем, что
;в этом варианте их и не было:
EXX
DC_9 LD A,L
RLCA
RLCA
RLCA
AND 7 ;ALL/32
INC A ;ALL/32+1
CPL ;255-ALL/32-1
CP H ;255-ALL/32-1 CP 255-BAD
JR NC,DC_5
LD H,E ;ALL/32+1 > BAD
;(т.е. ALL/32 >= BAD)
DC_5 LD A,C
RLCA
RLCA
RLCA
AND 7
INC A
CPL
CP B
JR NC,DC_6
LD B,E
DC_6 CP D
JR NC,DC_7
LD D,E
;По таблице определяем кодировку:
DC_7 LD E,C ;ALL_2=ALL_3.
XOR A ;A:=0, флаг C сбрасывается.
SBC HL,DE
RLA ;Флаг C сбрасывается.
ADD HL,DE
SBC HL,BC
RLA ;Флаг C сбрасывается.
EX DE,HL
SBC HL,BC
RLA
LD HL,CODE_TAB
LD D,0
LD E,A
ADD HL,DE
LD A,(HL)
RET
CODE_TAB DB 0,0,#FF,2,1,#FF,1,2
;---------------------------------------
;Процедура WORK_2S - обработка одного
;сочетания.
;
;Вход: HL - адрес сочетания,
; DE - адрес п/п перекодировки.
; BC - адрес буфера для соотв.
; варианта.
;
;Выход: A=0 - это не сочетание русских
; букв, или это сочетание
; уже встречалось раньше.
; A=1 - это сочетание русских букв.
;
;Флаг Z сброшен, если сочетание является
;недопустимым, иначе установлен. (Если на
;выходе A=0, то флаг Z установлен.)
;
;HL не меняется.
WORK_2S PUSH HL
LD (ADR_SUB),DE
LD (ADR_BUF),BC
CALL GET_WORK ;Первый символ
JR C,NO_L ;не буква!
LD B,A
INC HL
CALL GET_WORK ;Второй символ
JR C,NO_L ;не буква!
LD C,A
;Оба символа - буквы.
LD D,0
LD A,B
ADD A,A
ADD A,A
LD E,A
LD A,C
RRA
RRA
RRA
AND 3
ADD A,E
LD E,A
LD A,C
CPL
AND 7
ADD A,A
ADD A,A
ADD A,A
OR #46
LD (C_BIT_1),A
LD (C_BIT_2),A
ADD A,#40
LD (C_RES),A
;DE - смещение в таблице BUF.
;Проверяем: если соответствующий бит в
;таблице равен 0, значит, это сочетание
;уже встречалось раньше. Тогда выходим,
;игнорируя его (как если бы это были не
;буквы).
LD HL,0 ;Адрес буфера.
ADR_BUF EQU $-2
ADD HL,DE
BIT 0,(HL)
C_BIT_1 EQU $-1
RES 0,(HL) ;Сразу обнуляем.
C_RES EQU $-1
JR Z,NO_L
;Такое сочетание ещё не встречалось.
;Проверяем, допустимо оно или нет:
LD HL,TABLE_2S
ADD HL,DE
BIT 0,(HL)
C_BIT_2 EQU $-1
LD A,1
POP HL
RET
;Выход, если в сочетании - не буквы, или
;если такое сочетание уже встречалось.
NO_L XOR A ;A=0, Z=1.
POP HL
RET_ RET
;---------------------------------------
;Процедура GET_WORK берёт код символа,
;перекодирует его и, если это буква,
;преобразует к диапазону 0..31.
;
;Вход: HL - адрес символа.
;
;Выход: если это буква, в A - её поряд-
; ковый номер (0..31), и флаг C
; сброшен. Иначе флаг C=1.
GET_WORK LD A,(HL)
CP #80
CALL 0 ;Перекодировали.
ADR_SUB EQU $-2
RET C ;Не буква.
;Получаем порядковый номер, если буква:
CP #A0
JR NC,GET_W1
SUB #80
RET
GET_W1 CP #B0
JR NC,GET_W2
SUB #A0
RET
GET_W2 CP #E0
RET C ;Не буква.
CP #F0
CCF
RET C ;Не буква.
SUB #D0
RET
;---------------------------------------
;Процедура WIN2ALT перекодирует символ
;текста, код которого #80..#FF, из
;кодировки WIN в ALT, если этот символ
;является русской буквой. Тогда флаг C
;сброшен, иначе установлен.
WIN2ALT CP #C0
RET C ;Не буква.
SUB #10
CP #E0
RET NC
SUB #30
RET
;---------------------------------------
;Процедура KOI2ALT перекодирует символ
;текста, код которого #80..#FF, из
;кодировки KOI в ALT, если этот символ
;является русской буквой. Тогда флаг C
;сброшен, иначе установлен.
KOI2ALT CP #C0
RET C
PUSH HL
ADD A,TAB_K_A-#C0\256
LD L,A
LD A,TAB_K_A-#C0/256
ADC A,0
LD H,A
;Если таблица TAB_K_A не пересекает
;границу сегментов, то предыдущие три
;команды можно заменить на
;LD H,TAB_K_A/256.
LD A,(HL)
POP HL
RET
TAB_K_A DB "юабцдефгхийклмно"
DB "пярстужвьызшэщчъ"
DB "ЮАБЦДЕФГХИЙКЛМНО"
DB "ПЯРСТУЖВЬЫЗШЭЩЧЪ"
;---------------------------------------
;Таблица сочетаний хранится в
;инвертированном виде - это упрощает
;программу.
TABLE_2S
db #00,#00,#00,#38,#01,#41,#08,#04
db #02,#40,#08,#06,#03,#41,#0E,#7F
db #00,#00,#08,#44,#00,#00,#00,#30
db #21,#40,#2E,#F7,#00,#40,#0E,#40
db #00,#00,#00,#38,#E2,#C0,#80,#7E
db #58,#49,#0D,#7D,#00,#00,#8A,#24
db #03,#40,#28,#62,#00,#51,#04,#20
db #00,#00,#00,#38,#7B,#48,#0C,#60
db #00,#00,#00,#24,#00,#40,#00,#00
db #02,#40,#00,#00,#00,#00,#18,#38
db #7B,#61,#0F,#ED,#43,#40,#0F,#7B
db #5B,#45,#EF,#EF,#5B,#41,#47,#77
db #53,#40,#08,#F5,#7B,#79,#6F,#F7
db #FB,#FF,#FF,#FC,#80,#02,#08,#3E
db #82,#51,#90,#34,#EA,#C2,#03,#FF
db #80,#82,#18,#3D,#80,#02,#08,#3C
;---------------------------------------
;В BUF_1-BUF_3 хранится информация
;о том, какие сочетания букв уже
;встречались в каждом из трёх вариантов.
BUF_1 DS 128
BUF_2 DS 128
BUF_3 DS 128
В январе 2004 года я написал оптимизированный вариант вышеприведённой
процедуры. Если раньше эта процедура во время работы использовала три
;---------------------------------------------------------------
;Процедура DEF_CODE определяет кодировку текста.
;
;Вход: HL - адрес начала текста,
; BC - длина (>0).
;
;Выход: A - номер кодировки: 0 - ALT, 1 - WIN, 2 - KOI.
;TAB_KOI - таблица для получения порядкового номера буквы
;для кодировки KOI.
TAB_KOI DB 30,0,1,22,4,5,20,3,21,8,9,10,11,12,13,14
DB 15,31,16,17,18,19,6,2,28,27,7,24,29,25,23,26
DEF_CODE EXX
;Заполняем BUF_1-BUF_2 значением #FF:
LD HL,BUF_1
LD DE,BUF_1+1
LD BC,#FF
LD (HL),C
LDIR
;Если BUF_1 начинается с начала сегмента (т.е. младший байт
;адреса равен 0), то предыдущие пять команд и следующие две
;команды (DEC B и LD L,C) могут быть заменены на следующий
;фрагмент (экономия при этом составит 4 байта):
;
; LD HL,BUF_1
; LD B,#FF
;M1 LD (HL),B
; INC L
; JR NZ,M1
; LD C,L ;После этого BC=#FF00, L=0.
; В альтернативных регистрах хранятся:
; H - BAD_1', L - ALL_1,
; D - BAD_2',
; B - BAD_3', C - ALL_2 (=ALL_3),
; E - константа #FF, она пригодится в дальнейшем,
; при коррекции данных.
;Сейчас BC=0 после LDIR!
DEC B
LD L,C
;BC=#FF00, L=0.
LD H,B ;HL=#FF00
LD D,B ;D=#FF
LD E,B ;E=#FF
;Итак, BAD_1'=#FF, BAD_2'=#FF, BAD_3'=#FF, ALL_1=0, ALL_2=0.
EXX
;Главный цикл.
MAIN_LOOP DEC BC ;Уменьшаем счётчик.
LD A,B
OR C ;Весь текст обработан?
JR Z,CALC ;Если да - к вычислению результата.
LD D,(HL) ;Код первого символа.
INC HL
LD E,(HL) ;Код второго символа.
;Если код хотя бы одного символа меньше #80 (т.е. старший разряд
;равен 0) - значит, это не русская буква ни в одной из трёх
;кодировок, тогда переходим к началу цикла.
LD A,D
AND E
JP P,MAIN_LOOP
PUSH BC ;Сохранили счётчик.
PUSH HL ;Сохранили адрес следующего сочетания.
LD B,0 ;Значение будет использовано далее.
;Если два старших разряда обоих кодов равны 1 (то, что старшие
;разряды обоих кодов равны 1, мы уже установили) - значит, коды
;находятся в диапазоне #C0-#FF и надо сначала проверить варианты
;WIN и KOI. Иначе сразу переходим к проверке варианта ALT.
ADD A,A
JP P,V_ALT
;Вариант WIN.
;Младшие 5 битов кодов букв - это и есть порядковые номера букв.
LD HL,BUF_2+#100 ;HL больше на #100, т.к. H должно
;быть на 1 больше фактического.
CALL WORK_2S
EXX
JR Z,DC_3
DEC D ;BAD_2'=BAD_2'-1.
DC_3 AND A ;Сочетание встречалось раньше?
JR Z,V_ALT_0 ;Если да, то оно встречалось и
;в варианте KOI, так что сразу
;переходим к варианту ALT.
INC C ;ALL_2=ALL_2+1.
EXX
;Вариант KOI.
;В DE сейчас коды символов сочетания.
;Уже известно, что сочетание раньше не встречалось.
PUSH DE ;Сохранили коды символов сочетания.
LD A,E ;Код второй буквы.
AND 31
IF TAB_KOI/256-((TAB_KOI+31)/256) ;============================
;Таблица TAB_KOI пересекает границу сегментов.
LD C,A
LD HL,TAB_KOI
ADD HL,BC ;Используется, что B=0.
LD E,(HL) ;Получили номер второй буквы.
LD A,D ;Код первой буквы.
AND 31 ;После этого флаг C сброшен.
SBC HL,BC ;После этого HL=TAB_KOI.
LD C,A
ADD HL,BC ;Используется, что B=0.
ELSE ;=========================================================
;Таблица TAB_KOI не пересекает границу сегментов.
LD H,TAB_KOI/256
ADD A,TAB_KOI\256
LD L,A
LD E,(HL) ;Получили номер второй буквы.
LD A,D ;Код первой буквы.
AND 31
ADD A,TAB_KOI\256
LD L,A
ENDIF ;========================================================
LD A,(HL) ;Получили номер первой буквы.
LD H,1 ;Признак "без проверки", т.к. знаем,
;что сочетание ещё не встречалось.
CALL WORK_2S_2
POP DE ;Восстановили коды символов сочетания.
JR Z,V_ALT
EXX
DEC B ;BAD_3'=BAD_3'-1.
V_ALT_0 EXX
;Вариант ALT.
;Проверяем, являются ли коды символов текущего сочетания кодами
;русских букв в кодировке ALT, и если да, то получаем в младших
;пяти битах A и E порядковые номера соответственно первой и
;второй буквы.
V_ALT LD HL,#B030 ;Константы #B0 и #30.
LD A,E ;Код второго символа.
SUB H
JR C,OK ;Переход, если символ - буква.
CP L
JR C,NO_ALT ;Переход, если символ - не буква.
CP #40
JR NC,NO_ALT ;Переход, если символ - не буква.
LD E,A
OK LD A,D ;Код первого символа.
CP H
JR C,OK2 ;Переход, если символ - буква.
SUB H
CP L
JR C,NO_ALT ;Переход, если символ - не буква.
CP #40
JR NC,NO_ALT ;Переход, если символ - не буква.
OK2 LD HL,BUF_1+#100 ;HL больше на #100, т.к. H должно
;быть на 1 больше фактического.
CALL WORK_2S_1
EXX
JR Z,DC_2
DEC H ;BAD_1'=BAD_1'-1.
DC_2 ADD A,L ;Если надо, увеличиваем ALL_1 на 1.
LD L,A
EXX
NO_ALT POP HL ;Восстановили адрес следующего сочетания.
POP BC ;Восстановили счётчик.
;Проверка: может быть, в одном из вариантов уже набралось 128
;сочетаний (тогда старший бит ALL_1 или ALL_2 равен 1).
EXX
LD A,L
OR C ;После этого A=(ALL_1 or ALL_2).
EXX
JP P,MAIN_LOOP ;Нет, не набралось.
;Данные собраны. Теперь, если в каком-либо из вариантов
;недопустимых сочетаний не больше 1/32 от общего их числа,
;то считаем, что в этом варианте их и не было.
CALC EXX
LD A,L ;ALL_1
RLCA
RLCA
RLCA
AND 7 ;ALL_1/32
INC A ;ALL_1/32+1
CPL ;255-ALL_1/32-1
CP H ;255-ALL_1/32-1 CP BAD_1' (BAD_1'=255-BAD_1)
JR NC,DC_5
;255-ALL_1/32-1 < 255-BAD_1
; -ALL_1/32-1 < -BAD_1
; ALL_1/32+1 > BAD_1
; ALL_1/32 >= BAD_1
; BAD_1 <= ALL_1/32
LD H,E ;BAD_1'=255 -> BAD_1=0.
DC_5 LD A,C ;ALL_2 (=ALL_3)
RLCA
RLCA
RLCA
AND 7 ;ALL_2/32
INC A ;ALL_2/32+1
CPL ;255-ALL_2/32-1
CP B ;255-ALL_2/32-1 CP BAD_3' (BAD_3'=255-BAD_3)
JR NC,DC_6
;BAD_3 <= ALL_2/32 (не забываем, что ALL_2=ALL_3).
LD B,E ;BAD_3'=255 -> BAD_3=0.
DC_6 CP D ;255-ALL_2/32-1 CP BAD_2' (BAD_2'=255-BAD_2)
JR NC,DC_7
LD D,E ;BAD_2'=255 -> BAD_2=0.
DC_7 LD E,C
;Сейчас HL=a, DE=b, BC=c.
;Определяем кодировку (без таблицы).
XOR A ;A:=0, флаг C сбрасывается.
SBC HL,DE
RLA
ADD HL,DE
SBC HL,BC
RLA
;Если сейчас A=0 - значит, a>=b и a>=c, следовательно,
;кодировка - ALT. Номер этой кодировки как раз равен 0,
;так что можно сразу выйти из процедуры.
AND A ;Флаг C сбрасывается.
RET Z
;Теперь точно известно, что кодировка не ALT.
EX DE,HL
SBC HL,BC
;Если сейчас флаг C сброшен, то b>=c, следовательно,
;кодировка - WIN (её номер равен 1), иначе - KOI (2).
LD A,1
ADC A,0
RET
;---------------------------------------------------------------
;WORK_2S - обработка сочетания русских букв.
;
;Вход: B=0,
; D - в младших пяти битах порядковый номер первой буквы,
; старшие биты не определены,
; E - в младших пяти битах порядковый номер второй буквы,
; старшие биты не определены.
; Если надо проверять, встречалось ли раньше данное
; сочетание, то в HL указывается адрес 128-байтного массива
; с информацией о том, какие сочетания в рассматриваемом
; варианте уже встречались, причём H увеличено на 1.
; Указанный массив не может располагаться по адресу,
; старший байт которого равен 0 (на ZX Spectrum там всё
; равно находится ПЗУ). Если не надо проверять, встречалось
; ли раньше данное сочетание, то H=1.
;
;Выход: если сочетание уже встречалось, то A=0 и флаг Z
; установлен. Иначе A=1, и при этом, если сочетание
; является недопустимым, то флаг Z сброшен, иначе -
; установлен.
;
;Точка входа WORK_2S_1 используется, когда в аккумуляторе уже
;содержится значение, содержащее в младших пяти битах номер
;буквы первого символа сочетания (содержимое регистра D тогда
;не используется), а точка входа WORK_2S_2 - когда, кроме того,
;значение аккумулятора уже находится в диапазоне 0-31.
WORK_2S LD A,D
WORK_2S_1 AND 31
WORK_2S_2 ADD A,A
ADD A,A
LD C,A
LD A,E
RRA
RRA
RRA
AND 3
ADD A,C
LD C,A
LD A,E
AND 7
ADD A,A
ADD A,A
ADD A,A
OR #46
LD (C_BIT_2),A
;BC - смещение в массиве (не забываем, B=0!).
;Надо проверять, встречалось ли раньше данное сочетание?
DEC H
JR Z,NO_CHECK
;Да, надо.
LD (C_BIT_1),A
ADD A,#40
LD (C_RES),A
;Проверяем: если соответствующий бит массива равен 0, то
;сочетание уже встречалось.
ADD HL,BC
BIT 0,(HL)
C_BIT_1 EQU $-1
;Если сочетание уже встречалось, выходим.
LD A,B ;A=0
RET Z ;Z=1
;Сочетание ещё не встречалось. Помечаем, что оно встретилось.
RES 0,(HL)
C_RES EQU $-1
;Проверяем, допустимо ли это сочетание.
NO_CHECK LD HL,TABLE_2S
ADD HL,BC
;Если TABLE_2S начинается с адреса, кратного 256, то предыдущие
;две команды можно заменить на LD H,TABLE_2S/256: LD L,C.
BIT 0,(HL)
C_BIT_2 EQU $-1
LD A,1
RET
;---------------------------------------------------------------
;Таблица сочетаний (чтобы упростить программу, она хранится
;в инвертированном виде и с зеркально отражёнными значениями
;байтов).
TABLE_2S DB #00,#00,#00,#1C,#80,#82,#10,#20
DB #40,#02,#10,#60,#C0,#82,#70,#FE
DB #00,#00,#10,#22,#00,#00,#00,#0C
DB #84,#02,#74,#EF,#00,#02,#70,#02
DB #00,#00,#00,#1C,#47,#03,#01,#7E
DB #1A,#92,#B0,#BE,#00,#00,#51,#24
DB #C0,#02,#14,#46,#00,#8A,#20,#04
DB #00,#00,#00,#1C,#DE,#12,#30,#06
DB #00,#00,#00,#24,#00,#02,#00,#00
DB #40,#02,#00,#00,#00,#00,#18,#1C
DB #DE,#86,#F0,#B7,#C2,#02,#F0,#DE
DB #DA,#A2,#F7,#F7,#DA,#82,#E2,#EE
DB #CA,#02,#10,#AF,#DE,#9E,#F6,#EF
DB #DF,#FF,#FF,#3F,#01,#40,#10,#7C
DB #41,#8A,#09,#2C,#57,#43,#C0,#FF
DB #01,#41,#18,#BC,#01,#40,#10,#3C
;---------------------------------------------------------------
;В массивах BUF_1 и BUF_2 хранится информация о том, какие
;сочетания букв уже встречались соответственно в вариантах ALT
;и WIN. Эти массивы должны располагаться подряд и именно в таком
;порядке - это используется при их инициализации!
BUF_1 DS 128
BUF_2 DS 128