Advertisement
Guest User

Untitled

a guest
Jul 15th, 2019
81
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 25.52 KB | None | 0 0
  1. defmodule FarmbotCeleryScript.Compiler do
  2. @moduledoc """
  3. Responsible for compiling canonical CeleryScript AST into
  4. Elixir AST.
  5. """
  6. require Logger
  7.  
  8. alias FarmbotCeleryScript.{
  9. AST,
  10. Compiler,
  11. Compiler.IdentifierSanitizer
  12. }
  13.  
  14. use Compiler.Tools
  15. @valid_entry_points [:sequence, :rpc_request]
  16.  
  17. @kinds "install_farmware"
  18. @kinds "update_farmware"
  19. @kinds "remove_farmware"
  20. @kinds "channel"
  21. @kinds "explanation"
  22. @kinds "rpc_ok"
  23. @kinds "rpc_error"
  24. @kinds "pair"
  25. @kinds "scope_declaration"
  26.  
  27. @typedoc """
  28. Compiled CeleryScript node should compile to an anon function.
  29. Entrypoint nodes such as
  30. * `rpc_request`
  31. * `sequence`
  32. will compile to a function that takes a Keyword list of variables. This function
  33. needs to be executed before scheduling/executing.
  34.  
  35. Non entrypoint nodes compile to a function that symbolizes one individual step.
  36.  
  37. ## Examples
  38.  
  39. `rpc_request` will be compiled to something like:
  40. ```
  41. fn params ->
  42. [
  43. # Body of the `rpc_request` compiled in here.
  44. ]
  45. end
  46. ```
  47.  
  48. as compared to a "simple" node like `wait` will compile to something like:
  49. ```
  50. fn() -> wait(200) end
  51. ```
  52. """
  53. @type compiled :: (Keyword.t() -> [(() -> any())]) | (() -> any())
  54.  
  55. @doc """
  56. Recursive function that will emit Elixir AST from CeleryScript AST.
  57. """
  58. @spec compile(AST.t(), Keyword.t()) :: [compiled()]
  59. def compile(%AST{kind: kind} = ast, env \\ []) when kind in @valid_entry_points do
  60. # compile the ast
  61. {_, _, _} = compiled = compile_ast(ast)
  62.  
  63. # print_compiled_code(compiled)
  64. # entry points must be evaluated once more with the calling `env`
  65. # to return a list of compiled `steps`
  66.  
  67. # TODO: investigate why i have to turn this to a string
  68. # before eval ing it?
  69. # case Code.eval_quoted(compiled, [], __ENV__) do
  70. case Macro.to_string(compiled) |> Code.eval_string([], __ENV__) do
  71. {fun, _} when is_function(fun, 1) -> apply(fun, [env])
  72. {{:error, error}, _} -> {:error, error}
  73. end
  74. end
  75.  
  76. # The compile macro right here is generated by the Compiler.Tools module.
  77. # The goal of the macro is to do two things:
  78. # 1) take out all the common code between each node impl.
  79. # Example:
  80. # compile :fire_laser, %{after: 100}, targets, do: quote, do: fire_at(targets)
  81. # will compile down to:
  82. # def compile_ast(%AST{kind: :fire_laser, args: %{after: 100}, body: targets})
  83. #
  84. # 2) Accumulate implemented nodes behind the scenes.
  85. # This allows for the Corpus to throw warnings when a new node
  86. # is added.
  87.  
  88. # Compiles a `sequence` into an Elixir `fn`.
  89. compile :sequence, %{locals: %{body: params}}, block do
  90. # Sort the args.body into two arrays.
  91. # The `params` side gets turned into
  92. # a keyword list. These `params` are passed in from a previous sequence.
  93. # The `body` side declares variables in _this_ scope.
  94. {params_fetch, body} =
  95. Enum.reduce(params, {[], []}, fn ast, {params, body} = _acc ->
  96. case ast do
  97. # declares usage of a parameter as defined by variable_declaration
  98. %{kind: :parameter_declaration} -> {params ++ [compile_param_declaration(ast)], body}
  99. # declares usage of a variable as defined inside the body of itself
  100. %{kind: :parameter_application} -> {params ++ [compile_param_application(ast)], body}
  101. # defines a variable exists
  102. %{kind: :variable_declaration} -> {params, body ++ [ast]}
  103. end
  104. end)
  105.  
  106. {:__block__, [], assignments} = compile_block(body)
  107. steps = compile_block(block) |> decompose_block_to_steps()
  108.  
  109. quote location: :keep do
  110. fn params ->
  111. # This quiets a compiler warning if there are no variables in this block
  112. _ = inspect(params)
  113. # Fetches variables from the previous execute()
  114. # example:
  115. # parent = Keyword.fetch!(params, :parent)
  116. unquote_splicing(params_fetch)
  117. unquote_splicing(assignments)
  118.  
  119. # Unquote the remaining sequence steps.
  120. unquote(steps)
  121. end
  122. end
  123. end
  124.  
  125. compile :rpc_request, %{label: _label}, block do
  126. steps = compile_block(block) |> decompose_block_to_steps()
  127.  
  128. quote location: :keep do
  129. fn params ->
  130. # This quiets a compiler warning if there are no variables in this block
  131. _ = inspect(params)
  132. unquote(steps)
  133. end
  134. end
  135. end
  136.  
  137. # Compiles a variable asignment.
  138. compile :variable_declaration, %{label: var_name, data_value: data_value_ast} do
  139. # Compiles the `data_value`
  140. # and assigns the result to a variable named `label`
  141. # Example:
  142. # {
  143. # "kind": "variable_declaration",
  144. # "args": {
  145. # "label": "parent",
  146. # "data_value": {
  147. # "kind": "point",
  148. # "args": {
  149. # "pointer_type": "Plant",
  150. # "pointer_id": 456
  151. # }
  152. # }
  153. # }
  154. # }
  155. # Will be turned into:
  156. # parent = point("Plant", 456)
  157. # NOTE: This needs to be Elixir AST syntax, not quoted
  158. # because var! doesn't do what what we need.
  159. var_name = IdentifierSanitizer.to_variable(var_name)
  160.  
  161. quote location: :keep do
  162. unquote({var_name, [], nil}) = unquote(compile_ast(data_value_ast))
  163. end
  164. end
  165.  
  166. # Compiles an if statement.
  167. compile :_if, %{_then: then_ast, _else: else_ast, lhs: lhs, op: op, rhs: rhs} do
  168. # Turns the left hand side arg into
  169. # a number. x, y, z, and pin{number} are special that need to be
  170. # evaluated before evaluating the if statement.
  171. # any AST is also aloud to be on the lefthand side as
  172. # well, so if that is the case, compile it first.
  173. lhs =
  174. case lhs do
  175. "x" ->
  176. quote [location: :keep], do: FarmbotCeleryScript.SysCalls.get_current_x()
  177.  
  178. "y" ->
  179. quote [location: :keep], do: FarmbotCeleryScript.SysCalls.get_current_y()
  180.  
  181. "z" ->
  182. quote [location: :keep], do: FarmbotCeleryScript.SysCalls.get_current_z()
  183.  
  184. "pin" <> pin ->
  185. quote [location: :keep],
  186. do: FarmbotCeleryScript.SysCalls.read_pin(unquote(String.to_integer(pin)), nil)
  187.  
  188. # Named pin has two intents here
  189. # in this case we want to read the named pin.
  190. %AST{kind: :named_pin} = ast ->
  191. quote [location: :keep],
  192. do: FarmbotCeleryScript.SysCalls.read_pin(unquote(compile_ast(ast)), nil)
  193.  
  194. %AST{} = ast ->
  195. compile_ast(ast)
  196. end
  197.  
  198. rhs = compile_ast(rhs)
  199.  
  200. # Turn the `op` arg into Elixir code
  201. if_eval =
  202. case op do
  203. "is" ->
  204. # equality check.
  205. # Examples:
  206. # get_current_x() == 0
  207. # get_current_y() == 10
  208. # get_current_z() == 200
  209. # read_pin(22, nil) == 5
  210. # The ast will look like: {:==, [], lhs, compile_ast(rhs)}
  211. quote location: :keep do
  212. unquote(lhs) == unquote(rhs)
  213. end
  214.  
  215. "not" ->
  216. # ast will look like: {:!=, [], [lhs, compile_ast(rhs)]}
  217. quote location: :keep do
  218. unquote(lhs) != unquote(rhs)
  219. end
  220.  
  221. "is_undefined" ->
  222. # ast will look like: {:is_nil, [], [lhs]}
  223. quote location: :keep do
  224. is_nil(unquote(lhs))
  225. end
  226.  
  227. "<" ->
  228. # ast will look like: {:<, [], [lhs, compile_ast(rhs)]}
  229. quote location: :keep do
  230. unquote(lhs) < unquote(rhs)
  231. end
  232.  
  233. ">" ->
  234. # ast will look like: {:>, [], [lhs, compile_ast(rhs)]}
  235. quote location: :keep do
  236. unquote(lhs) > unquote(rhs)
  237. end
  238. end
  239.  
  240. # Finally, compile the entire if statement.
  241. # outputted code will look something like:
  242. # if get_current_x() == 123 do
  243. # execute(123)
  244. # else
  245. # nothing()
  246. # end
  247. quote location: :keep do
  248. FarmbotCeleryScript.SysCalls.log("IF Statement evaultaion")
  249.  
  250. if unquote(if_eval) do
  251. FarmbotCeleryScript.SysCalls.log("IF Statement will branch")
  252. unquote(compile_block(then_ast))
  253. else
  254. FarmbotCeleryScript.SysCalls.log("IF Statement will not branch")
  255. unquote(compile_block(else_ast))
  256. end
  257. end
  258. end
  259.  
  260. # Compiles an `execute` block.
  261. compile :execute, %{sequence_id: id}, parameter_applications do
  262. quote location: :keep do
  263. # We have to lookup the sequence by it's id.
  264. case FarmbotCeleryScript.SysCalls.get_sequence(unquote(id)) do
  265. %FarmbotCeleryScript.AST{} = ast ->
  266. # TODO(Connor) - figure out a way of inserting the sequence name here
  267. FarmbotCeleryScript.SysCalls.log("Executing Sequence")
  268. # compile the ast
  269. env = unquote(compile_params_to_function_args(parameter_applications))
  270. FarmbotCeleryScript.Compiler.compile(ast, env)
  271.  
  272. error ->
  273. error
  274. end
  275. end
  276. end
  277.  
  278. # Compiles `execute_script`
  279. # TODO(Connor) - make this actually usable
  280. compile :execute_script, %{label: package}, params do
  281. env =
  282. Enum.map(params, fn %{args: %{label: key, value: value}} ->
  283. {to_string(key), value}
  284. end)
  285.  
  286. quote location: :keep do
  287. package = unquote(compile_ast(package))
  288. env = unquote(Macro.escape(Map.new(env)))
  289. FarmbotCeleryScript.SysCalls.log("Executing Farmware: #{package}")
  290. FarmbotCeleryScript.SysCalls.execute_script(package, env)
  291. end
  292. end
  293.  
  294. # TODO(Connor) - see above TODO
  295. compile :take_photo do
  296. # {:execute_script, [], ["take_photo", {:%{}, [], []}]}
  297. quote location: :keep do
  298. FarmbotCeleryScript.SysCalls.execute_script("take_photo", %{})
  299. end
  300. end
  301.  
  302. compile :set_user_env, _args, pairs do
  303. kvs =
  304. Enum.map(pairs, fn %{kind: :pair, args: %{label: key, value: value}} ->
  305. quote location: :keep do
  306. FarmbotCeleryScript.SysCalls.set_user_env(unquote(key), unquote(value))
  307. end
  308. end)
  309.  
  310. quote location: :keep do
  311. (unquote_splicing(kvs))
  312. end
  313. end
  314.  
  315. compile :install_first_party_farmware, _ do
  316. quote location: :keep do
  317. FarmbotCeleryScript.SysCalls.log("Installing first party Farmware")
  318. FarmbotCeleryScript.SysCalls.install_first_party_farmware()
  319. end
  320. end
  321.  
  322. # Compiles a nothing block.
  323. compile :nothing do
  324. # AST looks like: {:nothing, [], []}
  325. quote location: :keep do
  326. FarmbotCeleryScript.SysCalls.nothing()
  327. end
  328. end
  329.  
  330. # Compiles move_absolute
  331. compile :move_absolute, %{location: location, offset: offset, speed: speed} do
  332. quote location: :keep do
  333. # Extract the location arg
  334. with %{x: locx, y: locy, z: locz} = unquote(compile_ast(location)),
  335. # Extract the offset arg
  336. %{x: offx, y: offy, z: offz} = unquote(compile_ast(offset)) do
  337. # Subtract the location from offset.
  338. # Note: list syntax here for readability.
  339. [x, y, z] = [
  340. locx + offx,
  341. locy + offy,
  342. locz + offz
  343. ]
  344.  
  345. FarmbotCeleryScript.SysCalls.log("Moving to (#{x}, #{y}, #{z})")
  346. FarmbotCeleryScript.SysCalls.move_absolute(x, y, z, unquote(compile_ast(speed)))
  347. end
  348. end
  349. end
  350.  
  351. # compiles move_relative into move absolute
  352. compile :move_relative, %{x: x, y: y, z: z, speed: speed} do
  353. quote location: :keep do
  354. with locx when is_number(locx) <- unquote(compile_ast(x)),
  355. locy when is_number(locy) <- unquote(compile_ast(y)),
  356. locz when is_number(locz) <- unquote(compile_ast(z)),
  357. curx when is_number(curx) <- FarmbotCeleryScript.SysCalls.get_current_x(),
  358. cury when is_number(cury) <- FarmbotCeleryScript.SysCalls.get_current_y(),
  359. curz when is_number(curz) <- FarmbotCeleryScript.SysCalls.get_current_z() do
  360. # Combine them
  361. x = locx + curx
  362. y = locy + cury
  363. z = locz + curz
  364. FarmbotCeleryScript.SysCalls.log("Moving relative to (#{x}, #{y}, #{z})")
  365. FarmbotCeleryScript.SysCalls.move_absolute(x, y, z, unquote(compile_ast(speed)))
  366. end
  367. end
  368. end
  369.  
  370. # compiles write_pin
  371. compile :write_pin, %{pin_number: num, pin_mode: mode, pin_value: value} do
  372. quote location: :keep do
  373. pin = unquote(compile_ast(num))
  374. mode = unquote(compile_ast(mode))
  375. value = unquote(compile_ast(value))
  376. FarmbotCeleryScript.SysCalls.log("Writing #{to_string(pin)} in mode: #{mode}: #{value}")
  377.  
  378. with :ok <- FarmbotCeleryScript.SysCalls.write_pin(pin, mode, value) do
  379. FarmbotCeleryScript.SysCalls.read_pin(pin, mode)
  380. end
  381. end
  382. end
  383.  
  384. # compiles read_pin
  385. compile :read_pin, %{pin_number: num, pin_mode: mode} do
  386. quote location: :keep do
  387. pin = unquote(compile_ast(num))
  388. mode = unquote(compile_ast(mode))
  389. FarmbotCeleryScript.SysCalls.log("Reading #{to_string(pin)} in mode: #{mode}")
  390. FarmbotCeleryScript.SysCalls.read_pin(pin, mode)
  391. end
  392. end
  393.  
  394. # compiles set_servo_angle
  395. compile :set_servo_angle, %{pin_number: pin_number, pin_value: pin_value} do
  396. quote location: :keep do
  397. pin = unquote(compile_ast(pin_number))
  398. angle = unquote(compile_ast(pin_value))
  399. FarmbotCeleryScript.SysCalls.log("Writing servo: #{pin}: #{angle}")
  400. FarmbotCeleryScript.SysCalls.set_servo_angle(pin, angle)
  401. end
  402. end
  403.  
  404. # Expands find_home(all) into three find_home/1 calls
  405. compile :find_home, %{axis: "all"} do
  406. quote location: :keep do
  407. FarmbotCeleryScript.SysCalls.log("Finding home on all axes")
  408.  
  409. with :ok <- FarmbotCeleryScript.SysCalls.find_home("z"),
  410. :ok <- FarmbotCeleryScript.SysCalls.find_home("y") do
  411. FarmbotCeleryScript.SysCalls.find_home("x")
  412. end
  413. end
  414. end
  415.  
  416. # compiles find_home
  417. compile :find_home, %{axis: axis} do
  418. quote location: :keep do
  419. with axis when axis in ["x", "y", "z"] <- unquote(compile_ast(axis)) do
  420. FarmbotCeleryScript.SysCalls.log("Finding home on the #{String.upcase(axis)} axis")
  421. FarmbotCeleryScript.SysCalls.find_home(axis)
  422. else
  423. {:error, reason} ->
  424. {:error, reason}
  425. end
  426. end
  427. end
  428.  
  429. # Expands home(all) into three home/1 calls
  430. compile :home, %{axis: "all", speed: speed} do
  431. quote location: :keep do
  432. FarmbotCeleryScript.SysCalls.log("Going to home all axes")
  433.  
  434. with speed when is_number(speed) <- unquote(compile_ast(speed)),
  435. :ok <- FarmbotCeleryScript.SysCalls.home("z", speed),
  436. :ok <- FarmbotCeleryScript.SysCalls.home("y", speed) do
  437. FarmbotCeleryScript.SysCalls.home("x", speed)
  438. end
  439. end
  440. end
  441.  
  442. # compiles home
  443. compile :home, %{axis: axis, speed: speed} do
  444. quote location: :keep do
  445. with axis when axis in ["x", "y", "z"] <- unquote(compile_ast(axis)),
  446. speed when is_number(speed) <- unquote(compile_ast(speed)) do
  447. FarmbotCeleryScript.SysCalls.log("Going to home on the #{String.upcase(axis)} axis")
  448. FarmbotCeleryScript.SysCalls.home(axis, speed)
  449. else
  450. {:error, reason} ->
  451. {:error, reason}
  452. end
  453. end
  454. end
  455.  
  456. # Expands zero(all) into three zero/1 calls
  457. compile :zero, %{axis: "all"} do
  458. quote location: :keep do
  459. FarmbotCeleryScript.SysCalls.log("Zeroing all axes")
  460.  
  461. with :ok <- FarmbotCeleryScript.SysCalls.zero("z"),
  462. :ok <- FarmbotCeleryScript.SysCalls.zero("y") do
  463. FarmbotCeleryScript.SysCalls.zero("x")
  464. end
  465. end
  466. end
  467.  
  468. # compiles zero
  469. compile :zero, %{axis: axis} do
  470. quote location: :keep do
  471. with axis when axis in ["x", "y", "z"] <- unquote(compile_ast(axis)) do
  472. FarmbotCeleryScript.SysCalls.log("Zeroing the #{String.upcase(axis)} axis")
  473. FarmbotCeleryScript.SysCalls.zero(axis)
  474. else
  475. {:error, reason} ->
  476. {:error, reason}
  477. end
  478. end
  479. end
  480.  
  481. # Expands calibrate(all) into three calibrate/1 calls
  482. compile :calibrate, %{axis: "all"} do
  483. quote location: :keep do
  484. FarmbotCeleryScript.SysCalls.log("Calibrating all axes")
  485.  
  486. with :ok <- FarmbotCeleryScript.SysCalls.calibrate("z"),
  487. :ok <- FarmbotCeleryScript.SysCalls.calibrate("y") do
  488. FarmbotCeleryScript.SysCalls.calibrate("x")
  489. else
  490. {:error, reason} ->
  491. {:error, reason}
  492. end
  493. end
  494. end
  495.  
  496. # compiles calibrate
  497. compile :calibrate, %{axis: axis} do
  498. quote location: :keep do
  499. with axis when axis in ["x", "y", "z"] <- unquote(compile_ast(axis)) do
  500. FarmbotCeleryScript.SysCalls.log("Calibrating the #{String.upcase(axis)} axis")
  501. FarmbotCeleryScript.SysCalls.calibrate(axis)
  502. else
  503. {:error, reason} ->
  504. {:error, reason}
  505. end
  506. end
  507. end
  508.  
  509. compile :wait, %{milliseconds: millis} do
  510. quote location: :keep do
  511. with millis when is_integer(millis) <- unquote(compile_ast(millis)) do
  512. FarmbotCeleryScript.SysCalls.log("Waiting for #{millis} milliseconds")
  513. FarmbotCeleryScript.SysCalls.wait(millis)
  514. else
  515. {:error, reason} ->
  516. {:error, reason}
  517. end
  518. end
  519. end
  520.  
  521. compile :send_message, %{message: msg, message_type: type}, channels do
  522. # body gets turned into a list of atoms.
  523. # Example:
  524. # [{kind: "channel", args: {channel_name: "email"}}]
  525. # is turned into:
  526. # [:email]
  527. channels =
  528. Enum.map(channels, fn %{kind: :channel, args: %{channel_name: channel_name}} ->
  529. String.to_atom(channel_name)
  530. end)
  531.  
  532. quote location: :keep do
  533. FarmbotCeleryScript.SysCalls.send_message(
  534. unquote(compile_ast(type)),
  535. unquote(compile_ast(msg)),
  536. unquote(channels)
  537. )
  538. end
  539. end
  540.  
  541. # compiles coordinate
  542. # Coordinate should return a vec3
  543. compile :coordinate, %{x: x, y: y, z: z} do
  544. quote location: :keep do
  545. FarmbotCeleryScript.SysCalls.coordinate(
  546. unquote(compile_ast(x)),
  547. unquote(compile_ast(y)),
  548. unquote(compile_ast(z))
  549. )
  550. end
  551. end
  552.  
  553. # compiles point
  554. compile :point, %{pointer_type: type, pointer_id: id} do
  555. quote location: :keep do
  556. FarmbotCeleryScript.SysCalls.point(unquote(compile_ast(type)), unquote(compile_ast(id)))
  557. end
  558. end
  559.  
  560. # compile a named pin
  561. compile :named_pin, %{pin_id: id, pin_type: type} do
  562. quote location: :keep do
  563. FarmbotCeleryScript.SysCalls.named_pin(unquote(compile_ast(type)), unquote(compile_ast(id)))
  564. end
  565. end
  566.  
  567. # compiles identifier into a variable.
  568. # We have to use Elixir ast syntax here because
  569. # var! doesn't work quite the way we want.
  570. compile :identifier, %{label: var_name} do
  571. var_name = IdentifierSanitizer.to_variable(var_name)
  572.  
  573. quote location: :keep do
  574. unquote({var_name, [], nil})
  575. end
  576. end
  577.  
  578. compile :tool, %{tool_id: tool_id} do
  579. quote location: :keep do
  580. FarmbotCeleryScript.SysCalls.get_toolslot_for_tool(unquote(compile_ast(tool_id)))
  581. end
  582. end
  583.  
  584. compile :emergency_lock do
  585. quote location: :keep do
  586. FarmbotCeleryScript.SysCalls.emergency_lock()
  587. end
  588. end
  589.  
  590. compile :emergency_unlock do
  591. quote location: :keep do
  592. FarmbotCeleryScript.SysCalls.emergency_unlock()
  593. end
  594. end
  595.  
  596. compile :read_status do
  597. quote location: :keep do
  598. FarmbotCeleryScript.SysCalls.read_status()
  599. end
  600. end
  601.  
  602. compile :sync do
  603. quote location: :keep do
  604. FarmbotCeleryScript.SysCalls.sync()
  605. end
  606. end
  607.  
  608. compile :check_updates, %{package: "farmbot_os"} do
  609. quote location: :keep do
  610. FarmbotCeleryScript.SysCalls.check_update()
  611. end
  612. end
  613.  
  614. compile :flash_firmware, %{package: package_name} do
  615. quote location: :keep do
  616. FarmbotCeleryScript.SysCalls.flash_firmware(unquote(compile_ast(package_name)))
  617. end
  618. end
  619.  
  620. compile :power_off do
  621. quote location: :keep do
  622. FarmbotCeleryScript.SysCalls.power_off()
  623. end
  624. end
  625.  
  626. compile :reboot, %{package: "farmbot_os"} do
  627. quote location: :keep do
  628. FarmbotCeleryScript.SysCalls.reboot()
  629. end
  630. end
  631.  
  632. compile :reboot, %{package: "arduino_firmware"} do
  633. quote location: :keep do
  634. FarmbotCeleryScript.SysCalls.firmware_reboot()
  635. end
  636. end
  637.  
  638. compile :factory_reset, %{package: "farmbot_os"} do
  639. quote location: :keep do
  640. FarmbotCeleryScript.SysCalls.factory_reset()
  641. end
  642. end
  643.  
  644. compile :change_ownership, %{}, body do
  645. pairs =
  646. Map.new(body, fn %{args: %{label: label, value: value}} ->
  647. {label, value}
  648. end)
  649.  
  650. email = Map.fetch!(pairs, "email")
  651.  
  652. secret =
  653. Map.fetch!(pairs, "secret")
  654. |> Base.decode64!(padding: false, ignore: :whitespace)
  655.  
  656. server = Map.get(pairs, "server")
  657.  
  658. quote location: :keep do
  659. FarmbotCeleryScript.SysCalls.change_ownership(
  660. unquote(email),
  661. unquote(secret),
  662. unquote(server)
  663. )
  664. end
  665. end
  666.  
  667. compile :dump_info do
  668. quote location: :keep do
  669. FarmbotCeleryScript.SysCalls.dump_info()
  670. end
  671. end
  672.  
  673. compile :toggle_pin, %{pin_number: pin_number} do
  674. quote location: :keep do
  675. # mode 0 = digital
  676. case FarmbotCeleryScript.SysCalls.read_pin(unquote(compile_ast(pin_number)), 0) do
  677. 0 -> FarmbotCeleryScript.SysCalls.write_pin(unquote(compile_ast(pin_number)), 0, 1)
  678. _ -> FarmbotCeleryScript.SysCalls.write_pin(unquote(compile_ast(pin_number)), 0, 0)
  679. end
  680.  
  681. FarmbotCeleryScript.SysCalls.read_pin(unquote(compile_ast(pin_number)), 0)
  682. end
  683. end
  684.  
  685. compile :resource_update,
  686. %{resource_type: kind, resource_id: id, label: label, value: value},
  687. body do
  688. initial = %{label => value}
  689. # Technically now body isn't supported by this node.
  690. extra =
  691. Map.new(body, fn %{args: %{label: label, data_value: value}} ->
  692. {label, value}
  693. end)
  694.  
  695. # Make sure the initial stuff higher most priority
  696. params = Map.merge(extra, initial)
  697.  
  698. quote do
  699. FarmbotCeleryScript.SysCalls.resource_update(
  700. unquote(compile_ast(kind)),
  701. unquote(compile_ast(id)),
  702. unquote(compile_ast(params))
  703. )
  704. end
  705. end
  706.  
  707. @doc """
  708. Recursively compiles a list or single Celery AST into an Elixir `__block__`
  709. """
  710. def compile_block(asts, acc \\ [])
  711.  
  712. def compile_block(%AST{} = ast, _) do
  713. case compile_ast(ast) do
  714. {_, _, _} = compiled ->
  715. {:__block__, [], [compiled]}
  716.  
  717. compiled when is_list(compiled) ->
  718. {:__block__, [], compiled}
  719. end
  720. end
  721.  
  722. def compile_block([ast | rest], acc) do
  723. case compile_ast(ast) do
  724. {_, _, _} = compiled ->
  725. compile_block(rest, acc ++ [compiled])
  726.  
  727. compiled when is_list(compiled) ->
  728. compile_block(rest, acc ++ compiled)
  729. end
  730. end
  731.  
  732. def compile_block([], acc), do: {:__block__, [], acc}
  733.  
  734. @doc """
  735. Compiles a `execute` block to a parameter block
  736.  
  737. # Example
  738. The body of this `execute` node
  739.  
  740. {
  741. "kind": "execute",
  742. "args": {
  743. "sequence_id": 123
  744. },
  745. "body": [
  746. {
  747. "kind": "variable_declaration",
  748. "args": {
  749. "label": "variable_in_this_scope",
  750. "data_value": {
  751. "kind": "identifier",
  752. "args": {
  753. "label": "variable_in_next_scope"
  754. }
  755. }
  756. }
  757. }
  758. ]
  759. }
  760.  
  761. Would be compiled to:
  762.  
  763. [variable_in_next_scope: variable_in_this_scope]
  764. """
  765. def compile_params_to_function_args(list, acc \\ [])
  766.  
  767. def compile_params_to_function_args(
  768. [%{kind: :parameter_application, args: args} | rest],
  769. acc
  770. ) do
  771. %{
  772. label: next_scope_var_name,
  773. data_value: data_value
  774. } = args
  775.  
  776. next_scope_var_name = IdentifierSanitizer.to_variable(next_scope_var_name)
  777. # next_value = compile_ast(data_value)
  778.  
  779. var =
  780. quote location: :keep do
  781. {unquote(next_scope_var_name), unquote(compile_ast(data_value))}
  782. end
  783.  
  784. compile_params_to_function_args(rest, [var | acc])
  785. end
  786.  
  787. def compile_params_to_function_args([], acc), do: acc
  788.  
  789. @doc """
  790. Compiles a function block's params.
  791.  
  792. # Example
  793. A `sequence`s `locals` that look like
  794. {
  795. "kind": "scope_declaration",
  796. "args": {},
  797. "body": [
  798. {
  799. "kind": "parameter_declaration",
  800. "args": {
  801. "label": "parent",
  802. "default_value": {
  803. "kind": "coordinate",
  804. "args": {
  805. "x": 100.0,
  806. "y": 200.0,
  807. "z": 300.0
  808. }
  809. }
  810. }
  811. ]
  812. }
  813.  
  814. Would be compiled to
  815.  
  816. parent = Keyword.get(params, :parent, %{x: 100, y: 200, z: 300})
  817. """
  818. # Add parameter_declaration to the list of implemented kinds
  819. @kinds "parameter_declaration"
  820. def compile_param_declaration(%{args: %{label: var_name, default_value: default}}) do
  821. var_name = IdentifierSanitizer.to_variable(var_name)
  822.  
  823. quote location: :keep do
  824. unquote({var_name, [], __MODULE__}) =
  825. Keyword.get(params, unquote(var_name), unquote(compile_ast(default)))
  826. end
  827. end
  828.  
  829. @doc """
  830. Compiles a function block's assigned value.
  831.  
  832. # Example
  833. A `sequence`s `locals` that look like
  834. {
  835. "kind": "scope_declaration",
  836. "args": {},
  837. "body": [
  838. {
  839. "kind": "parameter_application",
  840. "args": {
  841. "label": "parent",
  842. "data_value": {
  843. "kind": "coordinate",
  844. "args": {
  845. "x": 100.0,
  846. "y": 200.0,
  847. "z": 300.0
  848. }
  849. }
  850. }
  851. }
  852. ]
  853. }
  854. """
  855. # Add parameter_application to the list of implemented kinds
  856. @kinds "parameter_application"
  857. def compile_param_application(%{args: %{label: var_name, data_value: value}}) do
  858. var_name = IdentifierSanitizer.to_variable(var_name)
  859.  
  860. quote location: :keep do
  861. unquote({var_name, [], __MODULE__}) = unquote(compile_ast(value))
  862. end
  863. end
  864.  
  865. defp decompose_block_to_steps({:__block__, _, steps} = _orig) do
  866. Enum.map(steps, fn step ->
  867. quote location: :keep do
  868. fn -> unquote(step) end
  869. end
  870. end)
  871. end
  872.  
  873. # defp print_compiled_code(compiled) do
  874. # compiled
  875. # |> Macro.to_string()
  876. # |> Code.format_string!()
  877. # |> Logger.debug()
  878. # end
  879. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement