diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 9ec8f4f117..5f3591498e 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -54,6 +54,7 @@ body: - led_strip - libpng - libsodium + - lua - esp_linenoise - network_provisioning - nghttp diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml index b2e1b6c0a0..21a3ad09d2 100644 --- a/.github/workflows/upload_component.yml +++ b/.github/workflows/upload_component.yml @@ -57,6 +57,7 @@ jobs: json_parser led_strip libsodium + lua network_provisioning nghttp onewire_bus diff --git a/.gitmodules b/.gitmodules index 4e208d99a7..cdbf741121 100644 --- a/.gitmodules +++ b/.gitmodules @@ -81,3 +81,7 @@ [submodule "cjson/cJSON"] path = cjson/cJSON url = https://github.com/DaveGamble/cJSON.git + +[submodule "lua/lua"] + path = lua/lua + url = https://github.com/lua/lua.git diff --git a/.idf_build_apps.toml b/.idf_build_apps.toml index 833a709ef1..a131cb4331 100644 --- a/.idf_build_apps.toml +++ b/.idf_build_apps.toml @@ -27,6 +27,7 @@ manifest_file = [ "json_parser/.build-test-rules.yml", "libpng/.build-test-rules.yml", "libsodium/.build-test-rules.yml", + "lua/.build-test-rules.yml", "onewire_bus/.build-test-rules.yml", "pcap/.build-test-rules.yml", "pid_ctrl/.build-test-rules.yml", diff --git a/lua/.build-test-rules.yml b/lua/.build-test-rules.yml new file mode 100644 index 0000000000..a05d1ee856 --- /dev/null +++ b/lua/.build-test-rules.yml @@ -0,0 +1,4 @@ +lua/examples/lua_example: + enable: + - if: IDF_TARGET in ["esp32", "esp32c3"] + reason: "Sufficient to test on one Xtensa and one RISC-V target" diff --git a/lua/CMakeLists.txt b/lua/CMakeLists.txt new file mode 100644 index 0000000000..ad85ba53cd --- /dev/null +++ b/lua/CMakeLists.txt @@ -0,0 +1,45 @@ +idf_component_register( + SRCS + "lua/lapi.c" + "lua/lauxlib.c" + "lua/lbaselib.c" + "lua/lcode.c" + "lua/lctype.c" + "lua/lcorolib.c" + "lua/ldblib.c" + "lua/ldebug.c" + "lua/ldo.c" + "lua/lfunc.c" + "lua/lgc.c" + "lua/linit.c" + "lua/liolib.c" + "lua/llex.c" + "lua/lmathlib.c" + "lua/lmem.c" + "lua/lopcodes.c" + "lua/loadlib.c" + "lua/loslib.c" + "lua/lstate.c" + "lua/ltable.c" + "lua/ltm.c" + "lua/lvm.c" + "lua/lobject.c" + "lua/lparser.c" + "lua/lstrlib.c" + "lua/lstring.c" + "lua/ltablib.c" + "lua/ldump.c" + "lua/lundump.c" + "lua/lutf8lib.c" + "lua/lzio.c" + INCLUDE_DIRS + "port/include" + "lua" +) + +# Enable 32-bit mode for Lua (required for ESP32 and Xtensa/RISC-V architectures) +target_compile_definitions(${COMPONENT_LIB} PRIVATE LUA_32BITS=1) + +# Suppress compiler warnings from upstream Lua code +target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-function) + diff --git a/lua/Kconfig b/lua/Kconfig new file mode 100644 index 0000000000..286904cf06 --- /dev/null +++ b/lua/Kconfig @@ -0,0 +1,11 @@ +menu "Lua" + config LUA_MAXSTACK + int "Stack size limit" + default 1000000 + help + Limits the size of the Lua stack. + Change it if you need a different limit. This limit is arbitrary; + its only purpose is to stop Lua from consuming unlimited stack + space (and to reserve some numbers for pseudo-indices). + +endmenu diff --git a/lua/LICENSE b/lua/LICENSE new file mode 100644 index 0000000000..f41e9a5123 --- /dev/null +++ b/lua/LICENSE @@ -0,0 +1,22 @@ +Lua is licensed under the MIT license. + +Copyright (C) 1994-2025 Lua.org, PUC-Rio. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lua/README.md b/lua/README.md new file mode 100644 index 0000000000..5fe86b3d0f --- /dev/null +++ b/lua/README.md @@ -0,0 +1,59 @@ +# Lua - ESP-IDF Component + +ESP-IDF component which wraps Lua from upstream repository - https://www.lua.org/ + +This component provides Lua, a powerful, efficient, lightweight, embeddable scripting language. + +## Features + +- Suitable for embedding Lua code inside ESP-IDF applications +- Configurable stack size limit +- MIT license + +## Configuration option + +- `LUA_MAXSTACK` - default value: 1000000 - Limits the size of the Lua stack. + +Modify values: + +```shell +idf.py menuconfig +``` + +## Usage + +This component is suitable for embedding Lua scripts in ESP-IDF applications. For a complete example, see the `examples/lua_example` directory. + +### Basic Usage + +```c +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" + +void run_lua() { + lua_State *L = luaL_newstate(); + luaL_openlibs(L); + + // Run a simple Lua script + if (luaL_dostring(L, "print('Hello from Lua!')") != LUA_OK) { + printf("Error: %s\n", lua_tostring(L, -1)); + } + + lua_close(L); +} +``` + +## Changes to upstream + +This component uses header injection to provide ESP-IDF specific configuration overrides without modifying the upstream Lua source code. The platform-specific settings (such as `LUA_32BITS` for ESP32 and Xtensa/RISC-V architectures) are defined in `lua/port/include/luaconf.h`. + +## License + +Lua is licensed under the MIT license. See LICENSE file for details. + +## Upstream + +- Lua: https://www.lua.org/ +- Repository: https://github.com/lua/lua + diff --git a/lua/examples/lua_example/CMakeLists.txt b/lua/examples/lua_example/CMakeLists.txt new file mode 100644 index 0000000000..3747ac9e6f --- /dev/null +++ b/lua/examples/lua_example/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(lua_example) diff --git a/lua/examples/lua_example/README.md b/lua/examples/lua_example/README.md new file mode 100644 index 0000000000..d5dbfbee11 --- /dev/null +++ b/lua/examples/lua_example/README.md @@ -0,0 +1,56 @@ +# Lua Example + +This example demonstrates how to embed Lua in an ESP-IDF application. + +## Features + +- **Embedded Lua script execution**: Run Lua code directly from C strings +- **File-based Lua script execution**: Load and execute Lua scripts from LittleFS filesystem +- **Memory tracking**: Monitor memory usage throughout Lua operations +- **Error handling**: Proper error handling and reporting + +## Example Contents + +1. **Simple Embedded Script**: A basic Lua script that calculates and prints a value (answer = 42) +2. **Fibonacci Script**: A Lua script loaded from filesystem that calculates Fibonacci numbers + +## Hardware Requirements + +- Any ESP32 series board (ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6, etc.) +- Minimum 4MB flash (for filesystem partition) + +## Configuration + +- The example uses LittleFS to store Lua scripts +- A custom partition table is included with an "assets" partition for Lua scripts + +## Building and Flashing + +```bash +idf.py build flash monitor +``` + +## Expected Output + +``` +I (xxx) lua_example: Lua Example Starting +I (xxx) lua_example: Initializing LittleFS filesystem +I (xxx) lua_example: Filesystem mounted at /assets +I (xxx) lua_example: Starting Lua test: Simple Embedded Script +The answer is: 42 +I (xxx) lua_example: End of Lua test: Simple Embedded Script +I (xxx) lua_example: Starting Lua test from file: Fibonacci Script from File +Fibonacci of 10 is: 55 +I (xxx) lua_example: End of Lua test from file: Fibonacci Script from File +I (xxx) lua_example: End of Lua example application. +``` + +## Customization + +You can add your own Lua scripts to the `assets/` directory and execute them from your application code. + +## See Also + +- [Lua Component Documentation](../../README.md) +- [Lua Official Website](https://www.lua.org/) +- [Lua Reference Manual](https://www.lua.org/manual/5.5/) diff --git a/lua/examples/lua_example/assets/fibonacci.lua b/lua/examples/lua_example/assets/fibonacci.lua new file mode 100644 index 0000000000..f5df2860b6 --- /dev/null +++ b/lua/examples/lua_example/assets/fibonacci.lua @@ -0,0 +1,11 @@ +-- fibonacci.lua +-- This script calculates the 10th number in the Fibonacci sequence and prints it. + +local function fibonacci(n) + if n <= 1 then return n end + return fibonacci(n - 1) + fibonacci(n - 2) +end + +local fib_10 = fibonacci(10) +print('Fibonacci of 10 is: ' .. fib_10) +return fib_10 diff --git a/lua/examples/lua_example/main/CMakeLists.txt b/lua/examples/lua_example/main/CMakeLists.txt new file mode 100644 index 0000000000..f23be0886f --- /dev/null +++ b/lua/examples/lua_example/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS "lua_example.c" + INCLUDE_DIRS ".") + +# Note: you must have a partition named "assets" in your partition table +littlefs_create_partition_image(assets ../assets FLASH_IN_PROJECT) diff --git a/lua/examples/lua_example/main/idf_component.yml b/lua/examples/lua_example/main/idf_component.yml new file mode 100644 index 0000000000..fb08674bc0 --- /dev/null +++ b/lua/examples/lua_example/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + lua: + override_path: "../../.." + joltwallet/littlefs: "^1.20.3" diff --git a/lua/examples/lua_example/main/lua_example.c b/lua/examples/lua_example/main/lua_example.c new file mode 100644 index 0000000000..f8f68a1885 --- /dev/null +++ b/lua/examples/lua_example/main/lua_example.c @@ -0,0 +1,149 @@ +/* + * Lua Example + * + * This example demonstrates how to embed Lua in an ESP-IDF application. + * It shows both embedded Lua script execution and loading Lua scripts from + * a filesystem (LittleFS). + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_littlefs.h" +#include "esp_vfs.h" +#include + +#include "lua.h" +#include "lualib.h" +#include "lauxlib.h" + +#define TAG "lua_example" +#define LUA_FILE_PATH "/assets" + +// Function to log memory usage +static void log_memory_usage(const char *message) +{ + ESP_LOGI(TAG, "Free heap: %d, Min free heap: %d, Largest free block: %d, %s", + heap_caps_get_free_size(MALLOC_CAP_DEFAULT), + heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT), + heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT), + message); +} + +// Initialize and mount the filesystem +static void init_filesystem(void) +{ + ESP_LOGI(TAG, "Initializing LittleFS filesystem"); + + esp_vfs_littlefs_conf_t conf = { + .base_path = LUA_FILE_PATH, + .partition_label = "assets", + .format_if_mount_failed = true, + .dont_mount = false, + }; + + esp_err_t err = esp_vfs_littlefs_register(&conf); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to mount or format filesystem: %s", esp_err_to_name(err)); + } else { + ESP_LOGI(TAG, "Filesystem mounted at %s", LUA_FILE_PATH); + } +} + +// Function to run a Lua script from file +static void run_lua_file(const char *file_name, const char *test_name) +{ + ESP_LOGI(TAG, "Starting Lua test from file: %s", test_name); + + log_memory_usage("Start of test"); + + lua_State *L = luaL_newstate(); + if (L == NULL) { + ESP_LOGE(TAG, "Failed to create new Lua state"); + return; + } + log_memory_usage("After luaL_newstate"); + + luaL_openlibs(L); + + // Set the Lua module search path + if (luaL_dostring(L, "package.path = package.path .. ';./?.lua;/assets/?.lua'")) { + ESP_LOGE(TAG, "Failed to set package.path: %s", lua_tostring(L, -1)); + lua_pop(L, 1); + } + + log_memory_usage("After luaL_openlibs"); + + // Construct the full file path + char full_path[128]; + snprintf(full_path, sizeof(full_path), LUA_FILE_PATH"/%s", file_name); + + if (luaL_dofile(L, full_path) == LUA_OK) { + lua_pop(L, lua_gettop(L)); + } else { + ESP_LOGE(TAG, "Error running Lua script from file '%s': %s", full_path, lua_tostring(L, -1)); + lua_pop(L, 1); + } + log_memory_usage("After executing Lua script from file"); + + lua_close(L); + log_memory_usage("After lua_close"); + + ESP_LOGI(TAG, "End of Lua test from file: %s", test_name); +} + +// Function to run an embedded Lua script +static void run_embedded_lua_test(const char *lua_script, const char *test_name) +{ + ESP_LOGI(TAG, "Starting Lua test: %s", test_name); + + log_memory_usage("Start of test"); + + lua_State *L = luaL_newstate(); + if (L == NULL) { + ESP_LOGE(TAG, "Failed to create new Lua state"); + return; + } + log_memory_usage("After luaL_newstate"); + + luaL_openlibs(L); + log_memory_usage("After luaL_openlibs"); + + if (luaL_dostring(L, lua_script) == LUA_OK) { + lua_pop(L, lua_gettop(L)); + } else { + ESP_LOGE(TAG, "Error running embedded Lua script: %s", lua_tostring(L, -1)); + lua_pop(L, 1); + } + log_memory_usage("After executing Lua script"); + + lua_close(L); + log_memory_usage("After lua_close"); + + ESP_LOGI(TAG, "End of Lua test: %s", test_name); +} + +void app_main(void) +{ + ESP_LOGI(TAG, "Lua Example Starting"); + + // Initialize and mount the filesystem + init_filesystem(); + + // Test 1: Simple embedded Lua script + const char *simple_script = "answer = 42; print('The answer is: '..answer)"; + run_embedded_lua_test(simple_script, "Simple Embedded Script"); + + // Test 2: Run Lua script from a file (fibonacci.lua) + run_lua_file("fibonacci.lua", "Fibonacci Script from File"); + + ESP_LOGI(TAG, "End of Lua example application."); + + // Prevent the task from ending + while (1) { + vTaskDelay(pdMS_TO_TICKS(1000)); + } +} diff --git a/lua/examples/lua_example/partitions.csv b/lua/examples/lua_example/partitions.csv new file mode 100644 index 0000000000..3edcf2c25d --- /dev/null +++ b/lua/examples/lua_example/partitions.csv @@ -0,0 +1,6 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1M, +assets, data, littlefs, , 0xF0000, diff --git a/lua/examples/lua_example/pytest_lua_example.py b/lua/examples/lua_example/pytest_lua_example.py new file mode 100644 index 0000000000..eff4dca619 --- /dev/null +++ b/lua/examples/lua_example/pytest_lua_example.py @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.generic +def test_lua_example(dut: Dut) -> None: + dut.expect_exact('Lua Example Starting') + dut.expect_exact('Initializing LittleFS filesystem') + dut.expect_exact('Filesystem mounted at /assets') + dut.expect_exact('Starting Lua test: Simple Embedded Script') + dut.expect_exact('The answer is: 42') + dut.expect_exact('End of Lua test: Simple Embedded Script') + dut.expect_exact('Starting Lua test from file: Fibonacci Script from File') + dut.expect_exact('Fibonacci of 10 is: 55') + dut.expect_exact('End of Lua test from file: Fibonacci Script from File') + dut.expect_exact('End of Lua example application.') diff --git a/lua/examples/lua_example/sdkconfig.defaults b/lua/examples/lua_example/sdkconfig.defaults new file mode 100644 index 0000000000..f53cf8923c --- /dev/null +++ b/lua/examples/lua_example/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_LUA_MAXSTACK=1000000 diff --git a/lua/idf_component.yml b/lua/idf_component.yml new file mode 100644 index 0000000000..1ec121efcd --- /dev/null +++ b/lua/idf_component.yml @@ -0,0 +1,10 @@ +version: "5.5.0" +description: Lua is a powerful, efficient, lightweight, embeddable scripting language +url: https://github.com/espressif/idf-extra-components/tree/master/lua +repository: https://github.com/espressif/idf-extra-components.git +dependencies: + idf: ">=5.0" +sbom: + manifests: + - path: sbom_lua.yml + dest: lua diff --git a/lua/lua b/lua/lua new file mode 160000 index 0000000000..a5522f06d2 --- /dev/null +++ b/lua/lua @@ -0,0 +1 @@ +Subproject commit a5522f06d2679b8f18534fd6a9968f7eb539dc31 diff --git a/lua/port/include/luaconf.h b/lua/port/include/luaconf.h new file mode 100644 index 0000000000..d29d634270 --- /dev/null +++ b/lua/port/include/luaconf.h @@ -0,0 +1,48 @@ +/* +** Lua port configuration for ESP-IDF +** +** This file provides platform-specific overrides for Lua configuration. +** It uses header injection technique to modify upstream Lua behavior +** without forking the entire luaconf.h file. +** +** The upstream luaconf.h is included first, then specific macros are +** overridden for ESP-IDF platform requirements. +*/ + +#ifndef LUA_PORT_LUACONF_H +#define LUA_PORT_LUACONF_H + +#ifdef __IDF__ +/* +** Include upstream luaconf.h to get all default definitions. +** This ensures we have access to all upstream configuration. +*/ +#include "luaconf.h" + +/* +** ESP-IDF specific overrides +*/ + +/* +** LUA_32BITS enables Lua with 32-bit integers and 32-bit floats. +** This is required for ESP32 and other Xtensa/RISC-V architectures. +*/ +#undef LUA_32BITS +#define LUA_32BITS 1 + +/* +** LUAI_MAXSTACK limits the size of the Lua stack. +** Override to use ESP-IDF Kconfig value (CONFIG_LUA_MAXSTACK). +** This allows users to configure stack size via menuconfig. +*/ +#if 1000000 < (INT_MAX / 2) +#undef LUAI_MAXSTACK +#define LUAI_MAXSTACK CONFIG_LUA_MAXSTACK +#else +#undef LUAI_MAXSTACK +#define LUAI_MAXSTACK (INT_MAX / 2u) +#endif + +#endif /* __IDF__ */ + +#endif /* LUA_PORT_LUACONF_H */ diff --git a/lua/sbom_lua.yml b/lua/sbom_lua.yml new file mode 100644 index 0000000000..655e24fe50 --- /dev/null +++ b/lua/sbom_lua.yml @@ -0,0 +1,7 @@ +name: lua +version: 5.5.0 +cpe: cpe:2.3:a:lua:lua:{}:*:*:*:*:*:*:* +supplier: 'Organization: Lua.org, PUC-Rio ' +description: Lua is a powerful, efficient, lightweight, embeddable scripting language +url: https://www.lua.org/ +license: MIT