Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

François-Emmanuel CORTES
François-Emmanuel CORTES

Posted on

     

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')    })
Enter fullscreen modeExit fullscreen mode

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'})
Enter fullscreen modeExit fullscreen mode

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')
Enter fullscreen modeExit fullscreen mode

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 ('uid\tdef\talias\t\tmore info...')        texts.push ('--------'.repeat(5))        entries.map ((item) => {            if (item.def === 'STATE') {                texts.push ([                    item.uid, 'STATE', item.alias + '\t',                     (item.init ? '*init*' : '') +  (item.term ? "*term*" : "")                ].join('\t'))            }            if (item.def === 'ARROW') {                texts.push ([                    item.uid, 'ARROW', item.alias + '\t',                     item.from + ' --{' + item.accept +  '}-> ' +  item.to                ].join('\t'))            }        })        return texts.join('\n')    }    return {        state, arrow,  pack, read, table    }}
Enter fullscreen modeExit fullscreen mode

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

lately web dev at Université de Bretagne Occidentale -during yezr 2022) interests: aero and sea drones, chess playing, sailingPhysics Math and IT studies
  • Location
    Brest, Britany, Fraznce
  • Education
    IT bachelor Univfersité de Lille
  • Work
    web devUBO Brest FRance
  • Joined

More fromFrançois-Emmanuel CORTES

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp