Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Тема
Описание
Доп.

Язык 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.
    • -L/usr/local/lib
  • -llua — линковка с Lua
    • -llua5.4 → линковка с библиотекой Lua
  • -lm -ldl → дополнительные зависимости Lua на Linux

Стек Lua

  • В 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-функций из C


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