Скриптовый язык программирования s4g 0.9.2
Web site: s4g.su
Copyright © Vitaliy Buturlin, Evgeny Danilovich, 2017
1 Общее
s4g (scripts for games) – императивный, процедурный, встраиваемый, скриптовый язык программирования общего назначения, написанный на C++.
s4g создавался как мощный, гибкий, быстрый и простой скриптовый язык (конечно же в балансе всего этого), предоставляющий достаточные выразительные средства для решения различных задач.
Как такового понятия входа в программу нет, поэтому могут выполняться отдельные участки кода, такие как функции.
s4g легко расширяем посредством предоставляемого набора функций (api). Файл s4g_lib_std.h яркий пример простого расширения языка, в данном файле происходит экспорт языковых функций в скриптовую систему.
s4g поддерживает препроцесс похожий на C++.
Тип виртуальной машины – стековая.
При разработке был сделан особый упор на real-time производительность исполнения кода и отчетности скриптовой системы перед программистом, так как изначальная цель использования языка в среде критичной по времени исполнения.
Является свободно распространяемым программным средством по лицензии MIT (см. файл LICENSE).
2 Описание языка
2.1 Лексические соглашения
Имена – любые строки из букв/цифр/знака подчеркивания ( _ ) которые начинаются не с цифр, содержащие в себе не более S4G_MAX_LEN_VAR_NAME символов. Имена используются для именования полей таблиц, переменные в глобальном пространстве имен являются полями.
s4g чувствителен к регистру символов (Name не равно name).
Ключевые зарезервированные слова, которые не могут использоваться в именовании:
null, var, class, extends, function, true, false, for, if, else, while, do, return, break, continue, extern, _g
Допустимые символы:
/* */ // . , ; … + - ++ -- > < >= <= ! != == === !== * / = { } [ ] “ ‘ && || & |^ %
Однострочные комментарии начинаются с // и заканчиваются новой строкой.
Многострочные комментарии заключаются /* комментарии */ .
Каждое выражение должно заканчиваться ; (точка с запятой). Излишки данного символа не несут никакого смысла, и ошибок не вызывают.
Каждая управляющая конструкция (if, for, while, do while) имеющая более одного выражения, функция, объявление класса, блоки, должны быть заключены в {} (фигурные скобки).
2.2 Значения и типы
s4g язык с динамическим определением типов. В любой момент времени одна и та же переменная может иметь разные типы. Переменная может содержать значения любого из поддерживаемых типов. Возможность определения пользовательских типов отсутствует.
Всего в s4g определено 12 типов:
Null (S4G_TYPE_NULL) – пустой тип означающие отсутствие каких либо данных в переменной.
Table (S4G_TYPE_TABLE) – ассоциативные массивы которые индексируются числами (с 0) и строками (именами переменных). Так же это может быть просто массив. Одновременно в одной и той же таблице могут быть различные типы данных.
Array (S4G_TYPE_ARRAY) – аналог массива в C++.
String (S4G_TYPE_STRING) – пользовательские строки (массивы символов).
Float (S4G_TYPE_FLOAT) – число с плавающей запятой. C(++) тип можно переопределить (s4g_float).
Int (S4G_TYPE_INT) – целое десятичное знаковое число. C(++) тип можно переопределить (s4g_int).
Uint (S4G_TYPE_UINT) - целое десятичное беззнаковое число. C(++) тип можно переопределить в (s4g_uint).
Bool (S4G_TYPE_BOOL) – логическое значение, принимает либо true (истина) либо false (ложь). C(++) тип можно переопределить (s4g_boo).
Pointer to C(++) data (S4G_TYPE_PDATA) – указатель на пользовательские данные на стороне C(++).
C(++) function (S4G_TYPE_CFUNC) – функция на C(++). s4g_c_function - тип C(++) функции которая принимает скриптовая система.
Script function (S4G_TYPE_SFUNC) – скриптовая функция языка.
Class (S4G_TYPE_CLASS) – класс, пользовательский тип данных.
Class object (S4G_TYPE_CLASS_OBJECT) – объект класса.
1 ошибочный (none - возвращается в случае определения типа, когда не существует переменной)
Переменные с типом null всегда ссылаются на одни и те же данные (это значение зарезервировано в памяти и находится там константно).
Переменные с типом true и false ссылаются на 2 разные константные данные подобно null. Поэтому каждое новое создание переменной логического типа ни в коем случае не производит копирование данных true или false.
pdata регулируется только на стороне C(++). То есть создать/изменить/удалить пользовательские данные на стороне скриптов напрямую не получится. Но это можно сделать посредством экспорта функций в скриптовую систему которые создают/изменяют/удаляют пользовательские данные.
Литералы (пользовательские строки) должны быть заключены в двойные кавычки ("") либо одинарные ('').
Значения типа float (с плавающей запятой) должны содержать в себе либо точку, которая разделяет целую и дробные части, либо в конце числа символ f:
var a = 1.5;
var b = 1f;
Значения типа S4G_TYPE_UINT (unsigned integer - беззнаковые целочисленные) должны содержать после числа символ u:
var a = 1u;
Все остальные числа по умолчанию имеют тип int (integer – знаковые целочисленные).
2.3 Переменные
Контроль точности создания и обращения к переменным
Переменные используются для хранения результата.
ВАЖНО! Все переменные являются полями таблиц, либо контекстов.
Создание переменных происходит посредством обязательного указания перед именем переменной слова var, то есть var a; создает переменную a, Инициализация переменной не обязательна. var может создать только одну переменную. Пример:
var a = 10;
print(a);
a = 20;
Если при обращении к переменной она не будет найдена во всех доступных контекстах то будет выдано соответствующее сообщение об ошибке, также в случае если создаваемая переменная уже имеется в текущем контексте, то также будет выдано соответствующее сообщение об ошибке.
s4g предоставляет 4 вида переменных (по области видимости):
Глобальное пространство имен (по умолчанию _g определено в макросе S4G_GLOBAL_NM). Оно доступно как для скриптера так и для C(++) программиста.
Локальное пространство имен возникает тогда когда происходит выполнение блочных операций (функции, условия, циклы). Доступно только для скриптера в неявной форме.
Системное/языковое пространство имен существует для более быстрого доступа, как в плане написания кода, так и в плане производительности. Используется для дополнительных языковых функций и констант, скриптеру доступно в неявной форме только для чтения, программисту C(++) доступно для чтения/записи.
Поля таблиц, все вышеперечисленное является, по сути, операциями с таблицами.
До явного присвоения переменной значения она имеет тип null.
При выполнении тела скрипта (не функции) глобальное пространство имен доступно по умолчанию и не требует явного его указания. При выполнении блочных операций (функции и управляющие конструкции (условия, циклы)) оно доступно только через _g (определено в макросе S4G_GLOBAL_NM):
var a; //создание переменной, которая по умолчанию равна null
a = 10; //явное присвоение десятичного знакового числа
function qwert() {//создание функции
_g.a = 20; //обращение к глобальному ‘a’ через явное обращение к глобальному пространству имен
}
2.4 Выражения
Доступ к вложенным данным может осуществляться множеством способов, операторы . [] :[] и () могут применяться в любом контексте, при наличии валидной левой части. К примеру доступны конструкции вида:
Все эти операторы могут комбинироваться любым необходимым образом, до тех пор, пока левая часть возвращает допустимый тип значения
2.4.1. Оператор присваивания
В s4g есть только одиночное присваивание. Это связано с концепцией и основной идеей. Самая первая версия имела поддержку как мультиприсваивания, так и параллельного присваивания. Однако в новой версии это было вырезано.
Пример присваивания:
|
Имеется поддержка инкрементов (увеличения значения на единицу) и декрементов
(уменьшения значения на единицу), как префиксной записью (++a;
--a;
) так и постфиксной (a++;
a--;
).
Отличие префиксной записи от постфиксной заключается в разном выданном на
текущий момент результате. Так при префиксной записи значение будет тут же
изменено, а при постфиксной вместо самого значения на стек будет положена его
копия, а само значение будет увеличено.
Инкременты и декременты могут также входить в состав выражений. Пример:
|
Для лучшей читаемости кода рекомендуется использовать скобки ()
, на исполнение кода в случае облагораживания
кода они не имеют влияния.
В левой части присваивания модет находиться как простая переменная, так и составное обращение:
Присваивание с изменением, операторы += -= *= /= %= &= |= ^=
Переменная в левой части должна быть инициализирована.
Запись a += 5 эквивалентна записи a = a + 5, с остальными операторами аналогично
2.4.2. Арифметические операции
s4g поддерживает обычные арифметические операции.
Бинарные (значения находятся по обе стороны оператора)
+
-
*
/
%
Также поддерживается унарный минус, который меняет знак значения переменной
на противоположный. Значение стоящее перед операндом операции задает тип
значения которое получится в конечном итоге. К примеру если был первое значение
было int
то и
результат будет int
.
2.4.3. Операции сравнения
==
!=
>
<
>=
<=
===
!==
Данные операции возвращают (константные во внутренней реализации)
значения true
или false
. Операции сравнения можно
применять к различным типам данных, если их можно преобразовать. Так к
примеру 1 == 1.0
или 1 == "1"
вернет true
.
===
!==
операторы сравнения сначала
сравнивают типы значений и только потом производят сравнение.
2.4.4. Логические операции
&&
(И) ||
(или) !
(НЕ)
&&
и ||
являются бинарными, то есть
наличие операндов с обоих сторон данного оператора обязательно.
!
может быть только
перед операндом (выражением) и означает отрицание.
Все логические операции воспринимают null
, false
, 0
как ложь.
При этом для операторов И и ИЛИ, от результата вычисления левого операнда зависит, будет ли вычисляться правый. Например, в выражении true || func() - func() не будет вызвана, так как для логического ИЛИ достаточно, чтобы один из операндов являлся истиной
Это так же позволяет задавать значения по умолчанию, например код:
var arg = null;
print(arg || "Default arg\n");
arg = "Custom arg\n"
print(arg || "Default arg\n");
выведет строки:
Default arg
Custom arg
2.4.5. Побитовые операторы
Побитовые операторы позволяют считывать и устанавливать конкретные биты целых чисел.
Побитовые операторы
Пример |
Название |
Результат |
a & b |
И |
Устанавливаются только те биты, которые установлены и в a, и в b. |
a | b |
Или |
Устанавливаются те биты, которые установлены в a или в b. |
a ^ b |
Исключающее или |
Устанавливаются только те биты, которые установлены либо только в a, либо только в b, но не в обоих одновременно. |
~a |
Отрицание |
Устанавливаются те биты, которые не установлены в a, и наоборот. |
a << b |
Сдвиг влево |
Все биты переменной a сдвигаются на b позиций влево (каждая позиция подразумевает "умножение на 2") |
a >> b |
Сдвиг вправо |
Все биты переменной a сдвигаются на b позиций вправо (каждая позиция подразумевает "деление на 2") |
Побитовый сдвиг в s4g - это арифметическая операция. Биты, сдвинутые за границы числа, отбрасываются. Сдвиг влево дополняет число нулями справа, сдвигая в то же время знаковый бит числа влево, что означает что знак операнда не сохраняется. Сдвиг вправо сохраняет копию сдвинутого знакового бита слева, что означает что знак операнда сохраняется.
Используйте скобки для обеспечения необходимого приоритета операторов.
Например, a & b == true
сначала
проверяет на равенство, а потом выполняет побитовое и; тогда как (a & b) == true
сначала выполняет
побитовое и, а потом выполняет проверку на равенство.
Оба операнда и результат выполнения <<
и >>
всегда считаются за целое.
2.4.6. Конкатенация
Оператор конкатенации представлен в виде бинарной арифметической операции
сложения +
. Для
возникновения операции конкатенации необходимо, чтобы первым значением перед
знаком +
был
строковый тип. Если тип, следующий за знаком +
не является строкой то он будет преобразован в
строку, если есть для этого правила (если это скаляр).
Оператор конкатенации создает новую строку, не внося изменения в операнды.
2.4.7. Приоритеты операций
Порядок выполнения операторов:
. [] :[] () |
Обращение к содержимому обьектов, вызов функции |
~ |
Побитовое отрицание |
! |
Логическое отрицание |
++ -- - |
Пре/пост инкременты/декременты, унарный минус |
* / % |
Арифметические |
+ - |
Арифметические |
<< >> |
Побитовые сдвиги |
> >= < <= |
Логические |
== != === !=== |
Логические |
& |
Побитовое И |
^ |
Побитовое исключающее ИЛИ |
| |
Побитовое ИЛИ |
&& |
Логическое И |
|| |
Логические ИЛИ |
2.4.8. Операторы перехода
break
– завершает
выполнение цикла (в теле которого он объявлен). Может содержаться только в
циклах.
continue
– прекращает
исполнение всего того что расположено после данного оператора в блоке до конца
блока цикла.
return
– завершает
выполнение кода в текущей функции. Возвращает при этом значение исполняющей
функции сразу после вызова. Завершает цикл, условие, функцию.
2.5 Таблицы
Как и в некоторых других языках, таблицы в s4g являются основным хранилищем данных. Пространства имен, контексты также являются таблицами. Таблицы являются неким подобием ассоциативных массивов.
2.5.1 Создание таблиц, виды таблиц
Таблицы создаются посредством считывания выражения, которое может присваивать таблицу переменной {}.
Таблицы могут быть пустыми, частично заполненными и полными.
Таблицы могут быть полностью анонимными, посредственными, и непосредственными.
Пример создания пустой таблицы:
var table = {};
Создание таблицы с ключами:
var table = {var1 = “asdf”, 15, var154 = {“zxcv”}, {}};
Как видно таблица может содержать различные типы данных. Первое выражение var1 = “asdf” указывает на то что нулевое значение таблицы доступно по имени var1. Следующее значение доступно только посредством доступа по индексу. Так же таблица может содержать в себе другие таблицы и так далее. Последним выражение в создании значений в таблице является опосредованная таблица, которая доступна только через доступ по индексу. table является переменной с непосредственной таблицей.
Пример создания анонимной таблицы:
function ret_a_t(){
return {};
};
Функция ret_a_t вернет анонимную таблицу.
2.5.2 Доступ к элементам таблицы
Квадратные скобки (по умолчанию) [] используются для доступа к элементу таблицы по ключу (строковому, если на вход поступает число то оно преобразуется в строку).
Пример доступа посредством квадратных скобок (оба примера эквивалентны):
table[“var1”];
или:
var namevar = “var1”;
table[namevar];
В квадртаных скобках может быть выражение.
Обращение к элементу таблицы через точку подразумевает обращение по имени.
var.name эквивалентно var[“name”].
Обращение к элементу таблицы по индексу осуществляется посредством var:[index]. При попытке обращения по индексу без указания двоеточия перед квадратными скобками приведет к преобразованию индекса к строковому типу, и будет осуществлен поиск по имени. При попытке обращения по индексу, когда индекс равен количеству элементов таблицы, произойдет создание нового null элемента в конце таблицы. Обращение по индексу недопустимо тогда когда индекс больше размера таблицы.
Также возможно быстрое добавление в конец таблицы:
table[] = 10; //добавит в конец таблицы число 10
2.6 Управляющие конструкции (if, for, while, do while)
s4g поддерживает основные конструкции для управления
2.6.1 Условия и ветвления:
|
2.6.2 Цикл for:
|
Простой пример:
|
list_expressions
–
список (разделенный запятыми) простых выражений, в основном используется как инициализация
основных данных для цикла. Выполняется до начала цикла.
condition
– условие
выполнения, обращение к условию происходит перед каждой итерацией.
list_expressoins_steps
-
список (разделенный запятыми) простых выражений, в основном используется для
приращения количества шагов итераций (как в примере ++i
).
Цикл останавливается тогда, когда условие condition
возвращает false
, то есть ложь.
2.6.3 Циклы while и do while:
|
condition
- условие
выполнения.
В цикле while
-
обращение к условию происходит перед каждой итерацией.
В цикле do while
-
обращение к условию происходит после каждой итерации.
Цикл останавливается тогда, когда условие condition
возвращает false
, то есть ложь.
2.7 Массивы
Массивы в s4g являются набором анонимных данных. Нумерация с 0.
Создание массива:
var array = [“a”, 2, 3];
Обращение к элементу массива осуществляется через [] (квадратные скобки) в которых указывается числовой индекс ключа, если на вход поступает строка то она будет преобразована в число.
2.8 Классы
s4g поддерживает классы и наследование.
Важно! Наследование от экспортированных из C++ классов недопустимо!
Пример создания класса:
class A
{
var m_a;
function __constructor()
{
this.a = 10;
}
function __destructor()
{
}
function method()
{
return this.m_a;
}
};
Все переменные и методы класса публичны. В каждый метод класса передается скрытый аргумент this который является объектом текущего класса, доступ ко всем данным класса осуществляется только через него.
Поддерживаются: один конструктор и один деструктор. Конструктор может принимать аргументы, а деструктору они не нужны.
Наследование осуществляется следующим образом:
class B extends A
{
function method()
{
return 0;
}
};
При этом происходит наследование всех родительских методов и переменных, однако в случае переобъявления будут доступны только дочерние данные.
Конструктор и деструктор вызываются только дочерние.
2.9 Функции
Функции в s4g являются неотъемлемым элементом разделения кода. Являясь инструкцией, функция содержит в себе тело исполнения, которое является инструкциями.
Функции могут быть анонимными.
2.9.1 Объявление функций
Синтаксис объявления функции:
function func(arg1,arg2)
{
//body
};
Вариант присваивания переменной:
var func = function (arg1,arg2)
{
//body
};
Присваивание значения функции переменной является составным выражением и по сути является самостоятельной конструкцией.
Функция может создаваться внутри функции и так далее.
Имена аргументов функции должны быть уникальны в пределах этой функции.
Объявление функции - исполняемая операция, поэтому результатом будет объект функции со всеми необходимыми данными и скомпилированным телом исполнения.
Возвращать функция может только одно значение (вообще даже если функция не возвращает значений по коду, машина сама вернет null).
Аргументы функции инициализируются при ее вызове.
Аргументы в функции передаются по ссылке, то есть можно записывать результат работы непосредственно в аргументы, и это будет доступно вне функции, там где значения переданные аргументам будут доступны. Однако записывать в языковые данные - нельзя.
Пример недопустимого кода:
function qwert(a1,a2){
a1 = 10;a2 = 20;
}
qwert(1,2);
2.9.2 Функции с переменным количеством аргументов (мультиаргументность)
s4g поддерживает функции с переменным количеством аргументов.
Пример функций:
function func(…)
{
var arg0 =
args:[0];
var arg1 = args:[1];
}
Многоточие (…) сообщает парсеру что данная функция может принимать неопределенное количество аргументов. Для доступа ко всем аргументам которые не определены необходимо воспользоваться встроенной в каждую такую функцию переменной args которая является таблицей, в которую поочередно записаны аргументы.
Определять аргументы можно только до многоточия:
function func(arg1,arg2, …){};
2.9.3 Замыкания
Поддержка замыканий явная, посредством четкого указания скриптером данных которые функция должна копировать (в специальный внутренний контекст, уникальный и константный для каждой функции с замыканием). Замыкание возможно только для функции, которая присваивается переменной (составное выражение).
Пример функции с замыканием:
var a = 10;
var b = 20;
var func_closure = function() extern a,b
{
//body func
};
Данные берутся из всех доступных на данный момент контекстов. В замыкание могут быть включены только переменные (непосредственные имена). К примеру, данные из таблиц вида tab.a указывать нельзя.
2.9.4 Вызов функции
Синтаксис:
name_func(arg_list);
Для начала необходимо указать имя функции. Сама функция может находиться в любом доступном пространстве имен, массиве, классе. Правила обращения к функции (для вызова) в таблицах стандартны.
Далее следует открывающая скоба, после нее список аргументов через запятую, где аргументом может быть любое выражение, после аргументов закрывающая скобка.
По умолчанию каждая функция возвращает значение. Если этого не происходит, то при завершении работы функции она возвращает null.
Возвращать можно только одно значение.
s4g поддерживает рекурсивные вызовы функций. Максимальная вложенность вызовов регулируется макроопределением S4G_MAX_CALL которое определено в файле s4g.h. На стек вызовов исполняющей программы это никак не влияет, виртуальная машина s4g обеспечивает независимость своего стека вызовов.
2.10 Области видимости (контексты, пространства имен, блоки)
2.10.1 Пространства имен
Пространством имен является таблица. Таблица в свою очередь может быть контекстом, который не доступен для управления скриптеру, и значением, которым может манипулировать скриптер.
Изначально в s4g существует 2 пространства имен: языковое (нулевое), и глобальное (первое).
Языковое пространство имен содержит стандартную библиотеку с данными (функции и значения) и доступно из любого контекста непосредственно. Также в это пространство имен на стороне хост программы (управляющего скриптовой системой приложения) можно экспортировать необходимый функционал.
Глобальное пространство имен, доступно из любого участка кода (вне зависимости от контекста исполнения) через таблицу «_g» (по умолчанию), это значение определено в дефайне S4G_GLOBAL_NM в файле s4g.h. Пример:
_g. a = 10;
Глобальное пространство имен доступно непосредственно в глобальном контексте исполнения (в данном случае это одинаковые понятия). Вышеприведенный пример аналогичен:
var a = 10;
Глобальное пространство имен доступно из локальных контекстов только посредством таблицы «_g». Пример:
var a = 10;
if(a > 0)
{
_g.a = 11;
}
В случае попытки переопределения языковых функций/данных произойдет ошибка.
2.10.2 Контексты
Контекстом является таблица с данными, которая не доступна скриптеру для управления и принадлежит какой либо функции, конструкции, блоку. Контексты создаются и активируются/деактивируются виртуальной машиной.
Каждый вызов функции, каждая управляющая конструкция создают свои контексты (по одному на единицу).
Контекст функции (при вызове функции) блокирует все предыдущие контексты, и в ходе исполнения тела функции эти заблокированные контексты становятся недоступными, так же как и их данные.
2.10.3 Блоки
Исключен в 0.9.2
2.10.4 Области видимости
Обьявление переменной осуществляется с помощью ключевого слова var. При обьъявлении происходит поиск такой же переменной в текущем контексте, если она найдена - генерируется ошибка, в противном случае в текущем контексте создается новая переменная. При обращении к переменной происходит поиск во всех доступных контекстах, если при этом переменная найдена не была - генерируется ошибка.
Допустимые формы использования:
var a;
var a, b, c;
var a = 1;
var a, b = 1;
var a = 1, b = 1;
С таблицами аналогичная ситуация однако поиск и создание будут происходить только в таблице.
В нижеприведенном примере, после выхода из условия значение переменной a будет 11:
var a = 10;
if(a > 0)
{
a = 11;
}
Переменные созданные в конструкциях (if, for, while, do while), и функциях, не доступны вне их.
var a = 10;
if(a > 0)
{
var a
= 11;
print(a)
}
Этот код напечатает '11', но при выходе из условия, значение переменной a останется 10
2.11 Обработка ошибок
Работа s4g скрипта начинается с момент вызова (исполнения тела скрипта либо функции) и заканчивается только по возвращении управления от скрипта к хост программе.
Все ошибки возникающие в процессе работы должны обрабатываться хост программой.
Помимо ошибок существуют и другие виды сообщений: предупреждения (не являются критичными к исполнению) и простые уведомительные сообщения.
В скриптах доступна функция assert(cond,text) которая генерирует ошибку, в случае если cond ложное значение (false, 0, null) и выводит ошибку с текстом сообщения text.
Для генерации ошибки без проверки можно заведомо передавать в cond false, 0, null.
При ошибке дальнейшая работа со скриптовой системой становится невозможной , за исключением анализа данных стека исполения и стека аргументов. Однако сбросить состояние ошибки возможно посредством s4g_MainClear функции, которая очистить всю скриптовую систему (как будето скриптовая система только что создана), константный контекст не затронет.
2.12 Сборщик мусора
s4g осуществляет управление памятью. Все выделение памяти зависит от начальных настроек резервирования и кода исполняемых скриптов. Очистку и освобождение памяти производит сборщик мусора. Для вызова полного цикла сборки мусора необходимо на стороне хост программы вызвать функцию s4g_GCcall передав экземпляр скриптовой системы, в которой необходимо произвести очистку.
Чтобы осуществить сборку мусора из кода скрипта необходимо вызывать функцию call_gc без аргументов.
Сборищк мусора работает с данными на основании достижимости переменных и данных из глобального пространства имен и текущих доступных контекстов.
Существуют фиксированные данные, которые были созданы на этапе парсинга и компиляции, при сборке мусора такие данные не анализируются, что ускоряет процесс сборки мусора.
Все фиксированные данные могу быть удалены только в случае очистки скриптовой системы (s4g_MainClear) (для загрузки нового скрипта), либо в случае удаления скриптовой системы.
Сборщик мусора обрабатывает все типы данных кроме S4G_TYPE_PDATA.
2.13 Препроцесс
Директивы препроцессора - строки кода, начинающиеся с символа решетки #. Эти строки не являются программными выражениями, и обрабатываются препроцессором. Препроцессор осуществляет обработку кода до компиляции и выполняет все подстановки.
Директива препроцессора располагается в одной строке и заканчивается символом конца строки. В конце директивы не требуется точка с запятой (;). Чтобы расширить директиву на более чем одну строку - нужно поместить в конце строки обратный слеш (\).
Макроопределения (#define, #undef)
Чтобы задать определение для препроцессора, используется директива #define, ее синтаксис:
#define identifier replacement
Когда препроцессор встречает эту директиву, он заменяет все встреченные identifier на replacement далее по тексту программы. replacement может быть выражением, конструкцией, блоком, или вообще ничем. Препроцессор просто заменяет identifier на replacement.
#define SIZE 100
var some_size = SIZE;
var some_more_size = SIZE + 10;
После обработки препроцессором код будет выглядеть так:
var some_size = 100;
var some_more_size = 100 + 10;
#define так же может принимать параметры для определения макроса:
#define getsum(a,b) a+b
Это заменит все встреченные getsum с двумя аргументами на выражение замены, и каждый аргумент на его значение.
#define getsum(a,b) a+b
var $a = getsum(1,2);
после подстановки станет:
var a = 1+2;
Заданный макрос не зависит от структуры блоков. Он существует до тех пор, пока не будет отменен с помощью директивы #undef
#define SIZE 100
var some_size = SIZE;
#undef SIZE
#define SIZE 200
var some_more_size = SIZE;
сгенерирует такой же код, как
var some_size = 100;
var some_more_size = 200;
Функциональные макросы так же поддерживают два специальных оператора (#, ##) в выражении замены.
Оператор #, стоящий перед именем параметра, заменяется на строковый литерал, содержащий переданный аргумент.
#define str(x) #x
print(str(test));
будет преобразовано в
print("test");
Оператор ## соединяет два аргумента, не оставляя пробелов между ними:
#define concat(a,b) a ## b
concat(pri, nt)("text");
Будет преобразовано в
print("text");
Так как препроцессорные замены осуществляются до проверки синтаксиса s4g, они позволяют осуществлять различные хитрые вещи. Но будьте осторожны, большие сложные макросы сложно читать человеку, и в них трудно будет найти ошибку.
Условные включения (#ifdef, #ifndef, #if, #endif, #else)
Эти директивы позволяют включать либо исключать части кода из компиляции, в зависимости от выполнения каких-то условий.
#ifdef позволяет участку кода скомпилироваться только в том случае, если макрос, указанный в параметре, определен, в не зависимости от его значения:
#ifdef SIZE
a = SIZE;
#endif
В этом примере, строка кода a = SIZE; будет скомпилирована только в том случае, если макрос SIZE был предварительно определен с помощью #define, независимо от его значения.
#ifndef является противоположностью #ifdef - код будет скомпилирован только если макрос не определен:
#ifndef SIZE
#define SIZE 100
#endif
a = SIZE;
В этом примере, макрос SIZE определяется только в том случае, если он не был ранее определен.
Директивы #if, #else служат для задания специальных условий. Условные выражения должны включать только константные значения, включая макросы:
#if SIZE>200
#undef SIZE
#define SIZE 200
#else
#if SIZE<50
#undef SIZE
#define SIZE 50
#endif
#else
#undef SIZE
#define SIZE 100
#endif
var a = SIZE;
Поведение #ifdef и #ifndef так же может быть достигнуто с помощью операторов defined() и !defined() в директиве #if
#if defined(ARRAY_SIZE)
#define SIZE ARRAY_SIZE
#else
#if !defined(BUFFER_SIZE)
#define SIZE 128
#else
#define SIZE BUFFER_SIZE
#endif
#endif
Включения файлов кода (#include)
Когда препроцессор встречает директиву #include, он заменяет ее на содержимое файла, указанного в аргументе.
#include "file"
Поиск осуществляется относительно текущей директории файла с кодом, а так же в заданных путях поиска.
3 Интерфейс программирования приложений (API)
В данной главе рассматривается C(++) API s4g, посредством, которого происходит доступ к внутренней среде и возможностям языка. А также описываются некоторые моменты работы s4g которые позволят грамотно использовать возможности s4g.
3.1 Стек
Тип виртуальной машины стековая. Общение между хост программой и средой s4g осуществляется посредством стека. В данной версии через стек исполнения виртуальной машины.
Размер стека исполнения ограничен значением дефайна S4G_VM_MAX_SIZE_STACK_EXE который объявлен в s4g.h, по умолчанию 1000, при превышении этого количества элементов в стеке будет выдана соответствующая ошибка.
Ограничение стека исполнения сделано для того чтобы избежать как ошибок программистов хост программы, так и ошибок разработчиков данной машины.
В то же время инструкции такого большого объема могут свидетельствовать о возможно неверном подходе к разработке скрипта.
Переполнение стека исполнения может говорить о неверной проектировки взаимодействия хост программы со скриптами.
Данный стек может принимать только значения типа s4g_Variable. Однако объекты данного типа могут содержать в себе все то, что может содержать код скрипта. И API s4g предоставляет для этого необходимые возможности.
Также виртуальная машина содержит еще дополнительный стек вызовов, который служит для хранения данных для вызова и возврата. Данный стек имеет ограничения по размеру, которое регулируется дефайном S4G_MAX_CALL определенным в s4g.h и означает максимальное количество вызовов (как рекурсивных, так и вложенных), по умолчанию 1000. При превышении этого количества будет выдана соответствующая ошибка. Если Вам нужно большее количество увеличивайте это значение.
Виртуальная машина содержит еще и стек блоков, который не имеет границ (кроме памяти). Данный стек работает (в правильном режиме) по классическому понимаю стека.
Индексация стека начинается с нуля. Реализация стеков не придерживается классического правила стеков (первый зашел – последний вышел). Положительный индекс это абсолютное положение в стеке, отрицательный индекс это позиция элемента относительно вершины стека, то есть -1 это вершина стека, -2 на один элемент ниже вершины и так далее.
3.2 Резервация памяти
s4g во время своей инициализации производит первоначальную резервацию памяти для всех необходимых (в начальном положении) данных, дополнительное расширение памяти происходит по ходу работы виртуальной машины.
Определение значений этих данных может сыграть значительную роль в производительности.
Вся информация и дефайны для резервации находятся в файле s4g.h.
3.3 Сообщения
Все сообщения, генерируемые скриптовой системой (лексер, парсер, компилятор, виртуальная машина, API и прочее) или скриптером (для системы) выводятся посредством общего вывода – через функцию, которую можно переназначить.
s4g поддерживает 3 вида сообщений, они определены в файле s4g.h:
//уровни сообщений
#define S4G_NOTICE 0 /* уведомление */
#define S4G_WARNING 1 /* предупреждение */
#define S4G_ERROR 2 /* ошибка */
Тип функции вывода сообщений определен в файле s4g.h:
typedef void(*s4g_report_func) (s4g_Main* s4gm, int level, const char* format, ...);
Стандартная функция (по умолчанию) осуществляет лишь вывод в стандартный поток, однако ее можно переназначить через функцию расположенную в s4g.h:
void s4g_SetRf(s4g_report_func fnRf);
Для генерации сообщения в скриптовую систему можно воспользоваться функцией:
void s4g_GenMsg(s4g_Main *s4gm, int iLevel, const char *szFormat, ...);
3.4 Типы данных
В s4g.h можно переопределить стандартные поддерживаемые типы. По умолчанию они:
#define s4g_int long
#define s4g_uint unsigned long
#define s4g_float float
#define s4g_bool short int
#define s4g_pdata void*
Виртуальная машина s4g поддерживает вызов C(++) функций, то есть возможность экспортировать в скриптовую систему функции из хост программы для исполнения на стороне скриптов. Однако эти функции должны соответствовать следующему типу:
typedef int(*s4g_c_function) (s4g_Main *s4gm);
Функции данного типа принимают на вход один единственный аргумент это объект скриптововй системы. В этой вызванной функции можно манипулировать скриптовой системой посредством API функций.
Если функция возвращает значение #S4G_ERROR значит произошла ошибка и машина остановит выполнение кода, если #S4G_OK значит, функция успешно отработала
3.5 Функции
Документацию по функциям смотрите на сайте с документацией. Либо в локальной версии ссылка.
4 Библиотеки
4.1 Стандартная библиотека
uint time()
возвращает текущее время работы скриптовой системы в миллисекундах
void print(string)
выводит в стандартный поток вывода строку
int system(string)
аналогично C++ функции system
void assert(var, string)
генерирует ошибку (текст во втором аргументе) если поступивший аргумент false либо null
int strlen(string)
возвращает длину строки в символах
int count(table or array)
возвращает размер таблицы/массива в элементах
void call_gc()
вызов сборщика мусора
bool function_exists(string)
доступна ли функция из текущего вызова
void call_func(string, ...)
вызов функции по ее имени в строковом представлении с передачей аргументов
int toint(var)
возвращает значение преобразованное в тип int
uint touint(var)
возвращает значение преобразованное в тип uint
float tofloat(var)
возвращает значение преобразованное в тип float
bool tobool(var)
возвращает значение преобразованное в тип bool
string tostring(var)
возвращает значение преобразованное в тип string
int type(var)
возвращает идентификатор (число) типа переменной
string str_type(var)
возвращает строкове представление типа переменной
bool isnull(var)
является ли переменная в аргументе типом null
bool isnum(var)
является ли переменная в аргументе числом
bool isint(var)
является ли переменная в аргументе типом int
bool isuint(var)
является ли переменная в аргументе типом uint
bool isfloat(var)
является ли переменная в аргументе типом float
bool isbool(var)
является ли переменная в аргументе типом bool
bool isstring(var)
является ли переменная в аргументе типом string
bool istable(var)
является ли переменная в аргументе типом table
bool ispdata(var)
является ли переменная в аргументе типом pdata
bool iscfunc(var)
является ли переменная в аргументе типом cfunc
bool issfunc(var)
является ли переменная в аргументе типом sfunc
bool isclass(var)
является ли переменная в аргументе типом class
bool isclassobject(var)
является ли переменная в аргументе типом class_object
4.2 Библиотека для работы с файлами
string file.read(string path)
считывает файл в строку и возвращает ее
bool file.save(string path, string text)
сохраняет text в файл path
bool file.exists(string path)
существует ли файл path
int file.size(string path)
возвращает размер файла path
4.3 Библиотека для работы со строками
string string.find(string src, string pattern, int start_pos=0)
возвращает первое вхождение pattern в src
string string.format(string format, ...)
возвращает форматированую строку, первым аргументом идет входная строка, далее в соответствии с модификаторами. Может иметь следующие модификаторы:
· %s - строка
· %i - int
· %u - uint
· %b - bool
· %f – float
string string.sub(string src, start, len=0)
возвращает вырезанную строку из src с позиции start и по количеству символов len (если <=0 тогда до конца строки)
string string.upper(string str)
возвращает копию str, где все маленькие буквы меняются на большие
string string.lower(string str)
возвращает копию str, где все большие буквы меняются на маленькие
array string.explode(string str, string delimiter, int count=0)
возвращает массив строк, полученных разбиением строки str с использованием delimiter в качестве разделителя, количество делений count (если <0 то делит до конца строки)
5 Полный синтаксис в РБНФ
Буква = Aa - Zz
Цифра = 0-9
Идентификатор = ['$'](Буква | '_') {Буква | Цифра | '_'}
Натуральное число = Цифра {Цифра}
Знаковое целое число (или int) = Натуральное число
Беззнаковое целое число (или uint) = Натуральное число[.u]
Число с плавающей запятой (или float) = int.
[ ({[0-9]} | f)]
Символы_строковых_литералов = "Все возможные символы в том числе и экранированные"
Строковые_литералы = ("'" Символы_строковых_литералов "'") | ('"' Символы_строковых_литералов '"')
Оператор_присваивания = Бинарный_оператор_присваивания | Бинарные_арифметические_операторы_с_присваиванием
Операция_присваивания_полная = TLVal Оператор_присваивания TRVal
Операция_присваивания_ограниченная = TLVal Оператор_присваивания TRSVal
Левое_значение = (['var'] Идентификатор) | Обращение_к_полю_класса | Обращение_к_элементу_таблицы | Обращение_к_элементу_массива
Правое_значение = ('(' Выражение ')' | Скаляр | Создание_таблицы | Создание_массива | Определение_анонимной_функции | Создание_объекта_класса | Вызов_функции | Левое_значение)
[Neg | Not] ((Lparen Expr Rparen) | Int | (Function | Lvalue))
Expr: (Lvalue Assign Expr) | (Rvalue [Op Rvalue]*)
RVal = [Унарная_инверсия_знака | Унарный_оператор_отрицания] (('(' LValOp ')') | (LVal Call) | Идентификатор)
LVal = (RVal (('.' Идентификатор) | ('[' Выражение ']')))
LValOp = (Унарные_арифметические_операторы_с_присваиванием LVal) | (LVal [Унарные_арифметические_операторы_с_присваиванием])
TRVal = TSRVal | Обьявление_функции | Обьявление_таблицы | Обьявление_массива | Создание_объекта_класса
TLVal = (['var'] Идентификатор) | LVal
TSRVal = Константа | RVal
CLSid = Идентификатор | RVal
Выражение = TRVal {Оператор TRVal}
Call = '(' [Список_выражений] ')'
Константа = int | uint | float | string
Оператор = Бинарные_арифметические_операторы | Бинарные_операторы_сравнения | Бинарные_логические_операторы
Инструкция_блока = Инструкция | Управляющие_конструкции | Оператор_прерывания_цикла | Оператор_игнорирования | Оператор_возврата
Тело_функции = '{' {Инструкция_блока} '}'
Блок = (Тело_функции) | Инструкция_блока
Список_выражений = Выражение {',' Выражение}
Бинарные_арифметические_операторы = '+' | '-' | '*' | '/' | ''
Бинарные_операторы_сравнения = '==' | '<' | '<=' | '>' | '>=' | '!=' | '===' | '!=='
Бинарные_логические_операторы = '&&' | '||'
Бинарный_оператор_присваивания = '='
Унарные_арифметические_операторы_с_присваиванием = '++' | '–'
Бинарные_арифметические_операторы_с_присваиванием = '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '~=' | '^=' | '|=' | '<<=' | '>>='
Унарная_инверсия_знака = '-'
Унарный_оператор_отрицания = '!'
Обьявление_таблицы = '{' (((Операция_присваивания_полная | Обьявление_функции) ';') | Обьявление_класса)'}'
Обьявление_массива = '[' [Список_выражений] ']'
(PARSER_NODE_CLASS_VAR)Переменная_класса = 'var' Идентификатор ';'
(PARSER_NODE_CLASS)Обьявление_класса = 'class' Идентификатор '{' {Переменная_класса | Обьявление_функции | Обьявление_класса} '}' ';'
(PARSER_NODE_FUNCTION)Обьявление_функции = "function" [Идентификатор] "(" [Список_идентификаторов] ")" ["extern" (Идентификатор | Список_идентификаторов)] Тело_функции
Управляющие_конструкции = if | for | while
Идентификатор = (Буква | '_') {Буква | Цифра | '_'}
Создание_объекта_класса = 'new' CLSid [Call]
if = 'if' '(' Инструкция_для_конструкций ')' Блок ['else' Блок]
for = 'for' '(' {Инструкция_для_конструкций ,} ';' Инструкция_для_конструкций ';' Инструкция_для_конструкций ')' Блок
while = 'while' '(' {Инструкция_для_конструкций } ')' Блок
Оператор_прерывания_цикла = "break" [uint] ";"
Оператор_игнорирования = "continue" ";"
Оператор_возврата = "return" Выражение ';'