В рамках одного из моих SDK-проектов нам понадобилось добавить скриптование, которое бы оказало наименьший эффект на размер конечного бинарного файла, но при этом предоставляло хорошую фунциональность. Это дало старт проекту, который описан в этой статье. Прошу заметить, что т.к. в SDK у нас есть определенные требования, они частично перенеслись на язык скриптования, поэтому в проекте не участвовали некоторые достаточно известные встраиваемые ЯП (но Lua включен для сравнения).
Сайт проекта доступен по ссылке. Скажу сразу, что на данный момент для меня лично победителями являются Chibi-Scheme и Wasm3. Подробности для заинтересовавшихся под катом.
Базовые требования были следующие:
- Максимальная переносимость (только C/C++).
- Достаточно строгая типизация (не позволяющая складывать апельсины с яблоками), соответственно реализации ECMAScript не рассматривались.
- Лицензия, дружелюбная к коммерческим проектам.
Отдельным пунктом было требование к синтаксису ЯП по дружелюбности к транформации кода. Тут может возникнуть много вопросов, поэтому сразу уточню несколько моментов:
- Можно трансформировать и байт-код (если он поддерживается), это могло бы убрать требования к изначальному синтаксису. Однако, в общем случае это тяжелее сделать, плюс формат байт-кода может быть проприетарным или нестабильным.
- Обязательная трансформация, которая нам нужна, это очистка кода от отладочной и неиспользуемой функциональности. Это служит не только минификации, но и убирает некоторые векторы атак;
- Необязательная, но желательная трансформация — минификация приватных символов, это нужно больше для обфускации и минификации;
- Необязательная, но желательная трансформация — полное удаление переносов строк, служит больше для минимизации.
Первоначальный список учитывал только ЯП, которые поддерживают все требования (включая необязательные), но был расширен в соответствии с пожеланиями сообщества.
Изначальный список был взят с проекта, в котором они пытаются учесть все возможные встраиваемые ЯП: https://github.com/dbohdan/embedded-scripting-languages Возможно, список не полный, и не содержит дополнительные ЯП, которые могли бы удовлетворять вышеуказанным требованиям. Если вы знаете о таких, пожалуйста, дайте мне знать либо в личку, либо комментарием.
Тестовый код для всех ЯП включал в себя создание и вызов функции, которая складывает "Hello, " и результат вызова внешней (C/C++) функции "read", возвращающей "world". Код функции представлен в таблице как образец синтаксиса. Конечный исполняемый файл скомпилирован с оптимизацией по размеру и с удалением всех ненужных символов (GCC -s
). Значения RSS снимались во время выполнения "read()".
Платформа: Linux amber 5.9.1-arch1-1 #1 SMP PREEMPT Sat, 17 Oct 2020 13:30:37 +0000 x86_64 GNU/Linux
ЯП | Размер ELF (KiB) | RSS (KiB) | Пример скрипта |
---|---|---|---|
TinyScheme | 84 | 3048 | (define fn(lambda () (string-append "Hello, " (read)))) |
Wasm3 | 159 | 6024 | N/A [1] |
MicroPython | 186 | 1968 | import builtins; fn = lambda: 'Hello, ' + builtins.read() |
Lua | 247 | 1760 | function fn() return "Hello, " .. read() end |
Chibi-Scheme | 259 | 3828 | (define fn(lambda () (string-append "Hello, " (read)))) |
Squirrel | 270 | 1808 | function fn() { return "Hello, " + read(); } |
ArkScript | 443 | 1760 | (let fn(fun() (+ "Hello, " (read)))) |
Gravity | 500 | 3524 | extern var read; func fn() { return "Hello, " + read(); } |
Janet | 525 | 4152 | (defn fn[] (string "Hello, " (read))) |
ChaiScript | 1342 | 4912 | def fn() { return "Hello, " + read(); } |
AngelScript | 1878 | 4716 | string fn() { return 'Hello, ' + read(); } |
Python | 3543 | 9320 | import usermod; fn = lambda: 'Hello, ' + usermod.read() |
[1] Wasm3 работает с WebAssembly, поэтому пример кода привести нельзя (это будет бинарный дамп). Но приведу пример исходника Rust, который используется для генерации этого бинарного файла (_m3
-функции используется для обмена данных с интерпретатором):
#[no_mangle]
pub fn r#fn() -> i32 {
let read_str = load_m3(unsafe { read() });
return store_m3(&format!("Hello, {}", read_str));
}
Описание и документация по этим ЯП приведены в GitLab-проекте, здесь же хочу поделиться своими впечатлениями:
- TinyScheme реализует лишь подмножество R5RS, используется в GIMP. Если бы не было Chibi-Scheme с полноценной поддержкой R7RS, возможно стал бы победителем. Также немного смущает хостинг на SourceForge используя SVN репозиторий с одной только trunk-веткой.
- ChaiScript очень интересный — наверное самый простой по удобству встраивания, плюс поддерживается крупными компаниями. К сожалению, тот факт, что он состоит только из заголовочных файлов, очень сильно замедляет компиляцию (мой Dell XPS 13 компилирует минимум 2-3 минуты). Также, он достаточно сильно увеличивает размер исполняемого файла.
- Gravity может понравиться любителям Swift. Достаточно интересный ЯП, но к сожалению, не так много документации. Ориентирован на мобильную разработку.
- Squirrel достаточно компактный, но достаточно сложно встраивать (любая ошибка в работе со стеком вызовов приводит к краху приложения — у них недостаточно хорошая система обработки ошибок).
- MicroPython не очень дружелюбен к встраиванию, надо либо ориентироваться, что во время компиляции он сгенерирует
main()
для вашего проекта, либо писать достаточно большие Makefile-ы. Документация по встраиванию тоже весьма скудна.
Победителя два:
- Wasm3, т.к. позволяет использовать разные ЯП для написания скриптов, и достаточно компактный. К сожалению, сам по себе формат WASM достаточно небогатый (например, нет поддержки строк и сборщика мусора), и хотя WASI улучшает ситуацию, многое приходится писать самим или полагаться на рантайм, добавляемый компиляторами. Также для тестового кода я реализовал небольшой менеджер памяти, чтобы обмениваться данными между песочницой и интерпретатором, ничего встроенного wasm3 пока не имеет.
- Chibi-Scheme, т.к. поддерживает современный компактный Scheme (R7RS) и имеет достаточно большую дополнительную библиотеку (плюс стандартная библиотека Scheme и возможность использовать уже написанный код). Естественно, встраивание библиотек увеличит конечный код, но даже без библиотек он может полноценно работать (в тестовом коде я не встраивал
init-7.scm
полностью, а только добавил процедуруstring-append
).
Встраивание внешних процедур достаточно простое (хотя я вставил лямбду, Chibi-Scheme полноценно работает с чистым C):
res = sexp_define_foreign(*ctx, sexp_context_env(*ctx), "read", 0,
[](sexp ctx, sexp self, sexp_sint_t n) -> sexp {
return sexp_c_string(ctx, "world", -1);
});
Из интересных дополнительных модулей хотел бы отметить:
- (chibi crypto rsa) — RSA-шифрование
- (chibi json) — операции с JSON
- (chibi net http-server) — HTTP-сервер
К сожалению, документация небогатая, но компенсируется доступом ко всему исходному коду. Плюс, Chibi-Scheme известен в своих кругах, и не будет потенциально брошен или переведен в режим минимальной поддержки, как некоторые другие потенциальные кандидаты.
Буду рад услышать все комментарии, и рассмотреть другие ЯП в рамках вышеупомянутых требований. Исследование до сих продолжается, и возможно победитель сменится, но это уже будет зависеть от вас.
rmrfchik
Chicken не рассматривали?
nuald Автор
Chiken требует сам себя, и я не нашел исходники bootstrap-версии, чтобы скомпилировать первичный компилятор. В принципе, я не против многофазовой сборки, но без первичной С-версии это не удовлетворяет требованию максимальной переносимости.