infinate recursion...

This commit is contained in:
connor rigby 2017-10-24 12:19:23 -07:00
parent bec0c3c5d1
commit 7e60283d71
5 changed files with 66 additions and 167 deletions

View file

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

View file

@ -1,4 +1,4 @@
defmodule Farmbot.CeleryScript.AST.CompileError do
defmodule Farmbot.CeleryScript.AST.Compiler.CompileError do
defexception [:message]
def exception(message) do

View file

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

View file

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

View file

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