farmbot_os/farmbot_celery_script/lib/farmbot_celery_script/compilers/if_compiler.ex

154 lines
4.3 KiB
Elixir

defmodule FarmbotCeleryScript.Compiler.If do
alias FarmbotCeleryScript.{AST, Compiler}
import FarmbotCeleryScript.Compiler.Utils
# Compiles an if statement.
def unquote(:_if)(
%{
args: %{
_then: then_ast,
_else: else_ast,
lhs: lhs_ast,
op: op,
rhs: rhs
}
},
env
) do
rhs = Compiler.compile_ast(rhs, env)
# 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.
# any AST is also aloud to be on the lefthand side as
# well, so if that is the case, compile it first.
lhs =
case lhs_ast do
"x" ->
quote [location: :keep],
do: FarmbotCeleryScript.SysCalls.get_cached_x()
"y" ->
quote [location: :keep],
do: FarmbotCeleryScript.SysCalls.get_cached_y()
"z" ->
quote [location: :keep],
do: FarmbotCeleryScript.SysCalls.get_cached_z()
"pin" <> pin ->
quote [location: :keep],
do:
FarmbotCeleryScript.SysCalls.read_cached_pin(
unquote(String.to_integer(pin))
)
# Named pin has two intents here
# in this case we want to read the named pin.
%AST{kind: :named_pin} = ast ->
quote [location: :keep],
do:
FarmbotCeleryScript.SysCalls.read_cached_pin(
unquote(Compiler.compile_ast(ast, env))
)
%AST{} = ast ->
Compiler.compile_ast(ast, env)
end
# Turn the `op` arg into Elixir code
if_eval =
case op do
"is" ->
# equality check.
# Examples:
# get_current_x() == 0
# get_current_y() == 10
# get_current_z() == 200
# read_pin(22, nil) == 5
# The ast will look like: {:==, [], lhs, Compiler.compile_ast(rhs)}
quote location: :keep do
unquote(lhs) == unquote(rhs)
end
"not" ->
# ast will look like: {:!=, [], [lhs, Compiler.compile_ast(rhs)]}
quote location: :keep do
unquote(lhs) != unquote(rhs)
end
"is_undefined" ->
# ast will look like: {:is_nil, [], [lhs]}
quote location: :keep do
is_nil(unquote(lhs))
end
"<" ->
# ast will look like: {:<, [], [lhs, Compiler.compile_ast(rhs)]}
quote location: :keep do
unquote(lhs) < unquote(rhs)
end
">" ->
# ast will look like: {:>, [], [lhs, Compiler.compile_ast(rhs)]}
quote location: :keep do
unquote(lhs) > unquote(rhs)
end
_ ->
quote location: :keep do
unquote(lhs)
end
end
truthy_suffix =
case then_ast do
%{kind: :execute} -> "branching"
%{kind: :nothing} -> "continuing execution"
end
falsey_suffix =
case else_ast do
%{kind: :execute} -> "branching"
%{kind: :nothing} -> "continuing execution"
end
# Finally, compile the entire if statement.
# outputted code will look something like:
# if get_current_x() == 123 do
# execute(123)
# else
# nothing()
# end
quote location: :keep do
prefix_string = FarmbotCeleryScript.SysCalls.format_lhs(unquote(lhs_ast))
# examples:
# "current x position is 100"
# "pin 13 > 1"
# "peripheral 10 is unknon"
result_str =
case unquote(op) do
"is" -> "#{prefix_string} is #{unquote(rhs)}"
"not" -> "#{prefix_string} is not #{unquote(rhs)}"
"is_undefined" -> "#{prefix_string} is unknown"
"<" -> "#{prefix_string} is less than #{unquote(rhs)}"
">" -> "#{prefix_string} is greater than #{unquote(rhs)}"
end
if unquote(if_eval) do
FarmbotCeleryScript.SysCalls.log(
"Evaluated IF statement: #{result_str}; #{unquote(truthy_suffix)}"
)
unquote(compile_block(then_ast, env))
else
FarmbotCeleryScript.SysCalls.log(
"Evaluated IF statement: #{result_str}; #{unquote(falsey_suffix)}"
)
unquote(compile_block(else_ast, env))
end
end
end
end