Cleanup+document new ElixirAST compiler

pull/974/head
connor rigby 2019-01-29 15:01:08 -08:00 committed by Connor Rigby
parent cd90a1f18a
commit fbb9d294a8
No known key found for this signature in database
GPG Key ID: 29A88B24B70456E0
4 changed files with 434 additions and 283 deletions

View File

@ -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": {}
}
]
]
}

View File

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

View File

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

View File

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