Cleanup+document new ElixirAST compiler
parent
cd90a1f18a
commit
fbb9d294a8
|
@ -1,194 +1,199 @@
|
|||
{
|
||||
"kind": "sequence",
|
||||
"name": "Test Sequence (TM)",
|
||||
"color": "red",
|
||||
"id": 2,
|
||||
"args": {
|
||||
"version": 20180209,
|
||||
"locals": {
|
||||
"kind": "scope_declaration",
|
||||
"args": {}
|
||||
}
|
||||
},
|
||||
"body": [{
|
||||
"kind": "move_absolute",
|
||||
"args": {
|
||||
"speed": 100,
|
||||
"offset": {
|
||||
"kind": "coordinate",
|
||||
"args": {
|
||||
"y": 20,
|
||||
"x": 10,
|
||||
"z": -30
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"kind": "point",
|
||||
"args": {
|
||||
"pointer_type": "Plant",
|
||||
"pointer_id": 1
|
||||
}
|
||||
"kind": "sequence",
|
||||
"name": "Test Sequence (TM)",
|
||||
"color": "red",
|
||||
"id": 2,
|
||||
"comment": "This is the root",
|
||||
"args": {
|
||||
"version": 20180209,
|
||||
"locals": {
|
||||
"kind": "scope_declaration",
|
||||
"args": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "move_relative",
|
||||
"args": {
|
||||
"x": 10,
|
||||
"y": 20,
|
||||
"z": 30,
|
||||
"speed": 50
|
||||
},
|
||||
"comment": "Slow move"
|
||||
},
|
||||
{
|
||||
"kind": "write_pin",
|
||||
"args": {
|
||||
"pin_number": 0,
|
||||
"pin_value": 0,
|
||||
"pin_mode": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "write_pin",
|
||||
"args": {
|
||||
"pin_mode": 0,
|
||||
"pin_value": 1,
|
||||
"pin_number": {
|
||||
"kind": "named_pin",
|
||||
"args": {
|
||||
"pin_type": "Peripheral",
|
||||
"pin_id": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "read_pin",
|
||||
"args": {
|
||||
"pin_mode": 0,
|
||||
"label": "---",
|
||||
"pin_number": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "read_pin",
|
||||
"args": {
|
||||
"pin_mode": 1,
|
||||
"label": "---",
|
||||
"pin_number": {
|
||||
"kind": "named_pin",
|
||||
"args": {
|
||||
"pin_type": "Sensor",
|
||||
"pin_id": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "wait",
|
||||
"args": {
|
||||
"milliseconds": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "send_message",
|
||||
"args": {
|
||||
"message": "FarmBot is at position {{ x }}, {{ y }}, {{ z }}.",
|
||||
"message_type": "success"
|
||||
},
|
||||
"body": [{
|
||||
"kind": "channel",
|
||||
"args": {
|
||||
"channel_name": "toast"
|
||||
}
|
||||
"body": [
|
||||
{
|
||||
"kind": "move_absolute",
|
||||
"args": {
|
||||
"speed": 100,
|
||||
"offset": {
|
||||
"kind": "coordinate",
|
||||
"args": {
|
||||
"y": 20,
|
||||
"x": 10,
|
||||
"z": -30
|
||||
}
|
||||
},
|
||||
"location": {
|
||||
"kind": "point",
|
||||
"args": {
|
||||
"pointer_type": "Plant",
|
||||
"pointer_id": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "channel",
|
||||
"args": {
|
||||
"channel_name": "email"
|
||||
}
|
||||
"kind": "move_relative",
|
||||
"args": {
|
||||
"x": 10,
|
||||
"y": 20,
|
||||
"z": 30,
|
||||
"speed": 50
|
||||
},
|
||||
"comment": "Slow move"
|
||||
},
|
||||
{
|
||||
"kind": "channel",
|
||||
"args": {
|
||||
"channel_name": "espeak"
|
||||
}
|
||||
"kind": "write_pin",
|
||||
"args": {
|
||||
"pin_number": 0,
|
||||
"pin_value": 0,
|
||||
"pin_mode": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "write_pin",
|
||||
"args": {
|
||||
"pin_mode": 0,
|
||||
"pin_value": 1,
|
||||
"pin_number": {
|
||||
"kind": "named_pin",
|
||||
"args": {
|
||||
"pin_type": "Peripheral",
|
||||
"pin_id": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "read_pin",
|
||||
"args": {
|
||||
"pin_mode": 0,
|
||||
"label": "---",
|
||||
"pin_number": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "read_pin",
|
||||
"args": {
|
||||
"pin_mode": 1,
|
||||
"label": "---",
|
||||
"pin_number": {
|
||||
"kind": "named_pin",
|
||||
"args": {
|
||||
"pin_type": "Sensor",
|
||||
"pin_id": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "wait",
|
||||
"args": {
|
||||
"milliseconds": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "send_message",
|
||||
"args": {
|
||||
"message": "FarmBot is at position {{ x }}, {{ y }}, {{ z }}.",
|
||||
"message_type": "success"
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"kind": "channel",
|
||||
"args": {
|
||||
"channel_name": "toast"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "channel",
|
||||
"args": {
|
||||
"channel_name": "email"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "channel",
|
||||
"args": {
|
||||
"channel_name": "espeak"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "find_home",
|
||||
"args": {
|
||||
"speed": 100,
|
||||
"axis": "all"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "_if",
|
||||
"args": {
|
||||
"rhs": 0,
|
||||
"op": "is_undefined",
|
||||
"lhs": "x",
|
||||
"_then": {
|
||||
"kind": "execute",
|
||||
"args": {
|
||||
"sequence_id": 1
|
||||
}
|
||||
},
|
||||
"_else": {
|
||||
"kind": "nothing",
|
||||
"args": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "_if",
|
||||
"args": {
|
||||
"rhs": 500,
|
||||
"op": ">",
|
||||
"_then": {
|
||||
"kind": "nothing",
|
||||
"args": {}
|
||||
},
|
||||
"_else": {
|
||||
"kind": "execute",
|
||||
"args": {
|
||||
"sequence_id": 1
|
||||
}
|
||||
},
|
||||
"lhs": {
|
||||
"kind": "named_pin",
|
||||
"args": {
|
||||
"pin_type": "Sensor",
|
||||
"pin_id": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "execute",
|
||||
"args": {
|
||||
"sequence_id": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "execute_script",
|
||||
"args": {
|
||||
"label": "plant-detection"
|
||||
},
|
||||
"body": [
|
||||
{
|
||||
"kind": "pair",
|
||||
"args": {
|
||||
"value": 0,
|
||||
"label": "plant_detection_input"
|
||||
},
|
||||
"comment": "Input"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "take_photo",
|
||||
"args": {}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "find_home",
|
||||
"args": {
|
||||
"speed": 100,
|
||||
"axis": "all"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "_if",
|
||||
"args": {
|
||||
"rhs": 0,
|
||||
"op": "is_undefined",
|
||||
"lhs": "x",
|
||||
"_then": {
|
||||
"kind": "execute",
|
||||
"args": {
|
||||
"sequence_id": 1
|
||||
}
|
||||
},
|
||||
"_else": {
|
||||
"kind": "nothing",
|
||||
"args": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "_if",
|
||||
"args": {
|
||||
"rhs": 500,
|
||||
"op": ">",
|
||||
"_then": {
|
||||
"kind": "nothing",
|
||||
"args": {}
|
||||
},
|
||||
"_else": {
|
||||
"kind": "execute",
|
||||
"args": {
|
||||
"sequence_id": 1
|
||||
}
|
||||
},
|
||||
"lhs": {
|
||||
"kind": "named_pin",
|
||||
"args": {
|
||||
"pin_type": "Sensor",
|
||||
"pin_id": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "execute",
|
||||
"args": {
|
||||
"sequence_id": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "execute_script",
|
||||
"args": {
|
||||
"label": "plant-detection"
|
||||
},
|
||||
"body": [{
|
||||
"kind": "pair",
|
||||
"args": {
|
||||
"value": 0,
|
||||
"label": "plant_detection_input"
|
||||
},
|
||||
"comment": "Input"
|
||||
}]
|
||||
},
|
||||
{
|
||||
"kind": "take_photo",
|
||||
"args": {}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
|
@ -4,7 +4,7 @@ defmodule Farmbot.CeleryScript.AST do
|
|||
Ast nodes.
|
||||
"""
|
||||
alias Farmbot.CeleryScript.AST
|
||||
alias AST.{Heap, Slicer, Unslicer}
|
||||
alias AST.{Compiler, Heap, Slicer, Unslicer}
|
||||
|
||||
@typedoc "Arguments to a ast node."
|
||||
@type args :: map
|
||||
|
@ -30,7 +30,7 @@ defmodule Farmbot.CeleryScript.AST do
|
|||
def decode(map_or_list_of_maps)
|
||||
|
||||
def decode(%{__struct__: _} = thing) do
|
||||
thing |> Map.from_struct() |> decode
|
||||
thing |> Map.from_struct() |> decode()
|
||||
end
|
||||
|
||||
def decode(%{} = thing) do
|
||||
|
@ -71,11 +71,12 @@ defmodule Farmbot.CeleryScript.AST do
|
|||
end
|
||||
|
||||
@spec new(atom, map, [map]) :: t()
|
||||
def new(kind, args, body) when is_map(args) and is_list(body) do
|
||||
def new(kind, args, body, comment \\ nil) when is_map(args) and is_list(body) do
|
||||
%__MODULE__{
|
||||
kind: String.to_atom(to_string(kind)),
|
||||
args: args,
|
||||
body: body
|
||||
body: body,
|
||||
comment: comment
|
||||
}
|
||||
|> decode()
|
||||
end
|
||||
|
@ -86,4 +87,6 @@ defmodule Farmbot.CeleryScript.AST do
|
|||
@spec unslice(Heap.t(), Address.t()) :: AST.t()
|
||||
def unslice(%Heap{} = heap, %Address{} = addr),
|
||||
do: Unslicer.run(heap, addr)
|
||||
|
||||
defdelegate compile(ast), to: Compiler
|
||||
end
|
||||
|
|
|
@ -8,9 +8,19 @@ defmodule Farmbot.CeleryScript.AST.Compiler do
|
|||
|
||||
@doc """
|
||||
Recursive function that will emit Elixir AST from CeleryScript AST.
|
||||
|
||||
## CeleryEnv
|
||||
A keyword list of the compile environment.
|
||||
* resource_id
|
||||
* resource_type
|
||||
"""
|
||||
def compile(ast, celery_env \\ [resource_id: 0, resource_type: "unspecified"])
|
||||
|
||||
# Compiles a `sequence` into an Elixir `fn`.
|
||||
def compile(%AST{kind: :sequence, args: %{locals: %{body: params}}, body: block}) do
|
||||
def compile(
|
||||
%AST{kind: :sequence, args: %{locals: %{body: params}}, body: block} = ast,
|
||||
celery_env
|
||||
) do
|
||||
# Sort the args.body into two arrays.
|
||||
# The `params` side gets turned into
|
||||
# a keyword list. These `params` are passed in from a previous sequence.
|
||||
|
@ -30,13 +40,18 @@ defmodule Farmbot.CeleryScript.AST.Compiler do
|
|||
# parent = Keyword.fetch!(params, :parent)
|
||||
unquote_splicing(params)
|
||||
# Unquote the remaining sequence steps.
|
||||
unquote(compile_block(body ++ block))
|
||||
unquote(compile_block(body ++ block, celery_env))
|
||||
end
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# Compiles a variable asignment.
|
||||
def compile(%AST{kind: :variable_declaration, args: %{label: var_name, data_value: ast}}) do
|
||||
def compile(
|
||||
%AST{kind: :variable_declaration, args: %{label: var_name, data_value: data_value_ast}} =
|
||||
ast,
|
||||
celery_env
|
||||
) do
|
||||
# Compiles the `data_value`
|
||||
# and assigns the result to a variable named `label`
|
||||
# Example:
|
||||
|
@ -54,15 +69,21 @@ defmodule Farmbot.CeleryScript.AST.Compiler do
|
|||
# }
|
||||
# }
|
||||
# Will be turned into:
|
||||
# parent = point("Plant", 456)
|
||||
{:=, [], [{String.to_atom(var_name), [], __MODULE__}, compile(ast)]}
|
||||
# parent = point("Plant", 456)
|
||||
# NOTE: This needs to be Elixir AST syntax, not quoted
|
||||
# because var! doesn't do what what we need.
|
||||
{:=, [], [{String.to_atom(var_name), [], __MODULE__}, compile(data_value_ast, celery_env)]}
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# Compiles an if statement.
|
||||
def compile(%AST{
|
||||
kind: :_if,
|
||||
args: %{_then: then_ast, _else: else_ast, lhs: lhs, op: op, rhs: rhs}
|
||||
}) do
|
||||
def compile(
|
||||
%AST{
|
||||
kind: :_if,
|
||||
args: %{_then: then_ast, _else: else_ast, lhs: lhs, op: op, rhs: rhs}
|
||||
} = ast,
|
||||
celery_env
|
||||
) do
|
||||
# Turns the left hand side arg into
|
||||
# a number. x, y, z, and pin{number} are special that need to be
|
||||
# evaluated before evaluating the if statement.
|
||||
|
@ -74,7 +95,7 @@ defmodule Farmbot.CeleryScript.AST.Compiler do
|
|||
"y" -> {:get_current_y, [], []}
|
||||
"z" -> {:get_current_z, [], []}
|
||||
"pin" <> pin -> {:read_pin, [], [String.to_integer(pin), nil]}
|
||||
%AST{} = ast -> compile(ast)
|
||||
%AST{} = ast -> compile(ast, celery_env)
|
||||
end
|
||||
|
||||
# Turn the `op` arg into Elixir code
|
||||
|
@ -89,13 +110,13 @@ defmodule Farmbot.CeleryScript.AST.Compiler do
|
|||
# read_pin(22, nil) == 5
|
||||
# The ast will look like: {:==, [], lhs, compile(rhs)}
|
||||
quote do
|
||||
unquote(lhs) == unquote(compile(lhs))
|
||||
unquote(lhs) == unquote(compile(lhs, celery_env))
|
||||
end
|
||||
|
||||
"not" ->
|
||||
# ast will look like: {:!=, [], [lhs, compile(rhs)]}
|
||||
quote do
|
||||
unquote(lhs) != unquote(compile(rhs))
|
||||
unquote(lhs) != unquote(compile(rhs, celery_env))
|
||||
end
|
||||
|
||||
"is_undefined" ->
|
||||
|
@ -107,21 +128,16 @@ defmodule Farmbot.CeleryScript.AST.Compiler do
|
|||
"<" ->
|
||||
# ast will look like: {:<, [], [lhs, compile(rhs)]}
|
||||
quote do
|
||||
unquote(lhs) < unquote(compile(rhs))
|
||||
unquote(lhs) < unquote(compile(rhs, celery_env))
|
||||
end
|
||||
|
||||
">" ->
|
||||
# ast will look like: {:>, [], [lhs, compile(rhs)]}
|
||||
quote do
|
||||
unquote(lhs) > unquote(compile(rhs))
|
||||
unquote(lhs) > unquote(compile(rhs, celery_env))
|
||||
end
|
||||
end
|
||||
|
||||
# {:if, [], [
|
||||
# if_eval,
|
||||
# [do: compile_block(then_ast), else: compile_block(else_ast)]
|
||||
# ]}
|
||||
|
||||
# Finally, compile the entire if statement.
|
||||
# outputted code will look something like:
|
||||
# if get_current_x() == 123 do
|
||||
|
@ -131,19 +147,23 @@ defmodule Farmbot.CeleryScript.AST.Compiler do
|
|||
# end
|
||||
quote do
|
||||
if unquote(if_eval),
|
||||
do: unquote(compile_block(then_ast)),
|
||||
else: unquote(compile_block(else_ast))
|
||||
do: unquote(compile_block(then_ast, celery_env)),
|
||||
else: unquote(compile_block(else_ast, celery_env))
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# Compiles an `execute` block.
|
||||
def compile(%AST{kind: :execute, args: %{sequence_id: id}, body: variable_declarations}) do
|
||||
def compile(
|
||||
%AST{kind: :execute, args: %{sequence_id: id}, body: variable_declarations} = ast,
|
||||
celery_env
|
||||
) do
|
||||
quote do
|
||||
# We have to lookup the sequence by it's id.
|
||||
case get_sequence(unquote(id)) do
|
||||
{:ok, %AST{} = ast} ->
|
||||
{:ok, %AST{} = ast, new_celery_env} ->
|
||||
# compile the ast
|
||||
fun = unquote(__MODULE__).compile_block(ast)
|
||||
fun = unquote(__MODULE__).compile(ast, new_celery_env)
|
||||
# And call it, serializing all the variables it expects.
|
||||
# see the `compile_param_application/1` docs for more info.
|
||||
fun.(unquote(compile_param_application(variable_declarations)))
|
||||
|
@ -152,117 +172,149 @@ defmodule Farmbot.CeleryScript.AST.Compiler do
|
|||
handle_error(err)
|
||||
end
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# Compiles `execute_script`
|
||||
# TODO(Connor) - make this actually usable
|
||||
def compile(%AST{kind: :execute_script, args: %{label: package}, body: params}) do
|
||||
def compile(
|
||||
%AST{kind: :execute_script, args: %{label: package}, body: params} = ast,
|
||||
celery_env
|
||||
) do
|
||||
env =
|
||||
Enum.map(params, fn %{args: %{label: key, value: value}} ->
|
||||
{to_string(key), value}
|
||||
end)
|
||||
|
||||
# {:execute_script, [], [package, {:%{}, [], env}]}
|
||||
quote do
|
||||
execute_script(unquote(package), unquote(Map.new(env)))
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# TODO(Connor) - see above TODO
|
||||
def compile(%AST{kind: :take_photo}) do
|
||||
def compile(%AST{kind: :take_photo} = ast, celery_env) do
|
||||
# {:execute_script, [], ["take_photo", {:%{}, [], []}]}
|
||||
quote do
|
||||
execute_script("take_photo", %{})
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# Compiles a nothing block.
|
||||
def compile(%AST{kind: :nothing}) do
|
||||
def compile(%AST{kind: :nothing} = ast, celery_env) do
|
||||
# AST looks like: {:nothing, [], []}
|
||||
quote do
|
||||
nothing()
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# Compiles move_absolute
|
||||
def compile(%AST{
|
||||
kind: :move_absolute,
|
||||
args: %{location: location, offset: offset, speed: speed}
|
||||
}) do
|
||||
def compile(
|
||||
%AST{
|
||||
kind: :move_absolute,
|
||||
args: %{location: location, offset: offset, speed: speed}
|
||||
} = ast,
|
||||
celery_env
|
||||
) do
|
||||
quote do
|
||||
# Extract the location arg
|
||||
%{x: locx, y: locy, z: locz} = unquote(compile(location))
|
||||
%{x: locx, y: locy, z: locz} = unquote(compile(location, celery_env))
|
||||
# Extract the offset arg
|
||||
%{x: offx, y: offy, z: offz} = unquote(compile(offset))
|
||||
%{x: offx, y: offy, z: offz} = unquote(compile(offset, celery_env))
|
||||
|
||||
# Subtract the location from offset.
|
||||
# Note: list syntax here for readability.
|
||||
[x, y, z] = [offx - locx, offy - locy, offz - locz]
|
||||
move_absolute(x, y, z, unquote(compile(speed)))
|
||||
move_absolute(x, y, z, unquote(compile(speed, celery_env)))
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# compiles move_relative into move absolute
|
||||
def compile(%AST{kind: :move_relative, args: %{x: x, y: y, z: z, speed: speed}}) do
|
||||
def compile(
|
||||
%AST{kind: :move_relative, args: %{x: x, y: y, z: z, speed: speed}} = ast,
|
||||
celery_env
|
||||
) do
|
||||
quote do
|
||||
# build a vec3 of passed in args
|
||||
%{x: locx, y: locy, z: locz} = %{
|
||||
x: unquote(compile(x)),
|
||||
y: unquote(compile(y)),
|
||||
z: unquote(compile(z))
|
||||
x: unquote(compile(x, celery_env)),
|
||||
y: unquote(compile(y, celery_env)),
|
||||
z: unquote(compile(z, celery_env))
|
||||
}
|
||||
|
||||
# build a vec3 of the current position
|
||||
%{x: offx, y: offy, z: offz} = %{x: get_current_x(), y: get_current_y, z: get_current_y()}
|
||||
%{x: offx, y: offy, z: offz} = %{
|
||||
x: get_current_x(),
|
||||
y: get_current_y,
|
||||
z: get_current_y()
|
||||
}
|
||||
|
||||
# Subtract the location from offset.
|
||||
# Note: list syntax here for readability.
|
||||
[x, y, z] = [offx - locx, offy - locy, offz - locz]
|
||||
move_absolute(x, y, z, unquote(compile(speed)))
|
||||
move_absolute(x, y, z, unquote(compile(speed, celery_env)))
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# compiles write_pin
|
||||
def compile(%AST{kind: :write_pin, args: %{pin_number: num, pin_mode: mode, pin_value: val}}) do
|
||||
# {:write_pin, [], [compile(num), compile(mode), compile(val)]}
|
||||
def compile(
|
||||
%AST{kind: :write_pin, args: %{pin_number: num, pin_mode: mode, pin_value: val}} = ast,
|
||||
celery_env
|
||||
) do
|
||||
quote do
|
||||
write_pin(unquote(compile(num)), unquote(compile(mode)), unquote(compile(val)))
|
||||
write_pin(
|
||||
unquote(compile(num, celery_env)),
|
||||
unquote(compile(mode, celery_env)),
|
||||
unquote(compile(val, celery_env))
|
||||
)
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# compiles read_pin
|
||||
def compile(%AST{kind: :read_pin, args: %{pin_number: num, pin_mode: mode}}) do
|
||||
# {:read_pin, [], [compile(num), compile(mode)]}
|
||||
def compile(%AST{kind: :read_pin, args: %{pin_number: num, pin_mode: mode}} = ast, celery_env) do
|
||||
quote do
|
||||
read_pin(unquote(compile(num)), unquote(compile(mode)))
|
||||
read_pin(unquote(compile(num, celery_env)), unquote(compile(mode, celery_env)))
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# Expands find_home(all) into three find_home/1 calls
|
||||
def compile(%AST{kind: :find_home, args: %{axis: "all", speed: speed}}) do
|
||||
def compile(%AST{kind: :find_home, args: %{axis: "all", speed: speed}} = ast, celery_env) do
|
||||
quote do
|
||||
find_home("x", unquote(compile(speed)))
|
||||
find_home("y", unquote(compile(speed)))
|
||||
find_home("z", unquote(compile(speed)))
|
||||
find_home("x", unquote(compile(speed, celery_env)))
|
||||
find_home("y", unquote(compile(speed, celery_env)))
|
||||
find_home("z", unquote(compile(speed, celery_env)))
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# compiles find_home
|
||||
def compile(%AST{kind: :find_home, args: %{axis: axis, speed: speed}}) do
|
||||
def compile(%AST{kind: :find_home, args: %{axis: axis, speed: speed}} = ast, celery_env) do
|
||||
quote do
|
||||
find_home(unquote(compile(axis)), unquote(compile(speed)))
|
||||
find_home(unquote(compile(axis, celery_env)), unquote(compile(speed, celery_env)))
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# compiles wait
|
||||
def compile(%AST{kind: :wait, args: %{milliseconds: millis}}) do
|
||||
def compile(%AST{kind: :wait, args: %{milliseconds: millis}} = ast, celery_env) do
|
||||
quote do
|
||||
find_home(unquote(compile(millis)))
|
||||
find_home(unquote(compile(millis, celery_env)))
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# compiles send_message
|
||||
def compile(%AST{kind: :send_message, args: %{message: msg, message_type: type}, body: channels}) do
|
||||
def compile(
|
||||
%AST{kind: :send_message, args: %{message: msg, message_type: type}, body: channels} =
|
||||
ast,
|
||||
celery_env
|
||||
) do
|
||||
# body gets turned into a list of atoms.
|
||||
# Example:
|
||||
# [{kind: "channel", args: {channel_name: "email"}}]
|
||||
|
@ -275,69 +327,84 @@ defmodule Farmbot.CeleryScript.AST.Compiler do
|
|||
|
||||
quote do
|
||||
# send_message("success", "Hello world!", [:email, :toast])
|
||||
send_message(unquote(compile(type)), unquote(compile(msg)), unquote(channels))
|
||||
send_message(
|
||||
unquote(compile(type, celery_env)),
|
||||
unquote(compile(msg, celery_env)),
|
||||
unquote(channels)
|
||||
)
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# compiles coordinate
|
||||
# Coordinate should return a vec3
|
||||
def compile(%AST{kind: :coordinate, args: %{x: x, y: y, z: z}}) do
|
||||
def compile(%AST{kind: :coordinate, args: %{x: x, y: y, z: z}} = ast, celery_env) do
|
||||
quote do
|
||||
coordinate(unquote(compile(x)), unquote(compile(y)), unquote(compile(z)))
|
||||
coordinate(
|
||||
unquote(compile(x, celery_env)),
|
||||
unquote(compile(y, celery_env)),
|
||||
unquote(compile(z, celery_env))
|
||||
)
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# compiles point
|
||||
def compile(%AST{kind: :point, args: %{pointer_type: type, pointer_id: id}}) do
|
||||
def compile(%AST{kind: :point, args: %{pointer_type: type, pointer_id: id}} = ast, celery_env) do
|
||||
quote do
|
||||
point(unquote(compile(type)), unquote(compile(id)))
|
||||
point(unquote(compile(type, celery_env)), unquote(compile(id, celery_env)))
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# compile a named pin
|
||||
def compile(%AST{kind: :named_pin, args: %{pin_id: id, pin_type: type}}) do
|
||||
def compile(%AST{kind: :named_pin, args: %{pin_id: id, pin_type: type}} = ast, celery_env) do
|
||||
quote do
|
||||
pin(unquote(compile(type)), unquote(compile(id)))
|
||||
pin(unquote(compile(type, celery_env)), unquote(compile(id, celery_env)))
|
||||
end
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# compiles identifier into a variable.
|
||||
# We have to use Elixir ast syntax here because
|
||||
# var! doesn't work quite the way we want.
|
||||
def compile(%AST{kind: :identifier, args: %{label: var_name}}) do
|
||||
{String.to_atom(var_name), [], __MODULE__}
|
||||
def compile(%AST{kind: :identifier, args: %{label: var_name}} = ast, celery_env) do
|
||||
meta = [celery_kind: :identifier, celery_comments: []]
|
||||
|
||||
{String.to_atom(var_name), meta, __MODULE__}
|
||||
|> add_meta(ast, celery_env)
|
||||
end
|
||||
|
||||
# Numbers and strings are treated as literals.
|
||||
def compile(lit) when is_number(lit), do: lit
|
||||
def compile(lit) when is_binary(lit), do: lit
|
||||
def compile(lit, _celery_env) when is_number(lit), do: lit
|
||||
def compile(lit, _celery_env) when is_binary(lit), do: lit
|
||||
|
||||
@doc """
|
||||
Recursively compiles a list or single Celery AST into an Elixir `__block__`
|
||||
"""
|
||||
def compile_block(asts, acc \\ [])
|
||||
def compile_block(asts, celery_env, acc \\ [])
|
||||
|
||||
def compile_block(%AST{} = ast, _) do
|
||||
case compile(ast) do
|
||||
def compile_block(%AST{} = ast, celery_env, _) do
|
||||
case compile(ast, celery_env) do
|
||||
{_, _, _} = compiled ->
|
||||
compiled
|
||||
{:__block__, [], [compiled]}
|
||||
|
||||
compiled when is_list(compiled) ->
|
||||
{:__block__, [], compiled}
|
||||
end
|
||||
end
|
||||
|
||||
def compile_block([ast | rest], acc) do
|
||||
case compile(ast) do
|
||||
def compile_block([ast | rest], celery_env, acc) do
|
||||
case compile(ast, celery_env) do
|
||||
{_, _, _} = compiled ->
|
||||
compile_block(rest, acc ++ [compiled])
|
||||
compile_block(rest, celery_env, acc ++ [compiled])
|
||||
|
||||
compiled when is_list(compiled) ->
|
||||
compile_block(rest, acc ++ compiled)
|
||||
compile_block(rest, celery_env, acc ++ compiled)
|
||||
end
|
||||
end
|
||||
|
||||
def compile_block([], acc), do: {:__block__, [], acc}
|
||||
def compile_block([], _, acc), do: {:__block__, [], acc}
|
||||
|
||||
@doc """
|
||||
Compiles a `execute` block to a paramater block
|
||||
|
@ -405,12 +472,25 @@ defmodule Farmbot.CeleryScript.AST.Compiler do
|
|||
|
||||
parent = Keyword.fetch!(params, :parent)
|
||||
"""
|
||||
def compile_param_declaration(%{args: %{label: var_name, data_type: type}}) do
|
||||
def compile_param_declaration(%{args: %{label: var_name, data_type: _type}}) do
|
||||
var_fetch =
|
||||
quote do
|
||||
Keyword.fetch!(params, unquote(String.to_atom(var_name)))
|
||||
end
|
||||
|
||||
{:=, [type: type], [{String.to_atom(var_name), [], __MODULE__}, var_fetch]}
|
||||
{:=, [], [{String.to_atom(var_name), [], __MODULE__}, var_fetch]}
|
||||
end
|
||||
|
||||
@doc "Adds metadata about a celery ast to the resulting elixir ast."
|
||||
def add_meta({a, meta, body}, %AST{kind: kind, comment: comment}, more \\ []) do
|
||||
meta =
|
||||
meta
|
||||
|> Keyword.update(:celery_comments, [comment], fn comments ->
|
||||
(comment || []) ++ comments
|
||||
end)
|
||||
|> Keyword.put(:celery_kind, kind)
|
||||
|> Keyword.merge(more)
|
||||
|
||||
{a, meta, body}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,24 +1,87 @@
|
|||
defmodule Mix.Tasks.Farmbot.CeleryScript.Compile do
|
||||
@moduledoc """
|
||||
Compile a json representation of CeleryScript
|
||||
|
||||
# Usage
|
||||
mix farmbot.celery_script.compile [switches] input1.json input2.json
|
||||
|
||||
# Switches
|
||||
* `--out filename.exs` (default=stdout) - Output compiled Elixir code to `filename.exs`
|
||||
* `--ast filename.exs` (default=false) - Output Elixir AST to `filename.exs`
|
||||
* `--format false` (default=true) - Format the resulting code
|
||||
"""
|
||||
@shortdoc true
|
||||
|
||||
alias Farmbot.CeleryScript.AST
|
||||
|
||||
use Mix.Task
|
||||
|
||||
def run([in_filename, out_filename]) do
|
||||
File.read!(in_filename)
|
||||
|> Jason.decode!()
|
||||
|> Farmbot.CeleryScript.AST.decode()
|
||||
|> Farmbot.CeleryScript.AST.Compiler.compile()
|
||||
# |> fn(data) ->
|
||||
# stuff = inspect(data, limit: :infinity)
|
||||
# stuff = Code.format_string!(stuff)
|
||||
# File.write!("ast.exs", stuff)
|
||||
# data
|
||||
# end.()
|
||||
|> Macro.to_string()
|
||||
|> Code.format_string!()
|
||||
|> (fn data ->
|
||||
File.write!(out_filename, data)
|
||||
end).()
|
||||
@options [
|
||||
strict: [
|
||||
out: :string,
|
||||
ast: :string,
|
||||
format: :boolean,
|
||||
help: :boolean
|
||||
],
|
||||
aliases: [
|
||||
o: :out,
|
||||
a: :ast,
|
||||
f: :format
|
||||
]
|
||||
]
|
||||
|
||||
def run(args) do
|
||||
{switches, files, _} = OptionParser.parse(args, @options)
|
||||
output_filename = Keyword.get(switches, :out, "stdout")
|
||||
ast_filename = Keyword.get(switches, :ast, false)
|
||||
format = Keyword.get(switches, :format, true)
|
||||
|
||||
if Keyword.get(switches, :help, false) do
|
||||
Mix.Task.run("help", ["farmbot.celery_script.compile"])
|
||||
System.halt(0)
|
||||
end
|
||||
|
||||
asts =
|
||||
Enum.map(files, fn filename ->
|
||||
filename
|
||||
|> File.read!()
|
||||
|> Jason.decode!()
|
||||
|> case do
|
||||
%{} = data -> [AST.decode(data)]
|
||||
data when is_list(data) -> Enum.map(data, &AST.decode/1)
|
||||
end
|
||||
|> Enum.map(&AST.compile/1)
|
||||
end)
|
||||
|
||||
case ast_filename do
|
||||
false ->
|
||||
asts
|
||||
|
||||
"stdout" ->
|
||||
IO.inspect(asts, limit: :infinity)
|
||||
|
||||
filename ->
|
||||
File.write!(filename, inspect(asts, limit: :infinity))
|
||||
asts
|
||||
end
|
||||
|
||||
code =
|
||||
asts
|
||||
|> Macro.to_string()
|
||||
|> case do
|
||||
code when format == true ->
|
||||
Code.format_string!(code) |> to_string()
|
||||
|
||||
code ->
|
||||
code
|
||||
end
|
||||
|
||||
case output_filename do
|
||||
"stdout" ->
|
||||
IO.puts(code)
|
||||
|
||||
filename ->
|
||||
File.write!(filename, code)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue