1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

examples/lua: Add REPL.

This example add a module thats starts an interactive READ-EVAL-
PRINT-LOOP written in Lua.
This commit is contained in:
Juan Carrano 2018-07-02 14:45:31 +02:00
parent ed4411602c
commit 87496f3376
4 changed files with 267 additions and 0 deletions

View File

@ -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)

View File

@ -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.

63
examples/lua_REPL/main.c Normal file
View File

@ -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 <j.carrano@fu-berlin.de>
*
* @}
*/
#include <stdio.h>
#include <string.h>
#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;
}

View File

@ -0,0 +1,94 @@
--[[
@file repl.lua
@brief Read-eval-print loop for LUA
@author Juan Carrano <j.carrano@fu-berlin.de>
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()