An Interest In:
Web News this Week
- March 5, 2024
- March 4, 2024
- March 3, 2024
- March 2, 2024
- March 1, 2024
- February 29, 2024
- February 28, 2024
smplie finite state machine for EcmaScript
Mach: flexible FSM in ECMAScript
Hi there,
I just developped a simple Finite State Machine in JS. My initial goal was to integrate it within a SvelteKit component to provide UI state management.
Design and usage
this is a single ECMAScript file; simply import it in yout code, and call the FSM () constructor with its parameters:
import { FSM } from './FSM.js' const fsm = FSM ({ debug: true, jumps: ({ state }) => console.log ({ state }), success: ({ state }) => console.log ('success'), failure: ({ state }) => console.log ('failure') })
Then you can simply define states and transition arrows:
fsm.state ({ name: 'INIT', init: true }) fsm.state ({ name: 'TERM', term: true }) fsm.state ({ name: 'OFF' }) fsm.state ({ name: 'ON' }) fsm.arrow ({ from: 'INIT', accept: 'activate', to: 'OFF'}) fsm.arrow ({ from: 'OFF', accept: 'switch', to: 'ON'}) fsm.arrow ({ from: 'ON', accept: 'switch', to: 'OFF'}) fsm.arrow ({ from: 'ON', accept: "kick", to: 'TERM'})
Of course with this way of doing things, you can freely hydrate states and transition arrows from an external data store such a database; or modularize you FSM design according to logial tests !
After all, pack the machine to run it, and read some entries:
fsm.pack ({ state: 'INIT '}) fsm.accept ({ word: 'activate'}) fsm.accept ({ word: 'on'}) fsm.accept ({ word: 'kick'}) // console.log ('success')
Implementation details
all entries (STATES and reanition arrows) are stored in the same array structure, as duck blocks. The items list is doubled by a lookup map which holds pairs (name) => [array-of-indexes] for all name inside the machine (UPPERCASED for STATES and lowercased for arrows). This shorts the time used to locate states/words blocks .
Further steps
adding support for asynchronous transduction operations.
/// Here's the complete code for the Mach FSM ...export const FSM = ({ jumping, success, failure, debug } = {}) => { // holds STATE and ARROW items const entries = [{ uid: 0, def: 'ROOT' }] // aliases lookups table const lookups = new Map () const runtime = { GUID: 0, state: null, trace: [], data: null } // state definition const state = ({ name, init, term, payload }) => { const def = 'STATE' const alias = ('' + name).toUpperCase () const uid = ++runtime.GUID const uids = lookups.get(alias) ||[] const len = uids.length const item = { uid, def, alias, init, term, payload } if (len === 0) { entries.push (item) lookups.set (alias, [uid]) } else { throw new Error ('duplicate entry ' + uis + ' ' + alias) } if (debug) { if (len === 0) { console.log ('creating new state', { alias, uid }) } else { console.log ('replacing item', { alias, uid }) } } } // arrow definition const arrow = ({ from, accept, to, ops }) => { const def = 'ARROW' const alias = ('' + accept).toLowerCase () const uid = ++runtime.GUID const uids = lookups.get(alias) ||[] const len = uids.length const item = { uid, def, alias, from, accept, to, ops } entries.push (item) uids.push (uid) lookups.set (alias, uids)// console.log ('craating arrow', { item }) } // ready to run !!! const pack = ({ state, data }) => { const alias = ('' + state).toUpperCase () runtime.state = alias runtime.data = data if (debug) { console.log('Finite State Machine packing', alias) } } // read entry word const read = ({ word, data }) => { const alias = ('' + word).toLowerCase () const uids = lookups.get (alias) || [] let accepted = false console.log ('read', { [alias]: uids.join(', ') }) uids.map ((uid) => entries [uid]) .map ((item) => { console.log ('MAP', { item }) return item }) .filter ((item) => accepted === false) .filter ((item) => item.def === 'ARROW') .filter ((item) => item.from === runtime.state) .filter ((item) => item.accept === alias) .map ((item) => { const suids = lookups.get (item.to) ||[] const final = entries [suids[0]] runtime.state = item.to runtime.trace.push (alias) accepted = true jumping && jumping.call && jumping.call (null, { trace: runtime.trace, result: runtime.data, state: runtime.state, word: alias }) item.ops && item.ios.call && item.ops.call (null, { data, trace: runtime.trace, result: runtime.data, state: runtime.state, word: alias }) if (final.term) { success && success.call (null, { data, trace: runtime.trace, result: runtime.data, state: runtime.state, word: alias }) } return true }) if (accepted === false) { failure && failure.call (null, { data, trace: runtime.trace, result: runtime.data, state: runtime.state, word: alias }) } } // return debug table as string const table = () => { const texts = [] texts.push ('~~~ Finistamach: lightweight Finite State Machine ~~~') texts.push ('') texts.push ('uiddefaliasmore info...') texts.push ('--------'.repeat(5)) entries.map ((item) => { if (item.def === 'STATE') { texts.push ([ item.uid, 'STATE', item.alias + '', (item.init ? '*init*' : '') + (item.term ? "*term*" : "") ].join('')) } if (item.def === 'ARROW') { texts.push ([ item.uid, 'ARROW', item.alias + '', item.from + ' --{' + item.accept + '}-> ' + item.to ].join('')) } }) return texts.join('
') } return { state, arrow, pack, read, table }}
Original Link: https://dev.to/hefeust/smplie-finite-state-machine-for-ecmascript-2a5d
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To