|
|
|
|
Язык Lua — это легковесный, интерпретируемый, высокоуровневый язык программирования. Сам интерпретатор Lua реализован на чистом ANSI C.
Он был создан с акцентом на простоту, гибкость, переносимость и лёгкую интеграцию с другими языками, особенно с C.
|
|
|
Встраиваемость (Embeddability)
Lua спроектирован как встраиваемый скриптовый язык. Это означает, что его часто используют не как самостоятельный язык для написания полноценных приложений, а как расширение или скриптовый движок внутри других программ, написанных, например, на C, C++ или другом языке.
Через C API можно:
- вызывать Lua-скрипты из C-кода,
- передавать данные из C в Lua и обратно,
- регистрировать C-функции, которые могут вызываться из Lua.
Конфигурация и скриптование
Lua часто применяется для:
- описания логики поведения в играх (например, поведение NPC, квесты, события),
- настройки сложных приложений,
- автоматизации задач в программах.
Применяется для гибкости:
- Программа на C/C++ вызывает Lua-скрипты для настройки поведения, без перекомпиляции.
- C — компилируемый, изменение логики требует перекомпиляции.
- Lua — интерпретируемый, его можно изменять "на лету", что идеально для:
- быстрой итерации в разработке,
- пользовательских модификаций (модов, аддонов),
- Например, если вы делаете IoT-устройство, и хотите, чтобы пользователи могли менять логику без перепрошивки.
- Или если вы разрабатываете промышленный контроллер с возможностью кастомизации через скрипты.
Производительность:
- Без JIT (а LuaJIT не работает на ARM Cortex-M без MMU и ОС) Lua значительно медленнее C.
- Для вычислительно тяжёлых задач лучше использовать C и вызывать его из Lua.
Отсутствие типобезопасности и защиты от ошибок:
- В критических системах (медицинские устройства, автопилоты) Lua редко используют
Lua особенно хорош в embedded-средах, если:
- Интерпретатор Lua (без JIT) может занимать от 200 КБ до 1 МБ в зависимости от конфигурации. Lua использует динамическое выделение памяти и сборку мусора (GC)
- Минимальная версия (например, Lua 5.4, собранная с отключёнными библиотеками) может работать даже при 64–256 КБ ОЗУ (хотя это уже предел).
- Lua работает на 32-битных (и даже некоторых 16-битных) микроконтроллерах, но комфортнее всего — на ARM Cortex-M3/M4/M7 и выше.
- Примеры платформ: ESP32, STM32, Raspberry Pi Pico (с достаточной RAM), Linux-based embedded (например, на OpenWrt).
|
|
|
Install
sudo apt update
sudo apt install lua5.4 liblua5.4-dev
lua -v
Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio
# Path lua libray
dpkg -L liblua5.4-dev | grep lua.h
/usr/include/lua5.4/lua.h
/usr/include/lua5.4/lua.hpp
команда GCC должна указывать путь к заголовкам через -I, иначе компилятор ищет их только в стандартных /usr/include
-I/usr/include/lua5.4
Настройка проекта .vscode/c_cpp_properties.json
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/usr/include/lua5.4"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c99",
"cppStandard": "c++17",
"intelliSenseMode": "linux-gcc-x64",
"browse": {
"path": [
"/usr/include/lua5.4"
],
"limitSymbolsToIncludedHeaders": true
}
}
],
"version": 4
}
Compiling:
gcc -std=c99 -Wall -Wextra -Wformat -Werror -Wconversion -Wformat=2 -Wformat-security -fdiagnostics-color=always -fmessage-length=0 -Wformat-diag \
-I/usr/include/lua5.4 -O0 main.c -o my_program.out -llua5.4 -lm -ldl
Где:
- -I — для компилятора, путь к заголовочным файлам при include
- -I/usr/include/lua5.4 → путь к Lua заголовкам (lua.h, lauxlib.h, lualib.h)
- -L — для линкера, бинарные библиотеки, путь к библиотекам (liblua.so, liblua.a). Нужно линкеру, чтобы он мог найти реализацию функций при сборке если они в нестандартном месте, например /usr/local/lib или /home/user/mylibs.
- -llua — линковка с Lua
- -llua5.4 → линковка с библиотекой Lua
- -lm -ldl → дополнительные зависимости Lua на Linux
|
|
|
|
|
|
- В Lua C API всё работает через стек.
- Значения кладутся на стек, функции читают их и кладут результаты обратно.
- lua_pop(L, n) — убрать
n элементов со стека.
- lua_gettop(L) — количество элементов на стеке.
|
|
|
|
|
Создание и закрытие Lua-состояния
|
lua_State *L = luaL_newstate(); - Создаёт новое Lua-состояние (аналог “виртуальной машины” Lua).
lua_close(L); - Закрывает Lua-состояние, освобождает память.
Пример:
lua_State *L = luaL_newstate();
luaL_openlibs(L); // подключаем стандартные библиотеки
lua_close(L);
|
|
Загрузка и выполнение Lua-файлов
|
luaL_dofile(L, "file.lua") - Загружает и выполняет Lua-файл.
luaL_loadfile(L, "file.lua") - Загружает Lua-файл, но не выполняет. Можно потом вызвать lua_pcall.
lua_pcall(L, nargs, nresults, errfunc) - Вызывает Lua-функцию с аргументами на стеке.
Пример:
if (luaL_dofile(L, "config.lua") != LUA_OK) {
printf("Error: %s\n", lua_tostring(L, -1));
}
|
|
Работа с глобальными переменными
|
lua_getglobal(L, "var") - Кладёт глобальную переменную Lua var на стек.
lua_setglobal(L, "var") - Берёт значение со стека и сохраняет его как глобальную переменную Lua var.
Пример:
lua_getglobal(L, "config"); // кладём таблицу config на стек
lua_setglobal(L, "config_copy"); // сохраняем её как другую глобальную переменную
|
|
|
lua_istable(L, index) - Проверяет, является ли значение на стеке таблицей.
lua_getfield(L, index, "key") - Берёт из таблицы поле key и кладёт на стек.
lua_setfield(L, index, "key") - Берёт значение со стека и помещает в таблицу под ключом key.
Пример:
lua_getglobal(L, "config"); // config
lua_getfield(L, -1, "window"); // config.window
lua_getfield(L, -1, "width"); // config.window.width
int width = (int)lua_tointeger(L, -1);
lua_pop(L, 1); // убрать width
|
|
|
lua_tointeger(L, index) - Преобразует значение на стеке в int.
lua_tonumber(L, index) - Преобразует в double.
lua_toboolean(L, index) - Преобразует в bool.
lua_tostring(L, index) - Преобразует в const char*.
lua_pushinteger(L, n) - Кладёт целое число на стек.
lua_pushnumber(L, n) - Кладёт число с плавающей точкой.
lua_pushboolean(L, b) - Кладёт true/false на стек.
lua_pushstring(L, s) - Кладёт строку на стек.
|
|
|
lua_getglobal(L, "my_function"); // кладём функцию на стек
lua_pushinteger(L, 10); // аргумент
lua_pushinteger(L, 20); // аргумент
if (lua_pcall(L, 2, 1, 0) != LUA_OK) {
printf("Error: %s\n", lua_tostring(L, -1));
}
int result = lua_tointeger(L, -1);
lua_pop(L, 1); // убрать результат
|
|
|
|
|
|
Файл script.lua:
-- однострочный
--[[
многострочный комментарии
]]
-- глобальные переменный (по умолчанию)
x = 10 -- число
x = "hello" -- теперь строка
local a = 1 -- локальная
local b = 2
if a > b then
print("a > b")
else
print("a <= b")
end
-- Функции
function add(a, b)
return a + b
end
local f = function(x) return x * 2 end
-- Цикл while
local i = 0
while i < 10 do
i = i + 1
end
-- Цикл for
i = nil -- удаляет значение переменной (эквивалент NULL)
i = 0
for i = 1, 10 do
print(i)
end
--[[
Lua не имеет массивов и структур как в C — есть таблицы, которые могут быть:
- массивом
- словарём
- объектом
- чем угодно одновременно
Индексация начинается с 1
]]
t = {10, 20, 30} -- массив
person = {name="Bob", age=20} -- словарь
print(t[1]) -- 10
-- Строки — иммутабельные
local s = "hello"
print(#s) -- длина строки
print(s .. " world") -- конкатенация
Файл main.c:
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
int main() {
lua_State *L = luaL_newstate(); // создаём Lua-состояние
luaL_openlibs(L); // подключаем стандартные библиотеки Lua
if (luaL_dofile(L, "script.lua") != LUA_OK) { // выполняем скрипт Lua
printf("Error: %s\n", lua_tostring(L, -1));
}
lua_close(L); // закрываем Lua
return 0;
}
Компиляция:
gcc -std=c99 -Wall -Wextra -Wformat -Werror -Wconversion -Wformat=2 -Wformat-security -fdiagnostics-color=always \
-fmessage-length=0 -Wformat-diag \
-I/usr/include/lua5.4 -O0 main.c -o my_program.out -llua5.4 -lm -ldl
|
|
Пример применения Lua для конфигурации
|
Файл config.lua:
-- Конфигурационный Lua-файл
config = {
window = {
width = 800,
height = 600,
fullscreen = false
},
player = {
name = "Alice",
lives = 3
}
}
-- Можно добавить вычисляемое значение
function window_area()
return config.window.width * config.window.height
end
Файл main.c:
#include <stdio.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
/*
Скрипты и конфиги на Lua можно менять на лету, и программа на C будет реагировать
на изменения без перекомпиляции
C-программа читает Lua-конфиг, использует его значения, и потом можно просто поменять
Lua-файл — программа снова прочтёт новые значения.
*/
void main(void){
lua_State *L = luaL_newstate(); // создаём Lua-состояние
luaL_openlibs(L); // подключаем стандартные библиотеки
// Загружаем Lua-файл
if (luaL_dofile(L, "config.lua") != LUA_OK) {
printf("Error loading config: %s\n", lua_tostring(L, -1));
lua_close(L);
return 1;
}
// Достаем таблицу config
lua_getglobal(L, "config"); // помещаем в стек
if (!lua_istable(L, -1)) {
printf("config is not a table!\n");
lua_close(L);
return 1;
}
// Достаем window.width
lua_getfield(L, -1, "window"); // config.window
lua_getfield(L, -1, "width"); // config.window.width
int width = (int)lua_tointeger(L, -1);
lua_pop(L, 1);
// Достаем window.height
lua_getfield(L, -1, "height");
int height = (int)lua_tointeger(L, -1);
lua_pop(L, 1);
// Достаем fullscreen
lua_getfield(L, -1, "fullscreen");
int fullscreen = lua_toboolean(L, -1);
lua_pop(L, 2); // pop window и fullscreen
printf("Window: %dx%d, fullscreen: %s\n",
width, height, fullscreen ? "yes" : "no");
// Вызов функции window_area()
lua_getglobal(L, "window_area");
if (lua_isfunction(L, -1)) {
if (lua_pcall(L, 0, 1, 0) == LUA_OK) {
int area = (int)lua_tointeger(L, -1);
printf("Window area: %d\n", area);
lua_pop(L, 1);
} else {
printf("Error calling window_area(): %s\n", lua_tostring(L, -1));
}
}
lua_close(L);
return 0;
}
Компиляция:
gcc -std=c99 -Wall -Wextra -Wformat -Werror -Wconversion -Wformat=2 -Wformat-security -fdiagnostics-color=always \
-fmessage-length=0 -Wformat-diag \
-I/usr/include/lua5.4 -O0 main.c -o my_program.out -llua5.4 -lm -ldl
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|