## State Machine ### Goal - Author methods, maybe override, using state machine ### Syntax - `$state_machine` declaration (add methods to a class or an anonymous interface) - `$state_input` definition - Declare a function, which will be implemented in the state machine - Add `override` if necessary - If an input is not used, an error occurs - `$state` definition - Optional parameters - `$goto_state` statement - Jump to the state, providing arguments - `$push_state` statement - Await for a new state machine - `$switch` statement - Await for inputs - Configuration for default: - `$switch`: `default { raise "not supported input ... "; }` - `$switch(ignore[_and_return])`: `default {[return;]}` - `$switch(pass[_and_return])`: `default {$pass_input; [return;]}` - `$pass_input` statement - The next `$switch` will use the current input ### Sample ``` class Calculator { var valueFirst : string = ""; var op : string = ""; prop Value : string = "0"; func Update(value : string) : void { SetValue(value); valueFirst = value; } func Calculate() : void { if (valueFirst == "") { valueFirst = value; } else if (op == "+") { Update((cast double valueFirst) + (cast double Value)); } else if (op == "*") { Update((cast double valueFirst) * (cast double Value)); } else { raise $"Unrecognized operator: $(op)"; } } $state_machine { $state_input Digit(i : int); $state_input Dot(); $state_input Add(); $state_input Mul(); $state_input Equal(); $state_input Clear(); $state Digits() { $switch(pass) { case Digit(i) { Value = Value & i; $goto_state Digits(); } } } $state Integer(newNumber: bool) { $switch(pass) { case Digit(i) { if (newNumber) { Value = i; } else { Value = Value & i; } $goto_state Digits(); } } } $state Number() { $push_state Integer(true); $switch(pass_and_return) { case Dot() { Value = Value & "."; } } $push_state Integer(false); } $state Calculate() { $push_state Number(); $switch { case Add(): {Calculate(); op = "+";} case Mul(): {Calculate(); op = "-";} case Equal(): {Calculate(); op = "=";} case Clear(): { valueFirst = ""; op = ""; Value = "0"; } } $goto_state Calculate(); } $state { $goto_state Calculate(); } } } func Main(): string { var calculator = new Calculator^(); calculator.Digit(1); calculator.Digit(2); calculator.Add(); calculator.Digit(3); calculator.Digit(4); calculator.Equal(); return calculator.Value; // "46" } ``` ### Generated ``` class Calculator {    ... // ignored everything before $state_machine enum Input { Invalid = 0, Digit = 1, Dot = 2, Add = 3, Mul = 4, Equal = 5, Clear = 6, } enum State { Invalid = 0, Start = 1, Digits = 2, Integer = 3, Number = 4, Calculate = 5, } [cpp:Private] var input: Input = ::Calculator::Input::Invalid; [cpp:Private] var coroutine: ::system::Coroutine^ = null; [cpp:Private] var i: int = 0; [cpp:Private] func Resume(): void { var previousResult: ::system::CoroutineResult^ = null; while (true) { if (coroutine: is null) { if (previousResult is not null) { if (previousResult.Failure is not null) { raise Result.Failure; } } break; }            if (coroutine.Status != ::system::CoroutineStatus::Stopped) { var currentCoroutine = coroutine; currentCoroutine.Status.Resume(true, previousResult); if (coroutine == currentCoroutine) { break; // wait for input } else if (currentCoroutine.Status == ::system::CoroutineStatus::Stopped) { // leave a state machine previousResult = new ::system::CoroutineResult^(); if (currentCoroutine.Failure is not null) { previousResult.Failure = currentCoroutine.Failure; } } else { // enter a state machine break; } }        } } func Digit(i: int): void { input = ::Calculator::Input::Digit; i = i; Resume(); } func Dot(): void { input = ::Calculator::Input::Dot; Resume(); } func Add(): void { input = ::Calculator::Input::Add; Resume(); } func Mul(): void { input = ::Calculator::Input::Mul; Resume(); } func Equal(): void { input = ::Calculator::Input::Equal; Resume(); } func Clear(): void { input = ::Calculator::Input::Clear; Resume(); } [cpp:Private] func CreateCoroutine(startState: State): void { previousCoroutine = coroutine; coroutine = $coroutine { try { var state : State = startState; while (true) { var currentState = state; state = ::Calculator::State::Invalid; switch (currentState) { case ::Calculator::State::Start : { state = ::Calculator::State::Calculate; goto OUT_OF_STATES; // goto can only jump to the end of a containing block goto OUT_OF_STATE_MACHINE; } case ::Calculator::State::Digits : { $pause; switch (input) { case ::Calculator::Input::Digit { input = ::Calculator::Input::Invalid; var i = i; Value = Value & i; state = ::Calculator::State::Digits; goto OUT_OF_STATES; } } goto OUT_OF_STATE_MACHINE; } case ::Calculator::State::Integer : { $pause; switch (input) { case ::Calculator::Input::Digit { input = ::Calculator::Input::Invalid; var i = i; if (newNumber) // TODO: Declare the parameter { Value = i; } else { Value = Value & i; } state = ::Calculator::State::Digits; goto OUT_OF_STATES; } } goto OUT_OF_STATE_MACHINE; } case ::Calculator::State::Number : { // TODO: Pass the parameter (true) CreateCoroutine(::Calculator::State::Integer); $pause; $pause; switch (input) { case ::Calculator::::Input::Dot: { input = ::Calculator::Input::Invalid; Value = Value & "."; } default: { goto OUT_OF_STATE_MACHINE; // pass_and_return } } // TODO: Pass the parameter (false) CreateCoroutine(::Calculator::State::Integer); $pause; } case ::Calculator::State::Calculate : { CreateCoroutine(::Calculator::State::Number); $pause; $pause; switch (input) { case ::Calculator::::Input::Add: { input = ::Calculator::Input::Invalid; Calculate(); op = "+"; } case ::Calculator::::Input::Mul: { input = ::Calculator::Input::Invalid; Calculate(); op = "*"; } case ::Calculator::::Input::Equal: { input = ::Calculator::Input::Invalid; Calculate(); op = "="; } case ::Calculator::::Input::Clear: { input = ::Calculator::Input::Invalid; valueFirst = ""; op = ""; Value = "0"; } case ::Calculator::::Input::Digit: { input = ::Calculator::Input::Invalid; raise "Calculator::Digit cannot be called at this moment."; } case ::Calculator::::Input::Dot: { input = ::Calculator::Input::Invalid; raise "Calculator::Dot cannot be called at this moment."; } } state = ::Calculator::State::Calculate; goto OUT_OF_STATES; goto OUT_OF_STATE_MACHINE; } } OUT_OF_CURRENT_STATE:; // label can only appear at the end of a block statement } OUT_OF_STATE_MACHINE:; } finally { coroutine = previousCoroutine; } }; }    new() { CreateCoroutine(:Calculator::State::Start); Resume(); } } ```