Переходный синтаксис SourcePawn

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


На этом все :slight_smile: