SourceMod 1.10 представляет Transitional API . Он построен на новом переходном синтаксисе в SourcePawn, который представляет собой набор языковых инструментов, делающих Pawn более современным. В частности, он позволяет разработчикам использовать старые API объектно-ориентированным образом, не нарушая совместимости. Когда-нибудь, если и когда SourcePawn сможет стать полноценным современным языком, переходный API сведет к минимуму усилия по переносу.
Переходный API имеет следующие основные функции и изменения:
- Новые деклараторы — более чистый способ объявления переменных, аналогичный Java и C#.
- Methodmaps — объектно-ориентированные оболочки старых API.
- Реальные типы — SourcePawn теперь имеет «int», «float», «bool», «void» и «char» в качестве реальных типов.
- null — новое общее ключевое слово для замены INVALID_HANDLE .
Новые деклараторы
Разработчики, знакомые с Pawn, узнают оригинальный стиль объявления Pawn:
new Float:x = 5.0;
new y = 7;
В переходном синтаксисе это можно перефразировать так:
float x = 5.0;
int y = 7;
Следующие встроенные теги теперь имеют типы:
- Float: is float.
- bool: is bool.
- _: (or no tag) is int.
- String: is char.
- void can now be used as a return type for functions.
Обоснование
В старом стиле помеченные переменные не являются реальными типами. Float:x не указывает на переменную типа с плавающей запятой, он указывает на 32-битную «ячейку» , помеченную как число с плавающей запятой. Тег можно удалить или изменить, что, хотя и гибко, опасно и сбивает с толку. Сам синтаксис также проблематичен. Синтаксический анализатор не имеет возможности идентифицировать символы между именем тега и двоеточием. Внутри компилятор не может представлять значения, которые не являются точно 32-разрядными.
Вывод таков: нет разумного способа представить такое понятие, как «int64» или «X — это тип, представляющий массив чисел с плавающей запятой». Грамматика тегов делает его слишком громоздким, а сам компилятор не может прикрепить такую информацию к тегу. Мы не можем исправить это сразу, но мы можем начать отказываться от системы тегов с помощью более нормального синтаксиса объявлений.
Простой пример, чтобы понять, почему это необходимо, — это странность выражения чего-то подобного с помощью тегов:
native float[3] GetEntOrigin() ;
Примечание по тегу String. Поначалу введение char может показаться запутанным. Причина этого в том, что в будущем мы хотели бы ввести настоящий «строковый» тип, который действует как объект, подобно строкам в большинстве других языков. Существующее поведение в Pawn представляет собой массив символов гораздо более низкого уровня. Переименование проясняет, что такое тип на самом деле, и оставляет дверь открытой для создания более совершенных типов в будущем.
Массивы
Новый стиль объявления устраняет неоднозначность между двумя типами массивов. В Pawn есть неопределенные массивы , размер которых неизвестен, и детерминированные массивы , размер которых известен. Мы называем их «динамическими» и «фиксированной длиной» массивами соответственно.
Массив фиксированной длины объявляется путем размещения квадратных скобок после имени переменной . Например:
int CachedStuff [ 1000 ] ; intPlayerData [ MAXPLAYERS + 1 ] = { 0 , ... } ; int Weapons [ ] = { WEAPON_AK47, WEAPON_GLOCK, WEAPON_KNIFE } ;
В этих примерах размер массива фиксирован. Размер известен заранее и не может измениться. При использовании квадратных скобок в этой позиции размер массива должен быть указан либо явным образом, либо выведен из начального значения.
Массив динамической длины имеет квадратные скобки перед именем переменной , то есть после типа . Наиболее распространенный случай — при указании функций, принимающих массив в качестве входных данных:
native void SetPlayerName(int player, const char[] name);
Здесь мы указываем, что длина имени не всегда известна — она может быть любой.
Динамические массивы также можно создавать в локальных областях. Например,
void FindPlayers ( )
{
int [ ] player = new int [ MaxClients + 1 ] ;
}
Это выделяет новый массив заданного размера и помещает ссылку в player . Память автоматически освобождается, когда она больше не используется.
Недопустимо инициализировать массив фиксированной длины неопределенным массивом, и незаконно инициализировать динамический массив фиксированным массивом. Также незаконно указывать фиксированный размер в массиве динамической длины.
По большей части это не меняет существующую семантику Pawn. Это просто новый синтаксис, предназначенный для разъяснения того, как работают массивы.
Обоснование
В исходном синтаксисе была тонкая разница в объявлении массива:
new array1[MAXPLAYERS + 1];
new array2[MaxClients + 1];
Здесь array1 и array2 — это очень разные типы: первый — это int[65] , а второй — int . Однако синтаксической разницы нет: компилятор должен сделать вывод, является ли выражение размера константой, чтобы определить тип. Новый синтаксис четко и явно устраняет неоднозначность этих случаев, поэтому в будущем, когда мы представим полностью динамические и гибкие массивы, у нас будет меньше шансов сломать существующий код.
Это может привести к некоторой путанице. Надеюсь, это не будет иметь значения в долгосрочной перспективе, поскольку, как только у нас появятся настоящие динамические массивы, фиксированные массивы станут гораздо менее полезными и более неясными.
Логика ограничения инициализаторов аналогична. Как только у нас появятся настоящие динамические массивы, ограничения будут сняты. А пока нам нужно убедиться, что мы ограничены семантикой, которая не будет иметь тонких различий в будущем.
Примеры
float x = 5.0;
int y = 4;
char name[32];
void DoStuff(float x, int y, char[] name, int length) {], length)"
Format(name, length, "%f %d", x, y);
}
Грамматика
Ниже представлена новая и старая грамматика объявления.
return-type ::= return-old | return-new
return-new ::= type-expr new-dims? // Note, dims not yet supported.
return-old ::= old-dims? label?
argdecl ::= arg-old | arg-new
arg-new ::= "const"? type-expr '&'? symbol old-dims? ('=' arg-init)?
arg-old ::= "const"? tags? '&'? symbol old-dims? ('=' arg-init)?
vardecl ::= var-old | var-new
var-new ::= var-new-prefix type-expr symbol old-dims?
var-new-prefix ::= "static" | "const"
var-old ::= var-old-prefix tag? symbol old-dims?
var-old-prefix ::= "new" | "decl" | "static" | "const"
global ::= global-old | global-new
global-new ::= storage-class* type-expr symbol old-dims?
global-old ::= storage-class* tag? symbol old-dims?
storage-class ::= "public" | "static" | "const" | "stock"
type-expr ::= (builtin-type | symbol) new-dims?
builtin-type ::= "void"
| "int"
| "float"
| "char"
| "bool"
tags ::= tag-vector | tag
tag-vector ::= '{' symbol (',' symbol)* '}' ':'
tag ::= label
new-dims ::= ('[' ']')*
old-dims ::= ('[' expr? ']')+
label ::= symbol ':'
symbol ::= [A-Za-z_]([A-Za-z0-9_]*)
Пользовательские теги
Карты методов не обязательно использовать с дескрипторами. Можно определить пользовательские карты методов для новых или существующих тегов. Например:
methodmap AdminId {
public int Rights() {
return GetAdminFlags(this);
}
};
Теперь, например, можно сделать:
GetPlayerAdmin ( id ) .Rights ( )
Определения типов
Теги функций и перечисления функций устарели в пользу более современного синтаксиса. В настоящее время они по-прежнему могут создавать только имена тегов для функций. Будущие версии будут поддерживать произвольные типы.
Обновить как functags, так и funcenums просто. Ниже приведены два примера:
functag public Action:SrvCmd(args);
funcenum Timer {
Action:public(Handle:Timer, Handle:hndl),
Action:public(Handle:timer),
};
Теперь это становится:
typedef SrvCmd = function Action (int args);
typeset Timer {
function Action (Handle timer, Handle hndl);
function Action (Handle timer);
};
Enum структуры
Структуры Enum были ранее неподдерживаемым механизмом эмуляции структур через массивы. Начиная с SourceMod 1.10, этот механизм теперь полностью поддерживается с помощью Transitional Syntax. Вот пример синтаксиса структуры enum:
enum struct Rectangle {
int x;
int y;
int width;
int height;
int Area() {
return this.width * this.height;
}
}
void DoStuff(Rectangle r) {
PrintToServer("%d, %d, %d, %d", r.x, r.y, r.width, r.height);
}
void SaveRectangle(ArrayList list, const Rectangle r) {
list.PushArray(r, sizeof(r));
}
void PopArray(ArrayList list, Rectangle r) {
list.GetArray(list.Length - 1, r, sizeof(r));
list.Erase(list.Length - 1);
}
Грамматика для структур enum выглядит следующим образом:
enum-struct ::= "enum" "struct" symbol "{" новая строка enum-struct-entry enum-struct-entry* "}" term
enum-struct-entry ::= enum-struct-field
| enum-struct-method
enum-struct-field ::= type-expr symbol old-dims? термин
enum-struct-method ::= type-expr symbol "(" аргументы-метода ")" func-body term
Применение нового синтаксиса
В качестве применения в документациях выведена прагма :
#pragma newdecls required