LizardScript - язык программирования:
- Скриптовый - LizardScript создан для взаимодействия с программами, написанными на C++. Он имеет наибольшее право называться скриптовым.
- Объектный - с помощью генератора метаданных METAGEN и скрипта make_metadata.py (спасибо Лёхе) из классов C++ извлекаются метаданные членов, и они становятся видимы в контексте LizardScript. Объявлять собственные классы в данный момент невозможно.
- Динамический - во время выполнения кода LizardScript можно компилировать новые функции с исходным кодом, заданным строковыми переменными.
Программист на LizardScript называется скриптолиз.
FAQ: Зачем ещё один скриптовый язык?
- А скриптовый язык, пользующийся классами С++ как родными?
| Релиз первой официальной версии: | 11.10.2019 |
| Автор: | killer-nyasha |
| Расширение файлов: | .ls |
| Последняя версия: | v0.1.0 |
| Релиз последней версии: | 11.10.2019 |
| Система типов: | Слабая статическая |
Экспериментировать с LizardScript можно в программе LizardScriptInteractive. При запуске с параметром командной строки она выполнит скрипт из файла, заданного параметром. Иначе откроется интерактивный режим, в котором можно вводить скрипты и сразу же видеть результат.
В LizardScriptInteractive введены метаданные следующих классов С++:
struct B
{
int x = 1;
float f = 1;
B* b = nullptr;
B() { }
};
struct A
{
int i = 10;
float f = 20.0f;
int k = 30;
A* a = nullptr;
B* b = nullptr;
void test()
{
f *= 2;
}
void test2(int a, int b)
{
f = a + b;
}
A()
{
i = 100;
}
};this - объект класса A.
Внимание: каждая строка (физическая, не строка кода), введённая в интерактивном режиме - это новая функция. Поэтому локальные переменные, объявленные в одной строке, в следующей уже не будут существовать. Все действия с локальной переменной должны быть в той же строке. Состояние объектов классов и их полей сохранятеся между строками.
Первый скрипт:
lsl.print("Hello world!");- Токен - это любое смысловое «слово» языка: оператор, литерал или идентификатор.
- Строки кода разделяются точкой с запятой.
- Фигурные скобки позволяют объявить блок кода
{}. Блок кода эквивалентен одной строке. - Разрывы строк и табуляция в исходном коде интерпретируются как пробелы.
Разрывы строк и знаки табуляции интерпретируются как пробелы.
Внимание: в данной версии все не-ascii символы кроме комментариев и строковых литералов выводят предупреждение и интерпретируются как пробелы.
Доступны комментарии в стиле С++:
- От символов
//до конца строки - От символов
/*до символов*/
Внимание: в отличие от С++ многострочные комментарии поддерживают вложенность.
Переменные объявляются почти так же, как в С++:
<тип> <имя>
Где <тип> и <имя> - токены.
int myInt;Можно сразу же присвоить ей значение:
int myInt = 1;Интересно, что объявить переменную можно прямо внутри выражения, тут же использовав её значение:
lsl.print(int myInt = 1);Область видимости локальной переменной заканчивается при выходе из ее блока кода. При совпадении имён более новые определение в своей области действия перекрывают старые.
int a = 15;
{
int a = 25;
lsl.print(a); //25
}
lsl.print(a); //15В LizardScript есть три способа задать строку.
$<токен>Самый короткий, но самый ограниченный способ. Возник ненамеренно. Подходит только для строк, состоящих из одного токена."<любые символы>". Всем привычный способ. Не будет работать, если строка содержит кавычки.[<любые символы>]. Самый продвинутый, но непривычный способ. Позволяет строке включать и кавычки, и корректно закрытые квадратные скобки, поэтому особенно удобно для хранения исходного кода в переменной. Примеры:
stringptr source = [ lsl.print([hello world]); ];
stringptr source = [ lsl.print("hello world"); ];
stringptr source = [ lsl.print($hello); ];LizardScript может вызывать функции-члены классов C++. Поддерживается перегрузка функций. Простейший пример - функция lsl.print. На самом деле это несколько перегрузок для разных типов.
Внимание: порядок проверки, подходит ли каждая из перегрузок функций к типам аргументов, зависит от порядка передачи метаданных этих перегрузок в METAGEN. Для простоты можно считать, что это зависит от воли рептилоидов, и избегать в этой версии перегрузок, вызывающих неоднозначности в выборе.
lsl в примерах выше - это ключевое слово, которое возвращает указатель на объект класса LizardScriptLibrary.
В этот класс помещены системные функции, которые могут понадобиться в коде на LizardScript.
void print(int);
void print(float);
void print(stringptr);
void eval(void* ths, stringptr type, stringptr source);Три перегрузки print выводят свой входной параметр на консоль. Всё сложнее с функцией eval - она во время выполнения вызывает компилятор LizardScript, динамически компилируя и выполняя код.
lsl.eval(this, "A", [
lsl.print("Hello world!");
lsl.print("from eval =)");
]);В данном примере компилятор для объекта this типа A выполняет код в квадратных скобках. Весь промежуточный вывод компилятора при выполнении eval выводится на консоль так же, как при компиляции основного скрипта.
Объявив переменную, например, int x;, мы можем в одном из сообщений компилятора заметить, что тип выражения x - int*, а не int.
При этом, например, 10 будет иметь тип int.
Почему?
В LizardScript нет понятий lvalue и rvalue, как в C++11.
Адрес переменной, по которому мы можем что-то записать, или передать переменную по ссылке (как lvalue), имеет тип <тип>* - он является указателем на значение переменной.
Просто <тип> - тип уже извлечённого значения переменной, и не имеет адреса (как rvalue).
Таким образом, x - ещё только указатель на переменную, так как не было необходимости извлекать значение.
Почти всегда при использовании <тип>* он будет неявно преобразовываться в <тип>.
В данный момент LizardScript имеет следующие типы:
int. 32-битное знаковое целое. Задается числовым литералом.float. Число с плавающей запятой одинарной точности. Задаётся числовым литералом.std::nullptr_t. Возвращается ключевым словомnull. Невозможно объявить переменную.void. Невозможно объявить переменную.stringptr. Умный указатель наstd::string. Задается строковым литералом.- Другие типы из C++, переданные в METAGEN. Видимыми будут только те их члены, которые переданы.
- Указатели.
<тип>*, где<тип>- абсолютно любой тип (даже указатель). Возможны, например,int**илиstringptr****.
Указатели объявляются так же, как в C#. В отличие от С++ звездочка при объявлении - часть типа, а не названия переменной.
Внимание: в данной версии LizardScript тип переменной - это один токен. Звёздочка не может входить в токен, поэтому типы указателей оборачивают в "backticks":
`текст`
Все типы указателей имеют размер 4/8 байт, зависит от платформы, не зависит от типа указателя.
Правильно:
`int*` x;Неправильно:
int *x;Неправильно в данной версии, будет добавлено позже:
int* x;В данной версии нормального механизма преобразований типов нет. Доступны следующие неявные преобразования, где <тип> - любой тип:
<тип>* -> <тип>
int -> float
std::nullptr_t -> <тип>
<тип> -> voidТам, где требуется void, любой тип будет передан без реальных преобразований, как "сырые" данные.
Тип std::nullptr_t, наоборот, преобразуется в любой тип - так ключевое слово null заполняет нулями переменную любого типа.
<тип_не_указатель>* = <тип_не_указатель>
<тип_не_указатель>** = <тип_не_указатель>*
int i;
i = 5;
`int*` p = i;
p = i = 5; //возвращает свой левый аргумент, право-ассоциативноint + int
int - int
int * int
int / int
float + float
float - float
float * float
float / float
Если один из параметров int, а другой - float, int будет преобразован во float.
int < int
int > int
Возвращает int, равный единице, если выражение истинно, и нулю, если ложно.
int++
int--
++int
--int
Внимание: в данный момент любой инкремент или декремент работает как префиксный в C++.
int i = 10;
lsl.print(i++); //11
i = 10;
lsl.print(++i); //11
Как операторы, но на самом деле не совсем =)
Определяют приоритеты действий. Без скобок приоритеты операторов такие (фрагмент исходного кода компилятора):
("="), 20
(">"), 30
("<"), 30
("+"), 50
("*"), 60
("-"), 50
("/"), 60
("++"), 90
("--"), 90
Также в круглые скобки оборачиваются аргументы функций.
Определяют блоки кода, подробнее см. [Немного о синтаксисе][]
<токен1>.<токен2>
Ищет идентификатор <токен2>, принадлежащий <токен1>.
Приоритеты поиска идентификаторов Когда LizardScript встречает токен, он проверяет, является ли он оператором. Если да, то приоритеты такие:
- Унарный оператор.
- Бинарный оператор.
- Ошибка компиляции. Если нет, то
- Локальная переменная. При совпадении имён более новые определение в своей области действия перекрывают старые. Если было явно указано
this.<токен>, пропускаем этот пункт. - Функция класса
this. - Поле класса
this. lsl- Тип (возможно, это объявление переменной).
- Ошибка компиляции.
Возвращает объект, для которого запущен скрипт.
В сочетании с оператором . даёт возможность обратиться к членам класса, если есть одноимённая локальная переменная.
Возвращает объект, который при присвоении обнуляет любую переменную.
Указывает на глобальный объект LizardScriptLibrary.
Создаёт в куче новый объект. Действует аналогично оператору new из C++.
Синтаксис:
new <тип>
new <тип>(<параметры конструктора>)
`A*` a = new A;Обычно функция, импортированная из C++ через METAGEN, имеет то же имя.
Исключение - конструктор, он имеет имя ctor.
Конструктор, если он есть, вызывается после создания объекта.
Внимание: конструктор без параметров вызывается только если после имени типа добавить круглые скобки.
Внимание: в данной версии есть проблемы с импортом конструкторов с параметрами.
`A*` a = new A; //память не инициализирована
`A*` b = new A(); //вызывает A::ctor();Интересно, что конструктор является обычной функцией, возвращающей void, и его можно вызывать, например, чтобы сбросить состояние объекта.
`A*` a = new A;
a.ctor();`A*` a = new A();
a.i = 25;
a.ctor();Внимание: new без конструктора может плохо закончиться, если в объекте есть указатели. LizardScriptInteractive стремится вывести информацию о содержимом указателей, и если null он может обработать корректно, то случайное значение - нет.
Поддерживается ветвление if и цикл while. Синтаксис следующий:
if ( <условие> ) <действие>
if ( <условие> ) <действие> else <действие иначе>
while ( <условие> ) <действие>
Как условие передаётся int, считающийся истиной, когда не равен нулю.
С else if есть проблемы. Оставлю этот пример здесь, но в этой версии он неправильно работает.
if (i > 11) lsl.print(1); else if (i > 12) lsl.print(2); else lsl.print(3);Примеры
int power = 0;
if (power > 9000)
lsl.print("over 9000!!");
else lsl.print(":(");int power = 0;
while (power++ < 9000)
{
if (power > 8990)
lsl.print(power);
}
lsl.print("over 9000!!");Вложенные циклы:
int a = 4;
while (a)
{
int b = 3;
while (b)
{
lsl.print(100*a+b);
b--;
}
a--;
}Тело цикла или условия может быть и пустым.
int power = 0;
while (power++ < 9000);
lsl.print("over 9000!!");int y = 1;
int n = 100;
int x = 1;
int j = 2;
while (j < n)
{
y = x + y;
x = y - x;
j++;
}
lsl.print("fib(");
lsl.print(n);
lsl.print(") = ");
lsl.print(y);