You are not logged in.
Pages: 1
Так, быстрое и сумбурное описание сабжа. Без лишних слов, это мой новый проект - компилятор высокоуровневого языка для CLEO скриптов.
Проект пока находится на стадии разработки, но кое-какие результаты уже есть, а кроме того, мне понадобилась помощь GTA-modding сообщества, но об этом ниже.
Для чего этот топик
Для мануалов, обсуждения языка, предложений, вопросов и ответов.
Для затравки пара скриптов, примерно описывающих синтаксис.
Скрипт 1
Нормальная арифметика выражений и всякое такое:cool:
/* Объявление статической скриптовой функции в теле скрипта */ [Opcode=0x0ACE] static void DISPLAY_HELP_MESSAGE_FORMATTING(auto format, ...); void digits_out(int digit1, int digit2, int digit3, int digit4) { DISPLAY_HELP_MESSAGE_FORMATTING("%1d%1d%1d%1d", digit1, digit2, digit3, digit4); } // точка входа void main() { int stringRepresentation; int i; int intRepresentation[4]; stringRepresentation = '1234'; // 0x31323334 for (i = 0; i < 4; i += 1) { intRepresentation[i] = (stringRepresentation >> (8 * i) & 0xFF) - '0'; } // на экран будет выведено "4321" digits_out(intRepresentation[0], intRepresentation[1], intRepresentation[2], intRepresentation[3]); }
Думаю, дополнительных пояснений не требуется.
Во что код компилируется:
// This file was decompiled using sascm.ini published by Seemann (http://sannybuilder.com/files/SASCM.rar) on 13.10.2007 {$VERSION 3.1.0027} {$CLEO .cs} //-------------MAIN--------------- 0AB1: call_scm_func @Noname_46 0 0A93: end_custom_thread :Noname_12 0ACE: show_formatted_text_box "%1d%1d%1d%1d" 0@ 1@ 2@ 3@ 0AB2: ret 0 :Noname_46 0@ = 825373492 1@ = 0 :Noname_63 not 1@ >= 4 jf @Noname_142 0A90: 6@ = 8 * 1@ // int 0B15: 7@ = 0@ SHR 6@ 0B10: 7@ = 7@ AND 255 7@ -= 48 0006: 2@(1@,4i) = 7@ // Note: the incorrect math opcode was used here 1@ += 1 jump @Noname_63 :Noname_142 0AB1: call_scm_func @Noname_12 4 2@ 3@ 4@ 5@ 0AB2: ret 0
Скрипт 2
Использование array's index overflow для создания расширенного набора локальных переменных. Идея взята отсюда: http://sannybuilder.com/forums/viewtopic.php?id=995
#define ARRAY_SIZE 10 [Opcode=0x0AD3] static void SPRINTF(auto destination, auto format, ...); [Opcode=0x0ACA] static void DISPLAY_HELP_MESSAGE(auto text); void main() { int address; /* на практике память выделяется по адресам памяти, кратным 4, на правильнее писать ARRAY_SIZE + 2 */ if (ALLOCATE_MEMORY((ARRAY_SIZE + 2) * sizeof(int), out address)) { int base, i; int extra_ints[1]; string test; base = (address - &extra_ints[0]) / sizeof(int); test = ""; // использование выделенной памяти через array overflow for (i = 0; i < ARRAY_SIZE; i += 1) { extra_ints[base + i] = i; } for (i = 0; i < ARRAY_SIZE; i += 1) { SPRINTF(test, "%s%1d", test, extra_ints[base + i]); } // будет выведено: 0123456789 DISPLAY_HELP_MESSAGE(test); FREE_MEMORY(address); } }
Во что компилируется:
// This file was decompiled using sascm.ini published by Seemann (http://sannybuilder.com/files/SASCM.rar) on 13.10.2007 {$VERSION 3.1.0027} {$CLEO .cs} //-------------MAIN--------------- 0AB1: call_scm_func @Noname_12 0 0A93: end_custom_thread :Noname_12 0AC8: 0@ = allocate_memory_size 48 jf @Noname_204 0AC7: 8@ = var 3@ offset 0A8F: 9@ = 0@ - 8@ // int 9@ /= 4 0006: 1@ = 9@ // Note: the incorrect math opcode was used here 06D2: 4@v = "" // @v = string 2@ = 0 :Noname_74 not 2@ >= 10 jf @Noname_125 0A8E: 8@ = 1@ + 2@ // int 0006: 3@(8@,1i) = 2@ // Note: the incorrect math opcode was used here 2@ += 1 jump @Noname_74 :Noname_125 2@ = 0 :Noname_132 not 2@ >= 10 jf @Noname_194 0A8E: 8@ = 1@ + 2@ // int 0AD3: sprintf 4@v "%s%1d" 4@v 3@(8@,1i) 2@ += 1 jump @Noname_132 :Noname_194 0ACA: show_text_box 4@v 0AC9: free_allocated_memory 0@ :Noname_204 0AB2: ret 0
А теперь о помощи. В GTA 4 мы имеем реальные имена скриптовых функций. Их надо соотнести с тем, что мы имеем в III modding'е (то, что принято называть опкодами).
Поэтому, если кто-то хочет заняться составлением этого списка, то в прикреплении находится небольшая тулза (будет хотеть .NET Framework 3.5) и 2 начатые базы BASIC_OPCODES.xml и CLEO_OPCODES.xml. Их можно использовать для примера. Описывать абсолютно все опкоды не имеет смысла - только наиболее часто используемые. Не нужны опкоды для работы с метками, не нужны опкоды для арифметики чисел...
Например, опкод 0001: wait 0 явно соотносится с функцией void WAIT(int time_in_ms). Тут все просто. Но есть несколько моментов, о которых следует упомянуть.
Первый момент - есть функции, возвращающие значения:
int CREATE_CHAR(int pedType, int mi, float x, float y, float z)
Возвращаемое значение - это всегда последний операнд в опкоде:
009A=6,%6d% = create_actor_pedtype %1d% model %2o% at %3d% %4d% %5d%
Второй момент - есть функции, возвращающие несколько значений, в этом случае они описываются в списке параметров с аттрибутом 'out':
void GET_CHAR_COORDINATES(int handle, out float x, out float y, out float z)
Третий момент - в CLEO есть так называемые IF and SET опкоды, которые устанавливают значение логической операции TRUE или FALSE. Такие функции нужно описывать так:
bool ALLOCATE_MEMORY(int size, out int pointer)
Четвертый момент - в CLEO многие опкоды не типизированы. Например, в качестве параметра могут приниматься либо scm-строки, либо указатели на C-строки. Для таких случаев введен тип auto. Для параметров такого типа компилятор не будет выполнять проверку и приведение типов.
int OPEN_FILE(auto path, auto mode)
Offline
Идея хороша, но сишный синтаксис не мое. Я бы предложил сделать что-то более простое и понятное для новичков, возможно с элементами из разных языков.
А вообще я бы начал с другой стороны. Написать сначала не обертку для низкоуровневого синтаксиса, а compile-on-fly прогу. Т.е. программу (или модуль в составе CLEO), которая бы читала текстовые исходники (с обычными опкодами), компилировала их в язык scm и запускала на исполнение. Аналог LUA. Т.е. при внесении изменений в исходник не требовалась бы запускать SB и перекомпилировать скрипт.
Потом на основе этого можно было бы однотипные синтаксические конструкции в исходниках заменять на высокоуровневый язык.
Offline
ИМХО лучше подучить несложный язык скриптинга, чем работать через интерпритатор с неизвестным КПД о_О
Что такое КПД? Скорость исполнения? или скорость компиляции? или еще что-то?
@Seemann -
C-подобный синтаксис выбран не из соображений понятности для новичков, а из соображений удобства реализации парсера и естественной соотносимости с низким уровнем. Вообще говоря, язык будет рассчитан явно не на новичков, а на опытных программистов или программеров-любителей, знакомых с языками типа C. Для них освоение такого скриптового языка - дело пары часов.
По поводу JIT-компиляции - предложение интересное, а главное, вполне реализуемое. Но это не то, чем я сейчас занимаюсь, так что вопрос отложим хотя бы до тех пор, пока не будет полностью рабочий и опробованный обычный компилятор.
Т.е. при внесении изменений в исходник не требовалась бы запускать SB и перекомпилировать скрипт.
Уточнюсь. cleo script compiler - это не компилятор из C-подобного языка в язык SB. Вообще, параллельно с компилятором идет выпиливание простенькой IDE к нему. Получится продукт, независимый от Sanny Builder'а (Хотя Sanny Builder будет удобно использовать в качестве декомпилятора и просмотра кода на низком уровне).
Ну а так, почему я решил этим заняться - просто проба сил. А почему компилятор для CLEO скриптов - да просто мне до жути надоело иметь дело с листингами скриптов на низком уровне.
______________________________________
А теперь кратко о языке.
[large]Типы данных[/large]
Имеются следующие типы данных:
int - 4-байтовое целое
float - вещественное одинарной точности
gxt - строка из 7 или менее символов
string - строка из 15 или менее символов (есть возможность использовать constant string длиной до 127 символов)
bool - логическое. Нельзя объявлять переменные этого типа - значения этого типа могут только возвращаться статическими функциями (опкодами) или пользовательскими функциями. Существуют 2 константных значения - true и false.
void - тип для функций, не возвращающих значения.
А также массивы: int[], float[], gxt[], string[].
[large]Объявление данных[/large]
Данные можно объявлять в теле функции в любом месте. Все, что объявлено между { и } действует только внутри этого блока и перегружает все одноименные символы из родительских блоков.
Объявление данных в глобальной области видимости в CLEO скриптах запрещено. Зато можно описать некоторые внешние символы из майна следующим образом:
[FixedVarPosition=2] extern int Player; [FixedVarPosition=3] extern int PlayerChar;
Естественно, все, что объявляется должно умещаться в набор 32 локальных переменных (в случае с миссиями - 1022) и при этом должно оставаться место для генерации компилятором временных переменных. Для обращения к таймерам 32@, 33@ используются предопределенные идентификаторы timer1, timer2.
[large]Операторы в математических инструкциях[/large]
Используются следующие операторы:
= (присваивание) +, += (сложение), -, -= (вычитание), *, -= (умножение), /, /= (деление), &, &=(поразрядное и), |, |= (поразрядное или), ^, ^= (поразрядное исключающее или) <<, >>, <<=, >>= (логические сдвиги) - (минус, унарный) sizeof (получение размера переменной / типа в байтах) ~ (поразрядное отрицание. унарный) %, %= (модуло) && (логическое и) || (логическое или) & (взятие ссылки на переменную, унарный) (int) (приведение float к int) (float) (приведение int к float) () (вызов функции или группировка операторов по первичности) == (тождественное равенство) >, <, >=, <=, != (операторы сравнения) ! (логическое отрицание, унарный)
Операторов постфиксного и префиксного инкремента/декремента нет и не будет. Оператора ?: также нет. Насчет других операторов - они имеют тот же приоритет и ассоциативность, что и в языке C.
Типы float <-> int приводятся автоматически, но лучше указывать приведение типов явно, чтобы не возникало неожиданных казусов.
Например, выражение 3 / 4 == 0, а выражение (float)3 / 4 == 0.75.
[large]Высокоуровневые конструкции[/large]
Используются следующие конструкции (кажется, все те же самые, что и в C): if, if-else, for, while, do-while, switch.
По поводу switch, здесь используется подход C# - неявный fall-through запрещен. Каждая ветвь исполнения должна оканчиваться либо на break, либо на goto case/ goto default.
ЗЫ: кто заинтересовался, спрашивайте, что непонятно и нужно пояснить.
Last edited by Alien (02-09-2010 19:40)
Offline
Я вообще не понял зачем такое нужно. Ну раз не хочется писать скрипт на низкоуровневом виде, то можно свою ASI библиотеку подключить. Можно подробней объяснить в чём суть представленного выше?
Offline
@Sw[ee]t - А разве ASI и CLEO скрипт - это одно и то же? Для того, чтобы написать делающую что-то полезное ASI библиотеку, нужно еще потанцевать с бубном вокруг грязных хаков. Посмотреть хотя бы на тот же gta_dll. Там половина всего кода - это объявления такого рода:
void (__thiscall *CPed__GiveWeapon)(CPed *, int weapon, int ammo) = (void (__thiscall *)(CPed *, int, int))(0x5E6080);
Это было во-первых. Во-вторых, нужно еще воткнуть эту ASI куда-то в процесс исполнения игры. Туда, куда воткнулась одна ASI, вторую уже не воткнешь, поэтому ASI библиотек, так мало, а CLEO скриптов так много.
А смысл перехода на этот язык - в общем повышении читабельности кода, в повышении защищенности.
Сколько здесь на форуме вопросов типа:
"Почему, какие бы координаты я ни поставил, я всегда появляюсь в центре карты?"
00A1: put_actor $PLAYER_ACTOR at 345 306 998
А все потому, что нет типизации. Да и от самого подхода к командам: опкод, операнды... - нужно отойти. За каждой командой нужно лезть в Opcode Search Tool, а за некоторыми вообще в SASCM.ini.
С именами гораздо удобнее: нажал ctrl+space - вывалился список всех команд, набрал первые буквы - получил свою команду.
@3Doomer -
По надежности высокоуровневый код будет всегда выигрывать у низкоуровневого. Ну а по оптимизации - ты получаешь только тот код, который пишешь. Каждая инструкция компилируется сама в себя. Есть ли разница, пишешь ты на низком уровне:
if 0AB0: 0x41// A then // ... end
или на высоком:
if (IS_KEY_PRESSED('A')) { // ... }
(Здесь ты кстати только сэкономишь на одной инструкции - 00D6: if 0).
ЗЫ: Забыл сказать, что комплексные условия, которые раньше образовывались с 00D6, теперь будут образовываться при помощи соответствующих переходов. То есть можно будет писать следующим образом:
if (PLAYER_DEFINED(0) && CHAR_HAS_WEAPON(PlayerChar, 28)) ...
Если первое подусловие ложно, то второе проверяеться не будет и вылета не последует.
Offline
теоретически, компиляторы си/паскаля тоже интерпретируют на асм только то, что ты пишешь...но написать на асме - получится гораздо короче/проще в большинстве случаев о_О
asi и cleo, на сколько я понимаюд, различаются. asi - dllка, выдаёт команды непосредственно exe игры, а скрипты - скриптовому движку
GIMS developer
Offline
Меня смутило слово CLEO в названии. Теперь суть проекта ясна. Это скорее альтернатива саннику, или развитие идей Point Compiler. Что ж удачи. Хотя у меня есть определенные сомнения в будущей популярности си-подобного синтаксиса среди скриптеров.
Offline
@Alien - я всё понял. Довольно интересно сделать что-то типа natives гта4
Присоединяюсь к симэну и желаю удачи!
Offline
За пожелания удачи всем спасибо. Насколько я понял, желающих помочь в составлении списка статических функций пока не нашлось? Ну чтож, буду справляться своими силами.
Кстати, если тут есть IV скриптеры, чем отличаются функции MESSAGE от NM_MESSAGE и можно ли их соотнести с show_text_highpriority и show_text_lowpriority?
теоретически, компиляторы си/паскаля тоже интерпретируют на асм только то, что ты пишешь...но написать на асме - получится гораздо короче/проще в большинстве случаев о_О
Это зависит и от асма. Если он содержит complex instruction set, то это представляет определенный простор для оптимизации. Взять тот же x86. Умножение регистра eax на 40 в общем случае выглядит так:
imul eax, 40
А оптимизировано по времени так (хотя я не думаю, что те кто пишет на ассемблере предпочтут этот вариант - он слишком коряво выглядит):
lea eax, [eax + 4*eax] shl eax, 3
Ну, а если в асме есть только одна инструкция для умножения, то тут особо не пооптимизируешь...
Хотя есть и более "жесткие" средства оптимизации. Но тут уж я предполагаю, что писаться будет удобо-компилируемый код. Например, вот в таком выражении:
extra_ints[base + i] = i / extra_ints[base + i];
Чтобы дважды не вычислялось base + i, его можно вынести в отдельное выражение:
int index = base + i; extra_ints[index] = i / extra_ints[index];
Хотя у меня есть определенные сомнения в будущей популярности си-подобного синтаксиса среди скриптеров.
Да уж. Не понимаю что такого страшного в сишном синтаксисе? Все равно среднестатистический гта-скриптер (как правило школьник 8-9 классов) будет с увлечением читать о таблицах переходов и о том, куда надо поставить нолик, а куда единичку, но будет страшно бояться разобраться в структуре switch-блока.:D
Last edited by Alien (05-09-2010 09:47)
Offline
"О себе бы подумал, ты ведь не отдыхаешь совсем. Всё о Роисси думаешь!"
Offline
Возник вопрос. Нужна ли поддержка меток и переходов (goto)? В некоторых случаях эта конструкция языка может помочь сделать код более эффективным, но при этом нарушается структурированность программы. Учитывая необъяснимую тягу скриптеров к меткам и переходам в SB, есть предложение выкинуть ее нафик, ибо за всю свою жизнь мне ни разу не пришлось ей воспользоваться (SB и asm не в счет), а вот неопытных скриптеров оградить от этого зла надо.
И еще. Каким образом подходить к скриптовым константам (педтипам, номерам клавиш, номерам ооружия и проч.)? Тут 2 варианта - либо нативная поддержка (как с опкодами), либо определение во внешних подключаемых модулях (include)? Этакая стандартная библиотека...
Offline
И еще. Каким образом подходить к скриптовым константам (педтипам, номерам клавиш, номерам ооружия и проч.)? Тут 2 варианта - либо нативная поддержка (как с опкодами), либо определение во внешних подключаемых модулях (include)? Этакая стандартная библиотека...
Я за вариант 2 (инклюды). Их исправить можно, если что не нравится.
Offline
Учитывая необъяснимую тягу скриптеров к меткам и переходам в SB, есть предложение выкинуть ее нафик
Хоть все и пользуются, все равно надо убрать... Где логика?
Все зависит от возможностей языка. Если язык позволяет обходиться без меток, поддержка их не нужна. Если нет - нужна. Все просто.
Offline
@Alien - Метки/goto - однозначно нет.
Константы - определять через const.
Для встроенных типов, я предпологаю в C4 ввести слово immutable, например:
immutable PEDHANDLE;
immutable VEHHANDLE;
А потом использовать PEDHANDLE в качестве типа.
полноценный typedef, скорее всего, не нужен.
Смысл такой конструкции в том, что у нас есть значение, с которым мы не можем сделать ничего внутри скрипта. Мы получаем его извне и можем передать дальше. Максимум - присвоить переменной такого же типа.
Заодно, это уберет кучу ошибок, когда хендл одного типа пытаются передать опкоду, который поддерживает хэндлы только другого типа.
Offline
ОК, от goto отказываемся раз и навсегда.=)
@listener - Ага, защищенные скриптовые хэндлы - это полезно. Только у меня они скорее будут как самостоятельные типы, явно приводимые к int при необходимости (через оператор (int)), а неявно не приводимые. Сделать эти типы объявляемыми в коде (через typedef/immutable) не выйдет, потому что хэндлы используются в основном скриптовыми функциями, хранимыми в автономных постоянных хранилищах - базах данных (чтобы не пришлось парсить много кода при каждой компиляции - они будут грузиться 1 раз в момент запуска IDE). Как их потом привязывать к этим объявлениям в коде?
Offline
Pages: 1