infinate recursion..

This commit is contained in:
connor rigby 2017-10-24 10:56:38 -07:00
parent 7bde7fb8c8
commit bec0c3c5d1
58 changed files with 1032 additions and 285 deletions

View file

@ -59,11 +59,9 @@ defmodule Farmbot.BotState.Transport.GenMQTT.Client do
def on_publish(["bot", _bot, "from_clients"], msg, state) do
Logger.warn("not implemented yet: #{msg}")
if state.cache do
GenMQTT.publish(self(), status_topic(state.device), state.cache, 0, false)
end
msg
|> Farmbot.CeleryScript.AST.parse()
|> Farmbot.CeleryScript.VirtualMachine.execute()
{:ok, state}
end

View file

@ -1,17 +1,26 @@
defmodule Farmbot.CeleryScript.Ast do
defmodule Farmbot.CeleryScript.AST do
@moduledoc """
Handy functions for turning various data types into Farbot Celery Script
Ast nodes.
"""
alias Farmbot.CeleryScript.Error
defmodule Meta do
@moduledoc "Metadata about an AST node."
defstruct [precompiled: false, encoded: nil]
@type t :: %__MODULE__{
precompiled: boolean,
encoded: binary
}
defimpl Inspect, for: __MODULE__ do
def inspect(thing, _) do
"#CeleryScript<#{thing.kind}: #{inspect(Map.keys(thing.args))}>"
def new(ast) do
bin = Map.from_struct(ast) |> Map.delete(:__meta__) |> Poison.encode!
encoded = :crypto.hash(:md5, bin) |> Base.encode16()
struct(__MODULE__, encoded: encoded)
end
end
alias Farmbot.CeleryScript.Error
@typedoc """
CeleryScript args.
"""
@ -21,6 +30,8 @@ defmodule Farmbot.CeleryScript.Ast do
Type for CeleryScript Ast's.
"""
@type t :: %__MODULE__{
__meta__: Meta.t,
uid: binary,
args: args,
body: [t, ...],
kind: String.t(),
@ -28,19 +39,29 @@ defmodule Farmbot.CeleryScript.Ast do
}
@enforce_keys [:args, :body, :kind]
defstruct [:args, :body, :kind, :comment]
defstruct [
kind: nil,
uid: nil,
args: %{},
body: [],
comment: nil,
__meta__: nil
]
@doc """
Parses json and traverses the tree and turns everything can
possibly be parsed.
"""
@spec parse({:ok, map} | map | [map, ...]) :: t
@spec parse(map | [map, ...]) :: t
def parse(map_or_json_map)
def parse(%{"kind" => kind, "args" => args} = thing) do
body = thing["body"] || []
comment = thing["comment"]
%__MODULE__{kind: kind, args: parse_args(args), body: parse(body), comment: comment}
uid = thing["uuid"] || generate_uid()
before_meta = %__MODULE__{kind: kind, args: parse_args(args), body: parse(body), comment: comment, uid: uid}
meta = thing["__meta__"] || Meta.new(before_meta)
%{before_meta | __meta__: meta}
end
def parse(%{__struct__: _} = thing) do
@ -50,7 +71,10 @@ defmodule Farmbot.CeleryScript.Ast do
def parse(%{kind: kind, args: args} = thing) do
body = thing[:body] || []
comment = thing[:comment]
%__MODULE__{kind: kind, body: parse(body), args: parse_args(args), comment: comment}
uid = thing[:uid] || generate_uid()
before_meta = %__MODULE__{kind: kind, body: parse(body), args: parse_args(args), comment: comment, uid: uid}
meta = thing[:__meta__] || Meta.new(before_meta)
%{before_meta | __meta__: meta}
end
# You can give a list of nodes.
@ -86,4 +110,8 @@ defmodule Farmbot.CeleryScript.Ast do
def create(kind, args, body) when is_map(args) and is_list(body) do
%__MODULE__{kind: kind, args: args, body: body}
end
defp generate_uid do
UUID.uuid1 |> String.split("-") |> List.first
end
end

View file

@ -0,0 +1,7 @@
defmodule Farmbot.CeleryScript.AST.CompileError do
defexception [:message]
def exception(message) do
%__MODULE__{message: message}
end
end

View file

@ -0,0 +1,87 @@
defmodule Farmbot.CeleryScript.AST.Compiler do
require Logger
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.AST.CompileError
alias Farmbot.CeleryScript.VirtualMachine.InstructionSet
import Farmbot.CeleryScript.AST.Compiler.Utils
def compile(ast, instrs) do
compiler_debug_log(
"#{Farmbot.DebugLog.color(:YELLOW)}BEGIN COMPILE: #{inspect(ast)}#{
Farmbot.DebugLog.color(:NC)
}"
)
compiler_debug_log_begin_step(ast, "ensure_implementation")
ast = ensure_implementation!(ast, instrs)
compiler_debug_log_complete_step(ast, "ensure_implementation")
compiler_debug_log_begin_step(ast, "precompile")
{state, ast} = precompile!(ast, instrs)
compiler_debug_log(Map.keys(state) |> Enum.join("\n\n"))
compiler_debug_log_complete_step(ast, "precompile")
ast
end
def ensure_implementation!(%AST{kind: kind, body: []} = ast, instrs) do
:ok = do_ensure(kind, instrs)
ast
end
def ensure_implementation!(%AST{kind: kind, body: body} = ast, instrs) do
:ok = do_ensure(kind, instrs)
ensure_implementation!(body, instrs)
ast
end
def ensure_implementation!([%AST{kind: kind, body: body} | next], instrs) do
:ok = do_ensure(kind, instrs)
ensure_implementation!(body, instrs)
ensure_implementation!(next, instrs)
end
def ensure_implementation!([], _), do: :ok
defp do_ensure(kind, instrs) do
compiler_debug_log(kind, "ensure implementation")
impl = instrs[kind] || raise CompileError, "No implementation for #{kind}"
if Code.ensure_loaded?(impl) do
:ok
else
raise CompileError, "Implementation module: #{impl} is not loaded."
end
end
# sequence
# -> execute_sequence
# -> move_abs
def precompile!(ast, instrs, state \\ %{}) do
impl = instrs[ast.kind]
if state[ast.__meta__.encoded] do
compiler_debug_log("#{inspect ast} compiled")
{state, ast}
else
case impl.precompile(ast) do
{:ok, res} ->
res = %{res | __meta__: %{res.__meta__ | precompiled: true}}
state = Map.put(state, res.__meta__.encoded, res)
{state, ast} = precompile!(res, instrs, state)
{state, body} = precompile_body(ast.body, instrs, state)
%{ast | body: body} |> precompile!(instrs, state)
{:error, reason} -> raise CompileError, reason
end
end
end
defp precompile_body(body, instrs, state, acc \\ [])
defp precompile_body([ast | rest], instrs, state, acc) do
{state, ast} = precompile!(ast, instrs, state)
acc = [ast | acc]
precompile_body(rest, instrs, state, acc)
end
defp precompile_body([], instrs, state, acc) do
{state, Enum.reverse(acc)}
end
end
# Farmbot.HTTP.get!("/api/sequences/2") |> Map.get(:body) |> Poison.decode! |> Farmbot.CeleryScript.AST.parse |> Farmbot.CeleryScript.VirtualMachine.execute

View file

@ -0,0 +1,59 @@
defmodule Farmbot.CeleryScript.AST.Compiler.Utils do
alias Farmbot.CeleryScript.VirtualMachine.InstructionSet
def compiler_debug_log(kind, msg) do
msg =
"#{format_kind(kind)}" <>
"#{Farmbot.DebugLog.color(:LIGHT_GREEN)}[ " <> msg <> " ]#{Farmbot.DebugLog.color(:NC)}"
compiler_debug_log(msg)
end
def compiler_debug_log_begin_step(ast, step) do
compiler_debug_log(
"#{Farmbot.DebugLog.color(:YELLOW)}[ #{inspect ast} ] " <> # "#{ast.__meta__.encoded} " <>
"#{Farmbot.DebugLog.color(:LIGHT_GREEN)}begin step: #{Farmbot.DebugLog.color(:YELLOW)}[ #{step} ]#{
Farmbot.DebugLog.color(:NC)
}"
)
end
def compiler_debug_log_complete_step(ast, step) do
compiler_debug_log(
"#{Farmbot.DebugLog.color(:YELLOW)}[ #{inspect ast} ] " <># "#{ast.__meta__.encoded} " <>
"#{Farmbot.DebugLog.color(:LIGHT_GREEN)}complete step: #{Farmbot.DebugLog.color(:YELLOW)}[ #{step} ]#{
Farmbot.DebugLog.color(:NC)
}"
)
end
def compiler_debug_log(msg) do
IO.puts(msg)
end
kinds = Map.keys(struct(InstructionSet)) -- [:__struct__]
max_chars =
Enum.reduce(kinds, 0, fn kind, acc ->
num_chars = to_charlist(kind) |> Enum.count()
if num_chars > acc do
num_chars
else
acc
end
end)
for kind <- kinds do
num_chars = to_charlist(kind) |> Enum.count()
pad = max_chars - num_chars
# "#{String.duplicate(" ", pad)} - " <>
res =
"#{Farmbot.DebugLog.color(:LIGHT_CYAN)}" <>
"[ " <>
"#{Farmbot.DebugLog.color(:CYAN)}#{kind}" <>
"#{String.duplicate(" ", pad)}" <>
"#{Farmbot.DebugLog.color(:LIGHT_CYAN)} ]" <> "#{Farmbot.DebugLog.color(:NC)} - "
def format_kind(unquote(kind |> to_string())), do: unquote(res)
end
end

View file

@ -0,0 +1,5 @@
defimpl Inspect, for: Farmbot.CeleryScript.AST do
def inspect(ast, _opts) do
"#CeleryScript[#{ast.uid}]<#{ast.kind}: #{inspect(Map.keys(ast.args))}>"
end
end

View file

@ -0,0 +1,5 @@
defmodule Farmbot.CeleryScript do
@moduledoc """
CeleryScript is the scripting language that Farmbot OS understands.
"""
end

View file

@ -1,4 +0,0 @@
defmodule Farmbot.CeleryScript.Error do
@moduledoc "Implementme.sh"
defexception [:message]
end

View file

@ -1,100 +0,0 @@
defmodule Farmbot.CeleryScript.Types do
@moduledoc """
Common types for shared CeleryScript Nodes.
"""
alias Farmbot.CeleryScript.Ast
@typedoc """
usually either `farmbot_os` | `arduino_firmware` but will also be things
like:
* Farmware Names
* Sync resources.
"""
@type package :: binary
@typedoc "X | Y | Z"
@type axis :: coord_x_bin | coord_y_bin | coord_z_bin
@typedoc "The literal string `X`"
@type coord_x_bin :: binary
@typedoc "The literal string `Y`"
@type coord_y_bin :: binary
@typedoc "The literal string `Z`"
@type coord_z_bin :: binary
@typedoc "Integer representing an X coord."
@type coord_x :: integer
@typedoc "Integer representing an Y coord."
@type coord_y :: integer
@typedoc "Integer representing an X coord."
@type coord_z :: integer
@typedoc """
Ast in the shape of:
```
%Ast{
kind: "pair",
args: %{label: binary, value: any},
body: []
}
```
"""
@type pair_ast :: ast
@typedoc false
@type pairs_ast :: [pair_ast]
@typep coord_args :: %{x: coord_x, y: coord_y, z: coord_z}
@typedoc """
Ast in the shaps of:
```
%Ast{
kind: "coordinate",
args: %{x: int, y: int, z: int},
body: []
}
```
"""
@type coord_ast :: %Ast{kind: binary, args: coord_args, body: []}
@typedoc """
Ast in the shape of:
```
%Ast{
kind: "nothing",
args: %{},
body: []
}
```
"""
@type nothing_ast :: %Ast{kind: binary, args: %{}, body: []}
@typedoc """
Ast in the shape of:
```
%Ast{
kind: "explanation",
args: %{message: binary},
body: []
}
```
"""
@type expl_ast_args :: %{message: binary}
@type explanation_ast :: %Ast{kind: binary, args: expl_ast_args, body: []}
@typedoc "Integer representing a pin on the arduino."
@type pin_number :: 0..69
@typedoc "Integer representing digital (0) or pwm (1)"
@type pin_mode :: 0 | 1
@typedoc false
@type ast :: Ast.t()
end

View file

@ -1,66 +0,0 @@
defmodule Farmbot.CeleryScript.VirtualMachine do
@moduledoc "Virtual Machine."
alias Farmbot.CeleryScript.Ast
alias Farmbot.CeleryScript.VirtualMachine.{InstructionSet, StackFrame}
require Logger
alias Farmbot.CeleryScript.VirtualMachine.RuntimeError, as: VmError
@typep instruction_set :: InstructionSet.t()
@typep ast :: Ast.t()
@typep stack_frame :: StackFrame.t()
defstruct instruction_set: %InstructionSet{},
call_stack: [],
program: [],
pc: -1,
running: true
@typedoc "State of a virtual machine."
@type t :: %__MODULE__{
instruction_set: instruction_set,
call_stack: [stack_frame],
program: [ast],
pc: integer,
running: boolean
}
# increment the program counter by one.
defp inc_pc(%__MODULE__{pc: pc} = vm), do: %{vm | pc: pc + 1}
def step(%__MODULE__{running: true} = vm) do
vm
|> inc_pc()
|> do_step()
end
def step(%__MODULE__{running: false} = vm), do: vm
defp do_step(%__MODULE__{} = vm) do
case vm.program |> Enum.at(vm.pc) do
%Ast{kind: kind, args: args, body: body} = ast ->
Logger.info("Doing #{inspect(ast)}")
# Turn kind into an instruction
instruction = Module.concat([kind])
# Lookup the implementation. This could raise.
impl = vm.instruction_set[instruction]
# Build a new stack frame and put it on the stack.
sf = %StackFrame{return_address: vm.pc, args: args, body: body}
vm = %{vm | call_stack: [sf | vm.call_stack]}
try do
# Execute the implementation.
impl.eval(vm)
rescue
ex in VmError -> reraise(ex, System.stacktrace())
ex -> raise VmError, machine: vm, exception: ex
end
nil ->
%{vm | running: false}
end
end
end

View file

@ -0,0 +1,8 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction do
@moduledoc "Behaviour for implementing CeleryScript nodes."
alias Farmbot.CeleryScript.AST
@callback precompile(AST.t) :: {:ok, AST.t} | {:error, term}
@callback execute() :: {:ok, Ast.t} | {:error, term}
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.If do
@moduledoc """
_if
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.Calibrate do
@moduledoc """
calibrate
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.CheckUpdates do
@moduledoc """
check_updates
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.ConfigUpdate do
@moduledoc """
config_update
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.Coordinate do
@moduledoc """
coordinate
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.DataUpdate do
@moduledoc """
data_update
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.EmergencyLock do
@moduledoc """
emergency_lock
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.EmergencyUnlock do
@moduledoc """
emergency_unlock
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,19 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.Execute do
@moduledoc """
execute
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
# require IEx; IEx.pry
res = Farmbot.HTTP.get!("/api/sequences/#{ast.args.sequence_id}") |> Map.get(:body) |> Poison.decode! |> Farmbot.CeleryScript.AST.parse
{:ok, res}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.ExecuteScript do
@moduledoc """
execute_script
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.Explanation do
@moduledoc """
explanation
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.FactoryReset do
@moduledoc """
factory_reset
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.FindHome do
@moduledoc """
find_home
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.Home do
@moduledoc """
home
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.InstallFarmware do
@moduledoc """
install_farmware
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.InstallFirstPartyFarmware do
@moduledoc """
install_first_party_farmware
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.MoveAbsolute do
@moduledoc """
move_absolute
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.MoveRelative do
@moduledoc """
move_relative
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.Nothing do
@moduledoc """
nothing
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.Pair do
@moduledoc """
pair
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.PowerOff do
@moduledoc """
power_off
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.ReadAllParams do
@moduledoc """
read_all_params
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.ReadParam do
@moduledoc """
read_param
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.ReadPin do
@moduledoc """
read_pin
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.ReadStatus do
@moduledoc """
read_status
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.Reboot do
@moduledoc """
reboot
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.RemoveFarmware do
@moduledoc """
remove_farmware
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule RPCError do
@moduledoc """
rpc_error
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule RPCOk do
@moduledoc """
rpc_ok
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule RPCRequest do
@moduledoc """
rpc_request
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.SendMessage do
@moduledoc """
send_message
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.Sequence do
@moduledoc """
sequence
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.SetUserEnv do
@moduledoc """
set_user_env
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.Sync do
@moduledoc """
sync
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.TakePhoto do
@moduledoc """
take_photo
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.TogglePin do
@moduledoc """
toggle_pin
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.UpdateFarmware do
@moduledoc """
update_farmware
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.Wait do
@moduledoc """
wait
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.WritePin do
@moduledoc """
write_pin
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -0,0 +1,17 @@
defmodule Farmbot.CeleryScript.VirtualMachine.Instruction.Zero do
@moduledoc """
zero
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end

View file

@ -1,62 +1,103 @@
defmodule Farmbot.CeleryScript.VirtualMachine.InstructionSet do
@moduledoc """
Instruction Set for a virtual machine.
This will allow swapping of instructions between machine executions.
"""
alias Farmbot.CeleryScript.VirtualMachine.UndefinedInstructionError
defstruct instructions: %{
# TODO(Connor) add back all the default modules here.
}
@typedoc "Instruction Set type."
@type t :: %__MODULE__{
instructions: %{optional(module) => module}
@moduledoc "Map of CeleryScript `kind` to implementation module."
alias Farmbot.CeleryScript.VirtualMachine.Instruction.{
Execute,
WritePin,
Nothing,
InstallFarmware,
Calibrate,
SetUserEnv,
MoveRelative,
RpcOk,
EmergencyUnlock,
MoveAbsolute,
ReadAllParams,
ReadParam,
InstallFirstPartyFarmware,
If,
Reboot,
RpcRequest,
Wait,
EmergencyLock,
TogglePin,
Zero,
ConfigUpdate,
RemoveFarmware,
ExecuteScript,
Sync,
TakePhoto,
RpcError,
Coordinate,
Pair,
Home,
UpdateFarmware,
Sequence,
PowerOff,
DataUpdate,
FactoryReset,
SendMessage,
Explanation,
FindHome,
ReadStatus,
CheckUpdates,
ReadPin
}
@doc false
def fetch(%__MODULE__{instructions: instrs}, instr) do
impl = instrs[instr] || raise UndefinedInstructionError, instr
{:ok, impl}
end
defstruct _if: If,
calibrate: Calibrate,
check_updates: CheckUpdates,
config_update: ConfigUpdate,
coordinate: Coordinate,
data_update: DataUpdate,
emergency_lock: EmergencyLock,
emergency_unlock: EmergencyUnlock,
execute: Execute,
execute_script: ExecuteScript,
explanation: Explanation,
factory_reset: FactoryReset,
find_home: FindHome,
home: Home,
install_farmware: InstallFarmware,
install_first_party_farmware: InstallFirstPartyFarmware,
move_absolute: MoveAbsolute,
move_relative: MoveRelative,
nothing: Nothing,
pair: Pair,
power_off: PowerOff,
read_all_params: ReadAllParams,
read_param: ReadParam,
read_pin: ReadPin,
read_status: ReadStatus,
reboot: Reboot,
remove_farmware: RemoveFarmware,
rpc_error: RPCError,
rpc_ok: RPCOk,
rpc_request: RPCRequest,
send_message: SendMessage,
sequence: Sequence,
set_user_env: SetUserEnv,
sync: Sync,
take_photo: TakePhoto,
toggle_pin: TogglePin,
update_farmware: UpdateFarmware,
wait: Wait,
write_pin: WritePin,
zero: Zero
@doc "Builds a new InstructionSet"
@spec new :: t
def new do
# don't use the default implementation.
%__MODULE__{instructions: %{}}
end
def fetch(instrs, instr) when is_binary(instr) do
valid_keys = Enum.map(Map.keys(instrs), &Atom.to_string(&1))
@doc "Implement an instruction. "
@spec impl(t, module, module) :: t
def impl(%__MODULE__{} = set, instruction, implementation)
when is_atom(instruction) and is_atom(implementation) do
implementation
|> ensure_loaded!
|> ensure_implemented!
instrs = Map.put(set.instructions, instruction, implementation)
%{set | instructions: instrs}
end
defp ensure_loaded!(impl) do
case Code.ensure_loaded(impl) do
{:module, _} ->
impl
{:error, _} ->
name = Macro.underscore(impl)
raise CompileError, description: "Failed to load implementation: #{name}."
if instr in valid_keys do
Map.fetch(instrs, :"#{instr}")
else
:error
end
end
defp ensure_implemented!(impl) do
unless function_exported?(impl, :eval, 1) do
name = Macro.underscore(impl)
raise CompileError, description: "#{name} does not implement CeleryScript."
def fetch(instrs, instr) do
case Map.get(instrs, instr) do
nil -> :error
mod -> {:ok, mod}
end
impl
end
end

View file

@ -1,31 +1,10 @@
defmodule Farmbot.CeleryScript.VirtualMachine.RuntimeError do
@moduledoc "Runtime Error of the Virtual Machine"
defexception [:message, :cs_stack_trace]
alias Farmbot.CeleryScript.VirtualMachine
defexception [:exception, :machine]
@doc "Requires a VirtualMachine state, and a message."
def exception(opts) do
machine =
case Keyword.get(opts, :machine) do
%VirtualMachine{} = machine -> machine
_ -> raise ArgumentError, "Machine state was not supplied to #{__MODULE__}."
end
exception =
Keyword.get(opts, :exception) ||
raise ArgumentError, "Exception was not supplied to #{__MODULE__}."
%__MODULE__{machine: machine, exception: exception}
message = Keyword.fetch!(opts, :message)
cs_stack_trace = Keyword.fetch!(opts, :cs_stack_trace)
%__MODULE__{message: message, cs_stack_trace: cs_stack_trace}
end
@doc false
def message(%__MODULE__{exception: ex}) do
Exception.message(ex)
end
@type t :: %__MODULE__{
exception: Exception.t(),
machine: Farmbot.CeleryScript.VirtualMachine.t()
}
end

View file

@ -1,14 +0,0 @@
defmodule Farmbot.CeleryScript.VirtualMachine.StackFrame do
@moduledoc "Frame of the callstack"
alias Farmbot.CeleryScript.Ast
@enforce_keys [:args, :return_address, :body]
defstruct [:args, :return_address, :body]
@typedoc "Part of a call stack."
@type t :: %__MODULE__{
args: Ast.args(),
body: Ast.body(),
return_address: integer
}
end

View file

@ -1,11 +0,0 @@
defmodule Farmbot.CeleryScript.VirtualMachine.UndefinedInstructionError do
@moduledoc "Undefined Instruction. Usually means something is not implemented."
defexception [:message, :instruction]
@doc false
def exception(instruction) do
instr = Macro.underscore(instruction)
%__MODULE__{message: "Undefined instruction: #{instr}", instruction: instruction}
end
end

View file

@ -0,0 +1,19 @@
defmodule Farmbot.CeleryScript.VirtualMachine do
@moduledoc "Executes CeleryScript"
alias Farmbot.CeleryScript.AST
alias AST.Compiler
alias Farmbot.CeleryScript.VirtualMachine.{InstructionSet, RuntimeError}
defmodule State do
@moduledoc false
defstruct instruction_set: struct(InstructionSet)
end
def execute(ast, state \\ %State{})
def execute(%AST{} = ast, state) do
ast |> Compiler.compile(state.instruction_set)
end
end

7
new_instruction.exs Normal file
View file

@ -0,0 +1,7 @@
Enum.each(struct(Farmbot.CeleryScript.VirtualMachine.InstructionSet) |> Map.from_struct(), fn({snake, camel}) ->
camel = Module.split(camel)
camel = Enum.join(camel, ".")
res = "#{:code.priv_dir(:farmbot)}/instruction.ex.eex" |> EEx.eval_file(camel_instruction: camel, snake_instruction: snake)
File.write!("lib/farmbot/celery_script/virtual_machine/instruction/#{snake}.ex", res)
end
)

17
priv/instruction.ex.eex Normal file
View file

@ -0,0 +1,17 @@
defmodule <%= camel_instruction %> do
@moduledoc """
<%= snake_instruction %>
"""
alias Farmbot.CeleryScript.AST
alias Farmbot.CeleryScript.VirtualMachine.Instruction
@behaviour Instruction
def precompile(%AST{} = ast) do
{:ok, ast}
end
def execute(args, body) do
end
end