Current section
Files
Jump to
Current section
Files
src/scamper.erl
-module(scamper).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/scamper.gleam").
-export([new/3, restore/6, transition/2, can_transition/2, current_state/1, current_context/1, is_final/1, available_events/1, history/1, elapsed/1, get_config/1, created_at/1, entered_at/1]).
-export_type([machine/3]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" Scamper — Type-safe finite state machine library for Gleam.\n"
"\n"
" Generic over `Machine(state, context, event)`. Build configs with the\n"
" pipeline operator, then create machines and transition them with events.\n"
"\n"
" ```gleam\n"
" let cfg =\n"
" config.new(timestamp_fn)\n"
" |> config.add_transition(from: Idle, on: Start, to: Running)\n"
" |> config.add_transition(from: Running, on: Complete, to: Done)\n"
" |> config.set_final_states([Done])\n"
"\n"
" let machine = scamper.new(cfg, Idle, initial_context)\n"
" let assert Ok(machine) = scamper.transition(machine, Start)\n"
" ```\n"
).
-opaque machine(HTV, HTW, HTX) :: {machine,
scamper@config:config(HTV, HTW, HTX),
HTV,
HTW,
list(scamper@history:transition_record(HTV, HTX, HTW)),
integer(),
integer()}.
-file("src/scamper.gleam", 37).
?DOC(" Create a new state machine with the given configuration, initial state, and context.\n").
-spec new(scamper@config:config(HTY, HTZ, HUA), HTY, HTZ) -> machine(HTY, HTZ, HUA).
new(Config, Initial_state, Context) ->
Now = scamper@config:get_timestamp(Config),
{machine, Config, Initial_state, Context, [], Now, Now}.
-file("src/scamper.gleam", 55).
?DOC(
" Restore a machine from serialized components.\n"
" Used by the serialization module to reconstruct a machine.\n"
).
-spec restore(
scamper@config:config(HUH, HUI, HUJ),
HUH,
HUI,
list(scamper@history:transition_record(HUH, HUJ, HUI)),
integer(),
integer()
) -> machine(HUH, HUI, HUJ).
restore(Config, State, Context, History_records, Created_at, Entered_at) ->
{machine, Config, State, Context, History_records, Created_at, Entered_at}.
-file("src/scamper.gleam", 76).
?DOC(
" Attempt to transition the machine by processing an event.\n"
" Returns a new machine on success, or a TransitionError on failure.\n"
" The input machine is never modified.\n"
).
-spec transition(machine(HUU, HUV, HUW), HUW) -> {ok, machine(HUU, HUV, HUW)} |
{error, scamper@error:transition_error(HUU, HUW)}.
transition(Machine, Event) ->
_pipe = scamper@transition:execute(
erlang:element(3, Machine),
erlang:element(4, Machine),
Event,
erlang:element(2, Machine),
erlang:element(5, Machine)
),
gleam@result:map(
_pipe,
fun(R) ->
{machine,
erlang:element(2, Machine),
erlang:element(2, R),
erlang:element(3, R),
erlang:element(4, R),
erlang:element(6, Machine),
erlang:element(5, R)}
end
).
-file("src/scamper.gleam", 100).
?DOC(
" Check whether a transition is possible for the given event\n"
" without actually executing it. Does not run callbacks or invariants.\n"
).
-spec can_transition(machine(any(), any(), HVJ), HVJ) -> boolean().
can_transition(Machine, Event) ->
scamper@transition:can_execute(
erlang:element(3, Machine),
Event,
erlang:element(4, Machine),
erlang:element(2, Machine)
).
-file("src/scamper.gleam", 108).
?DOC(" Get the current state of the machine.\n").
-spec current_state(machine(HVN, any(), any())) -> HVN.
current_state(Machine) ->
erlang:element(3, Machine).
-file("src/scamper.gleam", 113).
?DOC(" Get the current context of the machine.\n").
-spec current_context(machine(any(), HVU, any())) -> HVU.
current_context(Machine) ->
erlang:element(4, Machine).
-file("src/scamper.gleam", 118).
?DOC(" Check whether the machine is in a final (terminal) state.\n").
-spec is_final(machine(any(), any(), any())) -> boolean().
is_final(Machine) ->
gleam@list:contains(
scamper@config:get_final_states(erlang:element(2, Machine)),
erlang:element(3, Machine)
).
-file("src/scamper.gleam", 124).
?DOC(
" Get the list of events that have at least one matching transition rule\n"
" from the current state. Does not evaluate guards.\n"
).
-spec available_events(machine(any(), any(), HWH)) -> list(HWH).
available_events(Machine) ->
scamper@transition:available_events(
erlang:element(3, Machine),
erlang:element(2, Machine)
).
-file("src/scamper.gleam", 129).
?DOC(" Get the full transition history (newest first).\n").
-spec history(machine(HWM, HWN, HWO)) -> list(scamper@history:transition_record(HWM, HWO, HWN)).
history(Machine) ->
erlang:element(5, Machine).
-file("src/scamper.gleam", 137).
?DOC(
" Get milliseconds elapsed since the last transition\n"
" (or since creation if no transitions have occurred).\n"
).
-spec elapsed(machine(any(), any(), any())) -> integer().
elapsed(Machine) ->
scamper@config:get_timestamp(erlang:element(2, Machine)) - erlang:element(
7,
Machine
).
-file("src/scamper.gleam", 142).
?DOC(" Get the machine's configuration.\n").
-spec get_config(machine(HXC, HXD, HXE)) -> scamper@config:config(HXC, HXD, HXE).
get_config(Machine) ->
erlang:element(2, Machine).
-file("src/scamper.gleam", 149).
?DOC(" Get the timestamp when the machine was created.\n").
-spec created_at(machine(any(), any(), any())) -> integer().
created_at(Machine) ->
erlang:element(6, Machine).
-file("src/scamper.gleam", 154).
?DOC(" Get the timestamp when the current state was entered.\n").
-spec entered_at(machine(any(), any(), any())) -> integer().
entered_at(Machine) ->
erlang:element(7, Machine).