Sponsor us and add your link here. Help this open-source site — with open traffic stats — stay alive.
Notesclub

Orb state machine DSL

guides/state-machines.livemd

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)