farmbot_os/farmbot_os/lib/farmbot_os/lua.ex

117 lines
3.7 KiB
Elixir

defmodule FarmbotOS.Lua do
@moduledoc """
Embedded scripting language for testing,
assertion, and other debugging things.
"""
@type t() :: tuple()
@type table() :: [{any, any}]
require FarmbotCore.Logger
alias FarmbotOS.Lua.Ext.{
DataManipulation,
Firmware,
Info
}
# this function is used by SysCalls, but isn't a direct requirement.
@doc "Logs an assertion based on it's result"
def log_assertion(passed?, type, message) do
meta = [assertion_passed: passed?, assertion_type: type]
FarmbotCore.Logger.dispatch_log(__ENV__, :assertion, 2, message, meta)
end
@doc """
Evaluates some Lua code. The code should
return a boolean value.
"""
def eval_assertion(comment, str) when is_binary(str) do
init()
|> eval(str)
|> case do
{:ok, [true | _]} ->
true
{:ok, [false | _]} ->
false
{:ok, [_, reason]} when is_binary(reason) ->
{:error, reason}
{:ok, _data} ->
{:error, "bad return value from expression evaluation"}
{:error, {:lua_error, _error, _lua}} ->
{:error, "lua runtime error evaluating expression"}
{:error, {:badmatch, {:error, [{line, :luerl_parse, parse_error}], _}}} ->
FarmbotCore.Logger.error(
1,
"""
Failed to parse expression:
`#{comment}.lua:#{line}`
#{IO.iodata_to_binary(parse_error)}
""",
channels: [:toast]
)
{:error, "failed to parse expression (line:#{line}): #{IO.iodata_to_binary(parse_error)}"}
error ->
error
end
end
@spec init() :: t()
def init do
:luerl.init()
|> set_table([:calibrate], &Firmware.calibrate/2)
|> set_table([:emergency_lock], &Firmware.emergency_lock/2)
|> set_table([:emergency_unlock], &Firmware.emergency_unlock/2)
|> set_table([:find_home], &Firmware.find_home/2)
|> set_table([:home], &Firmware.home/2)
|> set_table([:move_absolute], &Firmware.move_absolute/2)
|> set_table([:get_position], &Firmware.get_position/2)
|> set_table([:check_position], &Firmware.check_position/2)
|> set_table([:get_pin], &Firmware.get_pin/2)
|> set_table([:get_pins], &Firmware.get_pins/2)
|> set_table([:coordinate], &Firmware.coordinate/2)
|> set_table([:read_status], &Info.read_status/2)
|> set_table([:send_message], &Info.send_message/2)
|> set_table([:version], &Info.version/2)
|> set_table([:current_month], &Info.current_month/2)
|> set_table([:current_hour], &Info.current_hour/2)
|> set_table([:current_minute], &Info.current_minute/2)
|> set_table([:current_second], &Info.current_second/2)
|> set_table([:update_device], &DataManipulation.update_device/2)
|> set_table([:get_device], &DataManipulation.get_device/2)
|> set_table([:update_fbos_config], &DataManipulation.update_fbos_config/2)
|> set_table([:get_fbos_config], &DataManipulation.get_fbos_config/2)
|> set_table([:update_firmware_config], &DataManipulation.update_firmware_config/2)
|> set_table([:get_firmware_config], &DataManipulation.get_firmware_config/2)
|> set_table([:new_farmware_env], &DataManipulation.new_farmware_env/2)
|> set_table([:new_sensor_reading], &DataManipulation.new_sensor_reading/2)
end
@spec set_table(t(), Path.t(), any()) :: t()
def set_table(lua, path, value) do
:luerl.set_table(path, value, lua)
end
@spec eval(t(), String.t()) :: {:ok, any()} | {:error, any()}
def eval(lua, hook) when is_binary(hook) do
:luerl.eval(hook, lua)
end
def unquote(:do)(lua, hook) when is_binary(hook) do
:luerl.do(hook, lua)
catch
:error, {:error, reason} ->
{{:error, reason}, lua}
error, reason ->
{{:error, {error, reason}}, lua}
end
end