Skip to content

Instantly share code, notes, and snippets.

@killer-nyasha
Last active October 11, 2019 14:24
Show Gist options
  • Save killer-nyasha/49bf5f05950b862d445921fb787fc061 to your computer and use it in GitHub Desktop.
Save killer-nyasha/49bf5f05950b862d445921fb787fc061 to your computer and use it in GitHub Desktop.
Мануал по LizardScript

LizardScript 🦎

Основная информация

LizardScript - язык программирования:

  • Скриптовый - LizardScript создан для взаимодействия с программами, написанными на C++. Он имеет наибольшее право называться скриптовым.
  • Объектный - с помощью генератора метаданных METAGEN и скрипта make_metadata.py (спасибо Лёхе) из классов C++ извлекаются метаданные членов, и они становятся видимы в контексте LizardScript. Объявлять собственные классы в данный момент невозможно.
  • Динамический - во время выполнения кода LizardScript можно компилировать новые функции с исходным кодом, заданным строковыми переменными.

Программист на LizardScript называется скриптолиз.

FAQ: Зачем ещё один скриптовый язык?

  • А скриптовый язык, пользующийся классами С++ как родными?
Релиз первой официальной версии: 11.10.2019
Автор: killer-nyasha
Расширение файлов: .ls
Последняя версия: v0.1.0
Релиз последней версии: 11.10.2019
Система типов: Слабая статическая

Hello world

Экспериментировать с 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!");

Немного о синтаксисе

  1. Токен - это любое смысловое «слово» языка: оператор, литерал или идентификатор.
  2. Строки кода разделяются точкой с запятой.
  3. Фигурные скобки позволяют объявить блок кода { }. Блок кода эквивалентен одной строке.
  4. Разрывы строк и табуляция в исходном коде интерпретируются как пробелы.

Разрывы строк и знаки табуляции интерпретируются как пробелы.

Внимание: в данной версии все не-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. Для простоты можно считать, что это зависит от воли рептилоидов, и избегать в этой версии перегрузок, вызывающих неоднозначности в выборе.

LizardScriptLibrary

lsl в примерах выше - это ключевое слово, которое возвращает указатель на объект класса LizardScriptLibrary. В этот класс помещены системные функции, которые могут понадобиться в коде на LizardScript.

void print(int);
void print(float);
void print(stringptr);
void eval(void* ths, stringptr type, stringptr source);

Три перегрузки print выводят свой входной параметр на консоль. Всё сложнее с функцией eval - она во время выполнения вызывает компилятор LizardScript, динамически компилируя и выполняя код.

simple eval

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 встречает токен, он проверяет, является ли он оператором. Если да, то приоритеты такие:

  1. Унарный оператор.
  2. Бинарный оператор.
  3. Ошибка компиляции. Если нет, то
  4. Локальная переменная. При совпадении имён более новые определение в своей области действия перекрывают старые. Если было явно указано this.<токен>, пропускаем этот пункт.
  5. Функция класса this.
  6. Поле класса this.
  7. lsl
  8. Тип (возможно, это объявление переменной).
  9. Ошибка компиляции.

Ключевые слова

this

Возвращает объект, для которого запущен скрипт. В сочетании с оператором . даёт возможность обратиться к членам класса, если есть одноимённая локальная переменная.

null

Возвращает объект, который при присвоении обнуляет любую переменную.

lsl

Указывает на глобальный объект LizardScriptLibrary.

new

Создаёт в куче новый объект. Действует аналогично оператору 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, else while

Поддерживается ветвление 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!!");

Вместо эпилога

fibonacci

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);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment