feat: Add listContains and firstWhereOrNull
This commit is contained in:
parent
96edecdf2a
commit
c1087fe12e
@ -1,99 +0,0 @@
|
|||||||
class NoTransitionPossibleException implements Exception {
|
|
||||||
@override
|
|
||||||
String errMsg() => "The transition graph allows no transition";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A deterministic finite automaton. [T] is the state type while
|
|
||||||
/// [I] is the input type.
|
|
||||||
/// Edges of the node must be added with [addTransition]. If a trap state
|
|
||||||
/// is required, it can be set in the constructor.
|
|
||||||
class DeterministicFiniteAutomaton<T, I> {
|
|
||||||
/// The current state of the DFA
|
|
||||||
T _state;
|
|
||||||
/// The edges of the DFA: State x Input -> State
|
|
||||||
Map<T, Map<I, T>> _transitions;
|
|
||||||
/// Trap state
|
|
||||||
T? trapState;
|
|
||||||
|
|
||||||
/// The argument is the initial state
|
|
||||||
DeterministicFiniteAutomaton(this._state, { this.trapState }) : _transitions = {};
|
|
||||||
|
|
||||||
T get state => _state;
|
|
||||||
|
|
||||||
void addTransition(T oldState, I input, T newState) {
|
|
||||||
assert(oldState != trapState);
|
|
||||||
// These are handled implicitly if no transition has been found
|
|
||||||
assert(newState != trapState);
|
|
||||||
|
|
||||||
if (!_transitions.containsKey(oldState)) {
|
|
||||||
_transitions[oldState] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
_transitions[oldState]![input] = newState;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Transition the DFA based on its current state and the input [input].
|
|
||||||
void onInput(I input) {
|
|
||||||
final newState = _transitions[_state]?[input];
|
|
||||||
if (newState == null) {
|
|
||||||
// Go to the trap state if we can
|
|
||||||
if (trapState != null) {
|
|
||||||
_state = trapState!;
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
throw NoTransitionPossibleException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_state = newState;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns where [input] would take the automaton to. Returns null if no transition
|
|
||||||
/// is possible, ignoring trap transitions.
|
|
||||||
T? peekTransition(I input) {
|
|
||||||
if (!_transitions.containsKey(_state) || !_transitions[_state]!.containsKey(input)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _transitions[_state]![input]!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef MealyAutomatonCallback<T, I> = void Function(T oldState, I input);
|
|
||||||
class MealyAutomaton<T, I> {
|
|
||||||
/// The base automaton
|
|
||||||
final DeterministicFiniteAutomaton<T, I> _automaton;
|
|
||||||
/// Mapping of State x Input -> Output callback
|
|
||||||
Map<T, Map<I, MealyAutomatonCallback<T, I>>> _outputs;
|
|
||||||
/// Trap state
|
|
||||||
MealyAutomatonCallback<T, I>? trapCallback;
|
|
||||||
|
|
||||||
// TODO: Assert that trapState != null implies trapCallback != null.
|
|
||||||
MealyAutomaton(T initialState, { T? trapState, this.trapCallback })
|
|
||||||
: _outputs = {},
|
|
||||||
_automaton = DeterministicFiniteAutomaton(initialState, trapState: trapState);
|
|
||||||
|
|
||||||
T get state => _automaton.state;
|
|
||||||
|
|
||||||
void addTransition(T oldState, I input, T newState, MealyAutomatonCallback<T, I> callback) {
|
|
||||||
_automaton.addTransition(oldState, input, newState);
|
|
||||||
|
|
||||||
if (!_outputs.containsKey(oldState)) {
|
|
||||||
_outputs[oldState] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
_outputs[oldState]![input] = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onInput(I input) {
|
|
||||||
final _state = _automaton.state;
|
|
||||||
if (_automaton.peekTransition(input) == null && trapCallback == null) {
|
|
||||||
throw new NoTransitionPossibleException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final callback = _outputs[_state]?[input] ?? trapCallback!;
|
|
||||||
|
|
||||||
_automaton.onInput(input);
|
|
||||||
callback(_state, input);
|
|
||||||
}
|
|
||||||
}
|
|
@ -72,6 +72,7 @@ abstract class AwaitableDataSender<
|
|||||||
/// Future will be returned that can be used to await a response. If it
|
/// Future will be returned that can be used to await a response. If it
|
||||||
/// is false, then null will be imediately resolved.
|
/// is false, then null will be imediately resolved.
|
||||||
Future<R?> sendData(S data, { bool awaitable = true, @visibleForTesting String? id }) async {
|
Future<R?> sendData(S data, { bool awaitable = true, @visibleForTesting String? id }) async {
|
||||||
|
// ignore: no_leading_underscores_for_local_identifiers
|
||||||
final _id = id ?? _uuid.v4();
|
final _id = id ?? _uuid.v4();
|
||||||
Future<R?> future = Future.value(null);
|
Future<R?> future = Future.value(null);
|
||||||
_log.fine("sendData: Waiting to acquire lock...");
|
_log.fine("sendData: Waiting to acquire lock...");
|
||||||
|
16
lib/lists.dart
Normal file
16
lib/lists.dart
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/// A wrapper around List<T>.firstWhere that does not throw but instead just
|
||||||
|
/// returns true if [test] returns true for an element or false if [test] never
|
||||||
|
/// returned true.
|
||||||
|
bool listContains<T>(List<T> list, bool Function(T element) test) {
|
||||||
|
return firstWhereOrNull<T>(list, test) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A wrapper around [List<T>.firstWhere] that does not throw but instead just
|
||||||
|
/// return null if [test] never returned true
|
||||||
|
T? firstWhereOrNull<T>(List<T> list, bool Function(T element) test) {
|
||||||
|
try {
|
||||||
|
return list.firstWhere(test);
|
||||||
|
} catch(e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
library moxlib;
|
library moxlib;
|
||||||
|
|
||||||
export "awaitabledatasender.dart";
|
export "awaitabledatasender.dart";
|
||||||
export "automaton.dart";
|
export "lists.dart";
|
||||||
export "math.dart";
|
export "math.dart";
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
import "package:moxlib/automaton.dart";
|
|
||||||
|
|
||||||
import "package:test/test.dart";
|
|
||||||
|
|
||||||
enum States {
|
|
||||||
a, b, c, trap
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test("Test a simple DFA", () {
|
|
||||||
final automaton = DeterministicFiniteAutomaton<States, int>(States.a);
|
|
||||||
automaton.addTransition(States.a, 1, States.b);
|
|
||||||
automaton.addTransition(States.b, 2, States.c);
|
|
||||||
automaton.addTransition(States.c, 3, States.a);
|
|
||||||
|
|
||||||
expect(automaton.state, States.a);
|
|
||||||
automaton.onInput(1);
|
|
||||||
expect(automaton.state, States.b);
|
|
||||||
automaton.onInput(2);
|
|
||||||
expect(automaton.state, States.c);
|
|
||||||
automaton.onInput(3);
|
|
||||||
expect(automaton.state, States.a);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Test a simple DFA with a trap state", () {
|
|
||||||
final automaton = DeterministicFiniteAutomaton<States, int>(States.a, trapState: States.trap);
|
|
||||||
automaton.addTransition(States.a, 1, States.b);
|
|
||||||
automaton.addTransition(States.b, 2, States.c);
|
|
||||||
automaton.addTransition(States.c, 3, States.a);
|
|
||||||
|
|
||||||
expect(automaton.state, States.a);
|
|
||||||
automaton.onInput(1);
|
|
||||||
expect(automaton.state, States.b);
|
|
||||||
automaton.onInput(2);
|
|
||||||
expect(automaton.state, States.c);
|
|
||||||
automaton.onInput(4);
|
|
||||||
expect(automaton.state, States.trap);
|
|
||||||
|
|
||||||
// Transitioning away from the trap state should not be possible
|
|
||||||
automaton.onInput(5);
|
|
||||||
expect(automaton.state, States.trap);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Test a simple Mealy Automaton", () {
|
|
||||||
bool called = false;
|
|
||||||
final callback = (state, input) {
|
|
||||||
called = true;
|
|
||||||
};
|
|
||||||
final automaton = MealyAutomaton<States, int>(States.a);
|
|
||||||
|
|
||||||
automaton.addTransition(States.a, 1, States.b, callback);
|
|
||||||
|
|
||||||
automaton.onInput(1);
|
|
||||||
|
|
||||||
expect(automaton.state, States.b);
|
|
||||||
expect(called, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("Test a simple Mealy Automaton with a trap state", () {
|
|
||||||
bool called = false;
|
|
||||||
bool trapCalled = false;
|
|
||||||
final callback = (state, input) {
|
|
||||||
called = true;
|
|
||||||
};
|
|
||||||
final trapCallback = (state, input) {
|
|
||||||
trapCalled = true;
|
|
||||||
};
|
|
||||||
final automaton = MealyAutomaton<States, int>(States.a, trapState: States.trap, trapCallback: trapCallback);
|
|
||||||
|
|
||||||
automaton.addTransition(States.a, 1, States.b, callback);
|
|
||||||
|
|
||||||
automaton.onInput(1);
|
|
||||||
expect(called, true);
|
|
||||||
|
|
||||||
automaton.onInput(1);
|
|
||||||
expect(automaton.state, States.trap);
|
|
||||||
expect(trapCalled, true);
|
|
||||||
});
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user