Orb state machine DSL
Mix.install([{:orb, "~> 0.0.18"}, :orb_wasmtime])
Section
defmodule StateMachine do
defmacro __using__(_opts) do
quote do
use Orb
alias Orb.Instruction
Orb.I32.global(state: 0, change_count: 0)
import unquote(__MODULE__)
defwp transition_to(new_state: Orb.I32) do
Instruction.global_set(Orb.I32, :state, Instruction.local_get(Orb.I32, :new_state))
Instruction.global_set(
Orb.I32,
:change_count,
Orb.I32.add(Instruction.global_get(Orb.I32, :change_count), 1)
)
end
defw get_change_count(), Orb.I32 do
Instruction.global_get(Orb.I32, :change_count)
end
end
end
defmacro on(call, target: target) do
alias Orb.I32
require Orb.IfElse.DSL
{name, args} = Macro.decompose_call(call)
[current_state] = args
case current_state do
# If current state is `_` i.e. being ignored.
{:_, _, _} ->
quote do
wasm do
func unquote(Macro.escape(name)) do
transition_to(unquote(target))
# Orb.DSL.typed_call(nil, :transition_to, [unquote(target)])
end
end
end
# If we are checking what the current state is.
current_state ->
quote do
# Module.register_attribute(__MODULE__, String.to_atom("func_#{unquote(name)}"), accumulate: true)
wasm do
func unquote(Macro.escape(name)) do
Orb.IfElse.DSL.if I32.eq(global_get(:state), unquote(current_state)) do
transition_to(unquote(target))
# Orb.DSL.typed_call(nil, :transition_to, [unquote(target)])
end
end
end
end
end
end
defmacro on(call, do: targets) do
alias Orb.I32
require Orb.IfElse.DSL
{name, []} = Macro.decompose_call(call)
statements =
for {:->, _, [matches, target]} <- targets do
effect =
case target do
{target, global_mutations} when is_list(global_mutations) ->
quote do
[
unquote(target),
global_set(:state),
unquote(
for {global_name, mutation} <- global_mutations do
case mutation do
:increment ->
quote do
[
I32.add(global_get(unquote(global_name)), 1),
global_set(unquote(global_name))
]
end
n when is_integer(n) ->
quote do
[
push(unquote(n)),
global_set(unquote(global_name))
]
end
end
end
),
:return
]
end
{target, {:snippet, _, _} = snippet} ->
quote do
[unquote(target), global_set(:state), unquote(snippet), :return]
end
target ->
IO.inspect(target)
quote do
[unquote(target), global_set(:state), :return]
end
end
case matches do
# catchall
[{:_, _, nil}] ->
effect
[match] ->
quote do
Orb.IfElse.DSL.if I32.eq(global_get(:state), unquote(match)) do
unquote(effect)
end
end
matches ->
quote do
Orb.IfElse.DSL.if I32.in?(global_get(:state), unquote(matches)) do
unquote(effect)
end
end
end
end
quote do
wasm do
func unquote(name) do
unquote(statements)
end
end
end
end
end
defmodule FlightBooking do
use Orb
# I32.enum State do
# @initial?
# @destination?
# @dates?
# @flights?
# @seats?
# @checkout?
# @checkout_failed?
# @booked?
# @confirmation?
# end
I32.export_enum([
:initial?,
:destination?,
:dates?,
:flights?,
:seats?,
:checkout?,
:checkout_failed?,
:booked?,
:confirmation?
])
use StateMachine, initial: :initial?
# I32.global(state: @initial?)
Memory.pages(1)
defw(get_current(), I32, do: @state)
defw(get_search_params(), I32, do: 0x0)
on next() do
@initial? -> @destination?
@destination? -> @dates?
@dates? -> @flights?
@flights? -> @seats?
end
defw get_path(), I32.String do
I32.match @state do
@initial? -> ~S[/book]
@destination? -> ~S[/destination]
@dates? -> ~S[/dates]
@flights? -> ~S[/flights]
@seats? -> ~S[/seats]
end
end
end
# OrbWasmtime.Wasm.call(ExampleA, :_true)
book = OrbWasmtime.Instance.run(FlightBooking)
OrbWasmtime.Instance.call(book, :get_current)
OrbWasmtime.Instance.call_reading_string(book, :get_path)
OrbWasmtime.Instance.call(book, :next)
OrbWasmtime.Instance.call(book, :next)
OrbWasmtime.Instance.call(book, :next)
OrbWasmtime.Instance.call(book, :next)
OrbWasmtime.Instance.call_reading_string(book, :get_path)