Info Guide
#11
05 июля 2015 |
|
Системки - Оберон для ZX Spectrum: Тонкости при разработке на Обероне в среде ZXDev (часть 1).
Оберон для ZX Spectrum Тонкости при разработке на Обероне в среде ZXDev Oleg N. Cher, VEDAsoft Oberon Club Концепция среды XDev (и её подсистемы ZXDev,нацеленной на разработку для Спект─ рума) формировалась в течение нескольких лет, но обрела свою реализацию в виде пер─ вой версии сравнительно недавно ─ в конце января 2015 г. Основные особенности: 1.В качестве входного языка применяется язык Оберон и его надмножества ─ языки Оберон-2 иКомпонентный Паскаль(из после─ днего поддержаны только некоторые фичи). 2.Кодогенерация реализована через тран─ сляцию ОберонавСис последующим вызовом сишного компилятора. В качестве основного используется компилятор SDCC, но путём редактирования сборочных скриптов возможно подключение и других компиляторов, напри─ мер, z88d. 3.Мультитаргетность. В репозитории про─ екта XDev(https://github.com/Oleg-N-Cher/ XDev ) есть подсистемы для таргетов MSX, MS-DOS, Windows (32/64 bit), Linux, нахо─ дящиеся в разной степени готовности. Не публиковались,но также намечены подсистемы для разработки под NES/Nintendo, Java ME и Android. Уже зарелижена первая версия под─ системы ZXDev с набором библиотек в комп─ лекте,хотя и не очень богатым, и примерами простых игр на Обероне ─ https://sourceforge.net/projects/bb-xdev Оберон - это очень компактный модульный язык программирования (компонентный,объек─ тно-ориентированный - в зависимости от ди─ алекта) со структурной парадигмой, строгой типизацией и автоматическим управлением памятью,спроектированный для универсально─ го применения (в том числе системного, где он заодно играет и роль скриптового язы─ ка ─ ОС ETH Oberon, A2/Bluebottle ). Это квинтэссенция творчества классика програм─ мирования доктора Никлауса Вирта, автора Паскаля иМодулы-2. В противовес современ─ ным средствам "основного потока" (main stream) ─ большим языкам, ещё и наращивае─ мым дальнейшим усложнением,Оберон основан на самых ключевых понятиях информатики ─ модуль, процедура, структура данных (за─ пись). Оберон-парадигма так же подвержена фра─ гментации,как и другие области IT. Поэтому и в Оберон-парадигме есть диалекты, напри─ мер,Oberon-07 (сверхминималистичная реви─ зия Оберона-1 ), OberonX (математическое расширение),Active Oberon (многопоточный диалект),Оберон-2 (с расширенными средст─ вами ООП), а также особенно любимый мною Компонентный Паскаль (надмножествоОберо─ на-2 для промышленного применения). Беззнаковые вычисления Язык Оберон не имеет беззнаковых типов данных, это подобно тому,как процессор Z80 не имеет беззнаковых регистров или ячеек памяти. Но их значения могут интерпретиро─ ваться как беззнаковые числа и быть подве─ ргнуты беззнаковым операциям. Это также похоже на языкФорт ─ значе─ ния на стеке не обладают типом - могут трактоваться как знаковые, так и беззнако─ вые (или даже как двойные - занимающие два слова). И хотя большинство стековых опера─ ций знаковые, но наряду со знаковым умно─ жением (* ) есть и беззнаковое умножение (U* ), так же и со сравнением, и с деле─ нием, и т.п. В общем, системного програм─ миста не должно смутить отсутствие беззна─ ковых типов,ему достаточно знать,что ZXDev имеет три типа для целочисленных данных размером в 1,2 и 4 байта. Рассмотрим,каким способом можно определить эффективные без─ знаковые операции в ZXDev. Беззнаковое сравнение байтов. Этот трюк я придумал в процессе работы над портом игры "Дурак", где применяется много знако─ вых сравнений байтов на больше-меньше, ко─ торые можно свободно изменить на беззнако─ вые сравнения, тем самым повысив эффектив─ ность кода. Знаковое сравнение: IF a > b THEN ... Беззнаковое сравнение: IF CHR( a ) > CHR( b ) THEN ... Здесь знаковые значения приводятся к беззнаковому типуCHAR и потом сравнивают─ ся. Сложение и вычитание реализуется одина─ ково для знаковых и беззнаковых.А вот сей─ час попробуем реализовать эффективные (без накладных расходов) операции беззнакового деления и умножения для байтов и слов. По─ добным образом можно реализовывать любые другие операции, отсутствующие в языке (ведь всего не предусмотришь): MODULE UMath; IMPORT SYSTEM, B := Basic; VAR a, b: SHORTINT; PROCEDURE -UMultBytes (a, b: SHORTINT): SHORTINT "( ((CHAR)a) * ((CHAR)b) )"; PROCEDURE -UMultWords (a, b: INTEGER): INTEGER "(((unsigned int)a)*((unsigned int)b))"; PROCEDURE -UDivBytes (a, b: SHORTINT): SHORTINT "( ((CHAR)a) / ((CHAR)b) )"; PROCEDURE -UDivWords (a, b: INTEGER): INTEGER "(((unsigned int)a)/((unsigned int)b))"; BEGIN (*$MAIN*) B.Init; (* Приводим, т.к. значение > SIZE(SHORTINT): *) a := SYSTEM.VAL(SHORTINT, 255); b := SYSTEM.VAL(SHORTINT, 255); (* Печатает: 65025 (255*255): *) B.PRWORD( UMultBytes(a, b) ); B.PRLN; (* Печатает: 1 ( (-1)*(-1) ): *) B.PRWORD( a * b ); B.PRLN; a := SYSTEM.VAL(SHORTINT, 255); b := 5; (* Печатает: 51 (255 DIV 5): *) B.PRWORD( UDivBytes(a, b) ); B.PRLN; (* Печатает: 0 (-1 DIV 5): *) B.PRWORD( a DIV b ); B.Quit END UMath. Обратите внимание на странный на первый взгляд результат процедур ─UMultiBytes и остальных ─ он того же размера,что и аргу─ менты. Это должно значить,что переполнение при умножении даст потерю разрядности ре─ зультата, однако же на практике этого не происходит,потому что изнутри эти операции устроены так: #define UMath_UDivBytes(a, b) ( ((CHAR)a) / ((CHAR)b) ) #define UMath_UDivWords(a, b) (((unsigned int)a)/((unsigned int)b)) #define UMath_UMultBytes(a, b) ( ((CHAR)a) * ((CHAR)b) ) #define UMath_UMultWords(a, b) (((unsigned int)a)*((unsigned int)b)) Здесь я применил при описании результа─ та короткий тип (байт),чтобы результат был совместим с коротким типом (в случае при─ сваивания результата переменной длиной в 1 байт) без удлинения, т.е.: short:=UMultBytes(short1,short2); вместо характерного дляОберона явного SHORT() для уменьшения разрядности типа (угадайте, какой вариант более эффекти─ вен?): short:=SHORT(UMultBytes(short1,short2)); Но если тип результата имеет среднюю разрядность (слово), то,как видите,старший разряд результата не теряется: integer:=UMultBytes(short1,short2); (ВОбероне обязательно явное указание SHORT() для уменьшения мощности числового типа; так сделано,чтобы было легче контро─ лировать возможное искажение результата в случае приведения бОльших типов к мень─ шим). Битовые вычисления Вирт попытался придать работе с битами более привлекательный вид, согласующийся с математическими абстракциями, поэтому биты машинного слова представлены как множество целых чисел ─ номеров отдельных битов (http://oberoncore.ru/library/wirth_sets). Для Оберона вместо универсальных множеств были выбраны множества небольших целых чисел. Тип SET в Обероне можно рассматри─ вать как битовый набор, спроектированный так,чтобы быть независимым от порядка сле─ дования байтов платформы (так называемый byte order: most significant byte ─ MSB, и least significant byte ─ LSB). Именно поэ─ тому Оберон не поощряет насильственного приведения целых к множествам и наоборот, т.к.такое системное приведение типа ─ опе─ рация достаточно низкоуровневая,чтобы учи─ тывать порядок байтов, и её использование может привести к непредсказуемым последст─ виям на платформах с разным порядком сле─ дования байтов, хотя, конечно, для Z80 это некритично. Размер типаSET в Обероне зафиксирован в соответствии с современными процессорами и составляет 4 байта (в GPCP есть тип LONGSET = 8 байт), но в ZXDev мы можем в конфиге Ofront.par указать произвольный размер множеств, и я настоятельно рекомен─ дую 1 байт, что наиболее эффективно для процессора Z80. ОперацияMOD (остаток от целочисленного деления) при соответствующем делителе бу─ дет оптимизирована до соответствующего ей логического AND, например, обероновское a MOD 8 будет транслировано в сишноеa&7. Эквивалент логических побитовых опера─ ций для целыхa и b: a AND b = ORD(BITS(a) * BITS(b)) a XOR b = ORD(BITS(a) / BITS(b)) a OR b = ORD(BITS(a) + BITS(b)) NOT a = ORD(-BITS(a)) Где ORD - это преобразование битового множества в целое, аBITS ─ целого во мно─ жество. Идеология Оберона не поощряет ра─ боту с целыми как с битами и наоборот, ибо это, по мнению Вирта, ведёт к неряшливому использованию типов и нивелирует преимуще─ ства строгой типизации, поэтому функций BITS() иORD(set) в стандарте Оберона нет, но есть вКомпонентном Паскале (и в XDev тоже). IF 0 IN set THEN(* if(set & 1) ... *) (* if (set & 0x23) ... *) IF BITS(23H) * set # {} THEN(* ... *) (* if (set & 0x23) ... *) IF {0, 1, 5} * set # {} THEN(* ... *) Последний вариант,как мне кажется,более наглядно показывает, что в наборе проверя─ ется состояние битов №№0,1 и 5. Пусть вас не вводит в заблуждение некоторая вычур─ ность записи битовых операций,особенно это кажущееся "умножить" ─ машкод получается что надо: ; if ((0x23 & _set) != 0x0) { ld a,(#_set + 0) and a, #0x23 jr Z, ... Таким образом, наОбероне можно сделать достаточно низкоуровневую программу,напри─ мер, эмулятор Спектрума. Константные массивы Для включения ресурсов и двоичных дан─ ных прямо в кодОберон не предлагает ниче─ го лучше,чем поэлементное присваивание.И я очень благодарен Олегу Комлеву (Saferoll) за его труд по добавлению в ZXDev нестан─ дартного языкового расширения ─ констант─ ных массивов. Дадим ему слово: Saferoll: Что удалось сделать по константным мас─ сивам на данный момент - май 2015 года. 1)Константные массивы любой вложеннос─ ти. 2)Типы элементов - линейка целых типов (включаяBYTE),BOOLEANилиCHAR.Все эти типы в C-исходнике становятся целыми конс─ тантами. 3)Если массив состоит изCHARилиBYTE, то элементы можно указывать либо как пере─ чень символов в скобках('f',20X,"7"),ли─ бо в виде строки"ab"без лишних скобок.Но строка обязательно подразумевает в конце символ0Х,для него тоже должно быть место в массиве! Ставить в кавычках меньше символов мож─ но,тогда символы после0Xмогут быть запо─ лнены мусором - зависит от реализации Си- компилера. Поэтому лучше считать,что неис─ пользуемый строкой остаток массива запол─ нен неопределёнными символами. Пустую строку "" можно указывать для любого массиваARRAY N OF CHAR(илиARRAY N OF BYTE). Примеры: TYPE MsgStr = ARRAY 3, 7 OF CHAR; CONST Way = MsgStr("Hello","Error","Try"); TYPE Labirint = ARRAY 3, 16 OF CHAR; CONST Map = Labirint( "...o..##...oo12", "...o..##...ooЗ5", "...o..##...oo78" ); Пока не сделано: экспорт константных массивов (с этим мы ещё не разобрались), возможность опускать размер массива, чтобы Ofront автоматически его рассчитал по ко─ личеству элементов, и указание$на фикси─ рованный символьный массив. Чую, что тут опять полезут проблемы с путаницей "сим─ вол или строка". Также есть куда развивать реализацию в смысле эффективности. Но то, что сделано сейчас, уже весьма полезно. Совместное использование Оберона и Си В Оберон-программы можно вставлять про─ извольные части кода, написанные на языке Си (и встроенном ассемблере).Есть несколь─ ко способов (http://zx.oberon2.ru/forum/ viewtopic.php?f=10&t=202 ),которые я крат─ ко перечислю. 1. Прямая вставка сишного файла в Оберон-программу IMPORT SYSTEM; PROCEDURE -includemain '#include "Main.c"'; // --- Main.c --- void main (void) { Basic_Init(); Laser_InitScroll(65392); Laser_InitSprites(Rsrc_SprStart, 4769); ... Basic_Quit(); } Разумнее всего будет вставлять так от─ дельные функции, хотя может оказаться, что этот способ имеет гораздо более широкие возможности. Просто нужно учитывать то,что Оберон-модуль как-то должен знать про этот сишный код, чтобы уметь с ним взаимодейст─ вовать. Как мы знаем, Оберон-строки являются нуль-терминированными, но в отличие от си─ шных к ним прикреплено значение максималь─ ной длины строки.Если процедуры для работы со строками всегда будут действовать в рамках этой длины ─ код всегда будет рабо─ тать корректно. Но можно ли работать на Обероне со строками целиком в сишном стиле без дополнительного поля макс. длины? Ко─ нечно, можно. Вот мы опишем вызовы сишных функций в обёртке обероновских процедур, и они могут даже не совпадать по параметрам (см., например,IntToStr ): TYPE (* C-like null-terminated string: *) CString = SYSTEM.PTR; PROCEDURE -includestdlib "#include <stdlib.h>"; PROCEDURE -includestring "#include <string.h>"; PROCEDURE -Length ( str: CString): INTEGER "strlen((char*)str)"; PROCEDURE -CopyStr (dest, src: CString) "strcpy((char*)dest, (char*)src)"; PROCEDURE -IntToStr ( n: INTEGER; str: CString) "_itoa(n, (char*)str, 10)"; PROCEDURE -UIntToStr ( u: INTEGER; s: CString) "_uitoa((unsigned int)u, (char*)s, 10)"; PROCEDURE -Concat (dest, src: CString) "strcat((char*)dest, (char*)src)"; Пример использования: IMPORT SYSTEM, B := Basic; CONST MaxIntSize = 7;(* ~-12345~ + 0X. *) VAR num: SHORTINT; strBuf: ARRAY MaxIntSize OF CHAR; BEGIN num := B.RND(1, 4); UIntToStr(num, SYSTEM.VAL( CString, SYSTEM.ADR(strBuf)) ); Concat(SYSTEM.VAL(CString, SYSTEM.ADR(strBuf)), SYSTEM.VAL(CString, SYSTEM.ADR(" is my number")) ); 2. Биндинг Чтобы Оберон умел взаимодействовать с кодом наСи ─ нужно как-то к нему прикре─ питься. Необходимо сделать описание интер─ фейса сишной библиотеки в стиле Оберон-мо─ дуля,чтобы другие модули могли вызывать из него процедуры, брать значение констант и т.д. Для этого мы должны подготовить бин─ динг-связку, в которой будет описан интер─ фейс чужеродного модуля,все константы,типы и процедуры (в случае XDev ─ с пустыми те─ лами). Замечу,что это обычная практика для связки модульных языков с сишными библио─ теками (применяется не только вОберонах, но и в языкахАда, Модула-2, Модула-3 ). XDev содержит достаточные средства для создания биндингов,учитывающие возможность использовать разные модели вызова функций, замену одних вызовов другими,описание про─ тотипов функций и т.д. С их помощью соз─ даны биндинги к WinAPI и libSDL для XDev/ WinDev. Приведу пример простого биндинга, отсылая за деталями к форуму http://zx.oberon2.ru/forum/ viewtopic.php?f=10&t=94. MODULE Input; IMPORT SYSTEM; CONST Backspace* = OCX; Enter* = ODX; Escape* = "E"; Space* = " "; (* Arrows *) Up * = "Q"; Down * = "A"; Right * = "P"; Left * = "O"; TYPE Key* = CHAR; (** Returns the number of keystrokes in the keyboard input buffer. *) PROCEDURE Available* (): SHORTINT; BEGIN RETURN 0 END Available; (** Read a key from the keyboard buffer. Blocks if no key is available. *) PROCEDURE Read* (): Key; BEGIN RETURN 0X END Read; PROCEDURE RunMe5OHz* ; END RunMe5OHz; END Input. Транслятор сгенерирует из этого биндин─ га сишный файл с пустыми телами функций, а также более нужные нам 1) заголовочный файлObj/Input.h, который будет подключен при компиляции, и 2) символьный файл Sym/ Input.sym, подобный таким же,сгенерирован─ ным для родных Оберон-модулей. В нём хра─ нится закодированное представление интер─ фейса модуля. Автосгенерированную пустую сишную реализацию мы игнорируем, подменяя при компиляции на реализацию, написанную ручками (на Си или асме), которая хранится в папке/C и реализует заявленную в интер─ фейсе функциональность. 3. Биндинг с использованием готового сишного стандартного заголовка и самодельного заголовка-переадресовщика Допускаю и такую вариацию: при необхо─ димости можно игнорировать и автосгенерён─ ный Оберон-транслятором заголовок(*.h). Делается модуль-биндинг на Обероне (для получения символьного файла), а при компи─ ляции подключается сделанный вручную заго─ ловочный сишный файл, из которого1) уже в свою очередь инклюдится стандартный сишный заголовок, не адаптированный к оберонскому манглированию имён префиксом и т.п.;и2) в котором описывается переадресация Оберон- процедур в сишные функции или даже макро─ сы. Подобным образом устроен биндинг к библиотеке trdos.lib (см. в дистрибутиве XDev ). (про сопряжение с ассемблером см.следующую статью)
Другие статьи номера:
Похожие статьи:
В этот день... 21 ноября