From 87496f337688f351b7743a1da73c7e770042d488 Mon Sep 17 00:00:00 2001 From: Juan Carrano Date: Mon, 2 Jul 2018 14:45:31 +0200 Subject: [PATCH] examples/lua: Add REPL. This example add a module thats starts an interactive READ-EVAL- PRINT-LOOP written in Lua. --- examples/lua_REPL/Makefile | 78 ++++++++++++++++++++++++++++++ examples/lua_REPL/README.md | 32 +++++++++++++ examples/lua_REPL/main.c | 63 +++++++++++++++++++++++++ examples/lua_REPL/repl.lua | 94 +++++++++++++++++++++++++++++++++++++ 4 files changed, 267 insertions(+) create mode 100644 examples/lua_REPL/Makefile create mode 100644 examples/lua_REPL/README.md create mode 100644 examples/lua_REPL/main.c create mode 100644 examples/lua_REPL/repl.lua diff --git a/examples/lua_REPL/Makefile b/examples/lua_REPL/Makefile new file mode 100644 index 0000000000..fb397b5319 --- /dev/null +++ b/examples/lua_REPL/Makefile @@ -0,0 +1,78 @@ +APPLICATION = lua_repl + +# If no BOARD is found in the environment, use this default: +BOARD ?= native + +# This has to be the absolute path to the RIOT base directory: +RIOTBASE ?= $(CURDIR)/../.. + +BOARD_INSUFFICIENT_MEMORY := bluepill calliope-mini cc2650-launchpad \ + cc2650stk maple-mini microbit nrf51dongle \ + nucleo-f030r8 nucleo-f031k6 nucleo-f042k6 \ + nucleo-f070rb nucleo-f072rb nucleo-f103rb \ + nucleo-f302r8 nucleo-f303k8 nucleo-f334r8 \ + nucleo-f410rb nucleo-l031k6 nucleo-l053r8 \ + opencm904 spark-core stm32f0discovery \ + stm32mindev airfy-beacon arduino-mkr1000 \ + arduino-mkrfox1200 arduino-mkrzero arduino-zero \ + b-l072z-lrwan1 cc2538dk ek-lm4f120xl feather-m0 \ + ikea-tradfri limifrog-v1 mbed_lpc1768 nrf6310 \ + nucleo-f091rc nucleo-l073rz nz32-sc151 \ + openmote-cc2538 pba-d-01-kw2x remote-pa \ + remote-reva remote-revb samd21-xpro saml21-xpro \ + samr21-xpro seeeduino_arch-pro slstk3401a \ + sltb001a slwstk6220a sodaq-autonomo \ + sodaq-explorer stk3600 stm32f3discovery \ + yunjia-nrf51822 + +BOARD_BLACKLIST := arduino-duemilanove arduino-mega2560 arduino-uno \ + chronos hifive1 jiminy-mega256rfr2 mega-xplained mips-malta \ + msb-430 msb-430h pic32-clicker pic32-wifire telosb \ + waspmote-pro wsn430-v1_3b wsn430-v1_4 z1 + +# Comment this out to disable code in RIOT that does safety checking +# which is not needed in a production environment but helps in the +# development process: +DEVELHELP ?= 1 + +# Uncomment the following lines to enable debugging features. +#CFLAGS_OPT = -Og +#CFLAGS += -DDEBUG_ASSERT_VERBOSE -DLUA_DEBUG + +# Change this to 0 show compiler invocation lines by default: +QUIET ?= 1 + +# This value is in excess because we are not sure of the exact requirements of +# lua (see the package's docs). It must be fixed in the future by taking +# appropriate measurements. +CFLAGS += -DTHREAD_STACKSIZE_MAIN='(THREAD_STACKSIZE_DEFAULT+7000)' + +USEPKG += lua + +include $(RIOTBASE)/Makefile.include + +# The code below generates a header file from any .lua scripts in the +# example directory. The header file contains a byte array of the +# ASCII characters in the .lua script. + +LUA_PATH := $(BINDIR)/lua + +# add directory of generated *.lua.h files to include path +CFLAGS += -I$(LUA_PATH) + +# generate .lua.h header files of .lua files +LUA = $(wildcard *.lua) + +LUA_H := $(LUA:%.lua=$(LUA_PATH)/%.lua.h) + +$(LUA_PATH)/: + @mkdir -p $@ + +# FIXME: This way of embedding lua code is not robust. A proper script will +# be included later. + +$(LUA_H): | $(LUA_PATH)/ +$(LUA_H): $(LUA_PATH)/%.lua.h: %.lua + xxd -i $< | sed 's/^unsigned/const unsigned/g' > $@ + +$(RIOTBUILD_CONFIG_HEADER_C): $(LUA_H) diff --git a/examples/lua_REPL/README.md b/examples/lua_REPL/README.md new file mode 100644 index 0000000000..fd1fb42ae9 --- /dev/null +++ b/examples/lua_REPL/README.md @@ -0,0 +1,32 @@ +## Lua interactive interpreter + +### About + +This example shows how to run a [Lua](https://www.lua.org/) Read-Eval-Print loop. +It works in a similar way to the lua shell that comes with your operating +system's default lua installation. + + +### How to run + +Type `make all flash` to program your board. The lua interpreter communicates +via UART (like the shell). + +It is not recommended to use `make term` because the default RIOT terminal messes +up the input and output and the REPL needs multi-line input. Instead, use something +like `miniterm.py` from pyserial: + +``` +miniterm.py --eol LF --echo /dev/ttyACM0 115200 +``` + +By default only some of the builtin modules are loaded, to preserve RAM. See +the definition of `BARE_MINIMUM_MODS` in main.c. + +### Using the interpreter + +See the [Lua manual](https://www.lua.org/manual/5.3/) for the syntax of the language. + +Each piece of single or multi-line input is compiled as a chunk and run. For this +reason, issuing "local" definitions may not work as expected: the definitions +will be local to that chunk only. diff --git a/examples/lua_REPL/main.c b/examples/lua_REPL/main.c new file mode 100644 index 0000000000..f985130430 --- /dev/null +++ b/examples/lua_REPL/main.c @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2018 Freie Universität Berlin. + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup examples + * @{ + * + * @file + * @brief Lua shell in RIOT + * + * @author Juan Carrano + * + * @} + */ + +#include +#include + +#include "lua_run.h" +#include "lua_builtin.h" +#include "repl.lua.h" + +/* The basic interpreter+repl needs about 13k ram AT Minimum but we need more + * memory in order to do interesting stuff. + */ +#define MAIN_LUA_MEM_SIZE (40000) + +static char lua_memory[MAIN_LUA_MEM_SIZE] __attribute__ ((aligned(__BIGGEST_ALIGNMENT__))); + +#define BARE_MINIMUM_MODS (LUAR_LOAD_BASE | LUAR_LOAD_IO | LUAR_LOAD_CORO | LUAR_LOAD_PACKAGE) + +const struct lua_riot_builtin_lua _lua_riot_builtin_lua_table[] = { + { "repl", repl_lua, sizeof(repl_lua) } +}; + + +const struct lua_riot_builtin_lua *const lua_riot_builtin_lua_table = _lua_riot_builtin_lua_table; + +const size_t lua_riot_builtin_lua_table_len = 1; + +int main(void) +{ + printf("Using memory range for Lua heap: %p - %p, %zu bytes\n", + lua_memory, lua_memory + MAIN_LUA_MEM_SIZE, sizeof(void *)); + + while (1) { + int status, value; + puts("This is Lua: starting interactive session\n"); + + status = lua_riot_do_module("repl", lua_memory, MAIN_LUA_MEM_SIZE, + BARE_MINIMUM_MODS, &value); + + printf("Exited. status: %s, return code %d\n", lua_riot_strerror(status), + value); + } + + return 0; +} diff --git a/examples/lua_REPL/repl.lua b/examples/lua_REPL/repl.lua new file mode 100644 index 0000000000..ff81f4241e --- /dev/null +++ b/examples/lua_REPL/repl.lua @@ -0,0 +1,94 @@ +--[[ + @file repl.lua + @brief Read-eval-print loop for LUA + @author Juan Carrano + Copyright (C) 2018 Freie Universität Berlin. Distributed under the GNU Lesser General Public License v2.1. +]] + +local _R_EVAL = 0 +local _R_CONT = 1 +local _R_EXIT = 2 +local _R_ERROR = 3 + +--[[ Read code from standard input (whatever stdin means for lua) + @return action_code what the eval loop should do + @return code_or_msg either code (_R_EVAL) or a message (_R_ERROR) or nil + (_R_CONT, _R_EXIT). +]] + +local function re() + io.write("L> ") + io.flush() + local ln = io.read() + + if not ln then + return _R_EXIT + elseif ln == "\n" then + return _R_CONT + end + -- Try to see if we have an expression + local maybe_code, compile_err = load("return "..ln) + -- Try to see if we have a single-line statement + if not maybe_code then + maybe_code, compile_err = load(ln) + end + -- Try a multiline statement + if not maybe_code then + -- It's not really necessary to use a coroutine, but it shows that they + -- work. + local _get_multiline = coroutine.create( + function () + coroutine.yield(ln.."\n") -- We already have the first line of input + while 1 do + io.write("L.. ") + io.flush() + local l = io.read() + if #l ~= 0 then + l = l.."\n" + end + coroutine.yield(l) + end + end + ) + local get_multiline = function() + local a, b = coroutine.resume(_get_multiline) + if a then + return b + else + return nil + end + end + + maybe_code, compile_err = load(get_multiline) + end + + if not maybe_code then + return _R_ERROR, compile_err + else + return _R_EVAL, maybe_code + end +end + +local function repl() + io.write("Welcome to the interactive interpreter\n"); + + while 1 do + local action, fn_or_message = re() + + if action == _R_EVAL then + local success, msg_or_ret = pcall(fn_or_message) + if not success then + print("Runtime error", msg_or_ret) + elseif msg_or_ret ~= nil then + print(msg_or_ret) + end + elseif action == _R_EXIT then + print() + return + elseif action == _R_ERROR then + print("Compile error:", fn_or_message) + end -- (action == _R_CONT) : do nothing + end +end + +repl()