diff --git a/lib/farmbot/celery_script/ast/ast.ex b/lib/farmbot/celery_script/ast/ast.ex index a4583c8e..7c5cce60 100644 --- a/lib/farmbot/celery_script/ast/ast.ex +++ b/lib/farmbot/celery_script/ast/ast.ex @@ -4,21 +4,6 @@ defmodule Farmbot.CeleryScript.AST do Ast nodes. """ - defmodule Meta do - @moduledoc "Metadata about an AST node." - defstruct [precompiled: false, encoded: nil] - @type t :: %__MODULE__{ - precompiled: boolean, - encoded: binary - } - - 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 """ @@ -30,8 +15,7 @@ defmodule Farmbot.CeleryScript.AST do Type for CeleryScript Ast's. """ @type t :: %__MODULE__{ - __meta__: Meta.t, - uid: binary, + compile_meta: map | nil, args: args, body: [t, ...], kind: String.t(), @@ -39,14 +23,11 @@ defmodule Farmbot.CeleryScript.AST do } @enforce_keys [:args, :body, :kind] - defstruct [ - kind: nil, - uid: nil, - args: %{}, - body: [], - comment: nil, - __meta__: nil - ] + defstruct kind: nil, + compile_meta: nil, + args: %{}, + body: [], + comment: nil @doc """ Parses json and traverses the tree and turns everything can @@ -58,10 +39,7 @@ defmodule Farmbot.CeleryScript.AST do def parse(%{"kind" => kind, "args" => args} = thing) do body = thing["body"] || [] comment = thing["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} + %__MODULE__{kind: kind, args: parse_args(args), body: parse(body), comment: comment} end def parse(%{__struct__: _} = thing) do @@ -71,10 +49,7 @@ defmodule Farmbot.CeleryScript.AST do def parse(%{kind: kind, args: args} = thing) do body = thing[:body] || [] comment = thing[: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} + %__MODULE__{kind: kind, body: parse(body), args: parse_args(args), comment: comment} end # You can give a list of nodes. @@ -102,16 +77,4 @@ defmodule Farmbot.CeleryScript.AST do end end) end - - @doc """ - Creates a new AST node. No validation is preformed on this other than making - sure its syntax is valid. - """ - 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 diff --git a/lib/farmbot/celery_script/ast/compile_error.ex b/lib/farmbot/celery_script/ast/compiler/compile_error.ex similarity index 62% rename from lib/farmbot/celery_script/ast/compile_error.ex rename to lib/farmbot/celery_script/ast/compiler/compile_error.ex index 20e3d240..863a0080 100644 --- a/lib/farmbot/celery_script/ast/compile_error.ex +++ b/lib/farmbot/celery_script/ast/compiler/compile_error.ex @@ -1,4 +1,4 @@ -defmodule Farmbot.CeleryScript.AST.CompileError do +defmodule Farmbot.CeleryScript.AST.Compiler.CompileError do defexception [:message] def exception(message) do diff --git a/lib/farmbot/celery_script/ast/compiler/compiler.ex b/lib/farmbot/celery_script/ast/compiler/compiler.ex index 14c300b8..0f0d6858 100644 --- a/lib/farmbot/celery_script/ast/compiler/compiler.ex +++ b/lib/farmbot/celery_script/ast/compiler/compiler.ex @@ -1,87 +1,82 @@ defmodule Farmbot.CeleryScript.AST.Compiler do require Logger - alias Farmbot.CeleryScript.AST - alias Farmbot.CeleryScript.AST.CompileError + alias Farmbot.CeleryScript.AST.Compiler.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 + def compile(ast, %InstructionSet{} = instruction_set) do + res = ast + |> tag(:precompile) + |> ensure_impl(instruction_set) + |> precompile(instruction_set, []) + require IEx; IEx.pry end - def ensure_implementation!(%AST{kind: kind, body: []} = ast, instrs) do - :ok = do_ensure(kind, instrs) - ast + def tag(ast, step) do + %{ast | compile_meta: Map.put(ast.compile_meta || %{}, :step, step), body: tag_body(ast.body, step)} end - def ensure_implementation!(%AST{kind: kind, body: body} = ast, instrs) do - :ok = do_ensure(kind, instrs) - ensure_implementation!(body, instrs) - ast + defp tag_body(body, step, acc \\ []) + + defp tag_body([ast | rest], step, acc) do + tag_body(rest, step, [tag(ast, step) | acc]) 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 + defp tag_body([], _step, acc), do: Enum.reverse(acc) - 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." + def ensure_impl(ast, %InstructionSet{} = instruction_set) do + unless instruction_set[ast.kind] do + raise CompileError, "#{ast.kind} has no implementation." end + + unless Code.ensure_loaded?(instruction_set[ast.kind]) do + raise CompileError, "#{ast.kind} implementation could not be loaded." + end + + %{ast | body: ensure_impl_body(ast.body, instruction_set)} end - # sequence - # -> execute_sequence - # -> move_abs + def ensure_impl_body(body, instruction_set, acc \\ []) - def precompile!(ast, instrs, state \\ %{}) do - impl = instrs[ast.kind] - if state[ast.__meta__.encoded] do - compiler_debug_log("#{inspect ast} compiled") - {state, ast} + def ensure_impl_body([ast | rest], %InstructionSet{} = instruction_set, acc) do + ensure_impl_body(rest, instruction_set, [ensure_impl(ast, instruction_set) | acc]) + end + + def ensure_impl_body([], %InstructionSet{} = _instruction_set, acc), do: Enum.reverse(acc) + + def precompile(ast, %InstructionSet{} = instruction_set, cache) do + md5 = :crypto.hash(:md5, Poison.encode!(ast)) |> Base.encode16 + IO.puts "precompiling: #{inspect ast}: #{inspect ast.compile_meta} => #{md5}" + IO.inspect cache + if md5 in cache do + 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) + cache = [md5 | cache] + case instruction_set[ast.kind].precompile(ast) do {:error, reason} -> raise CompileError, reason + {:ok, precompiled} -> + precompiled = %{precompiled | body: precompile_body(precompiled.body, instruction_set, cache)} + IO.inspect precompiled.compile_meta + case precompiled.compile_meta do + nil -> + precompiled + |> tag(:precompile) + |> ensure_impl(instruction_set) + |> precompile(instruction_set, cache) + |> tag(:compile) + %{step: :precompile} -> tag(precompiled, :compile) + end end end + end - defp precompile_body(body, instrs, state, acc \\ []) + def precompile_body(body, instruction_set, cache, 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) + def precompile_body([ast | rest], %InstructionSet{} = instruction_set, cache, acc) do + precompile_body(rest, instruction_set, cache, [precompile(ast, instruction_set, cache) | acc]) end - defp precompile_body([], instrs, state, acc) do - {state, Enum.reverse(acc)} - end + def precompile_body([], %InstructionSet{} = _instruction_set, _cache, acc), do: Enum.reverse(acc) end + # Farmbot.HTTP.get!("/api/sequences/2") |> Map.get(:body) |> Poison.decode! |> Farmbot.CeleryScript.AST.parse |> Farmbot.CeleryScript.VirtualMachine.execute diff --git a/lib/farmbot/celery_script/ast/compiler/utils.ex b/lib/farmbot/celery_script/ast/compiler/utils.ex deleted file mode 100644 index f547b4c9..00000000 --- a/lib/farmbot/celery_script/ast/compiler/utils.ex +++ /dev/null @@ -1,59 +0,0 @@ -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 diff --git a/lib/farmbot/celery_script/ast/inspect.ex b/lib/farmbot/celery_script/ast/inspect.ex index f7daf111..9fcc1911 100644 --- a/lib/farmbot/celery_script/ast/inspect.ex +++ b/lib/farmbot/celery_script/ast/inspect.ex @@ -1,5 +1,5 @@ defimpl Inspect, for: Farmbot.CeleryScript.AST do def inspect(ast, _opts) do - "#CeleryScript[#{ast.uid}]<#{ast.kind}: #{inspect(Map.keys(ast.args))}>" + "#CeleryScript<#{ast.kind}: #{inspect(Map.keys(ast.args))}>" end end