EcmaScript용 smplie 유한 상태 머신

7449 단어

Mach: ECMAScript의 유연한 FSM



안녕하세요,

방금 JS에서 간단한 Finite State Machine을 개발했습니다. 초기 목표는 SvelteKit 구성 요소 내에 통합하여 UI 상태 관리를 제공하는 것이었습니다.

디자인 및 사용법



이것은 단일 ECMAScript 파일입니다. 간단하게 코드로 가져오고 FSM() 생성자를 해당 매개변수와 함께 호출합니다.

    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')
    })


그런 다음 상태 및 전환 화살표를 간단히 정의할 수 있습니다.

    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'})


물론 이러한 방식으로 데이터베이스와 같은 외부 데이터 저장소에서 상태 및 전환 화살표를 자유롭게 수화할 수 있습니다. 또는 논리 테스트에 따라 FSM 설계를 모듈화하십시오!

결국, 그것을 실행하기 위해 기계를 포장하고 몇 가지 항목을 읽으십시오.

    fsm.pack ({ state: 'INIT '})

    fsm.accept ({ word: 'activate'})
    fsm.accept ({ word: 'on'})
    fsm.accept ({ word: 'kick'})

    // console.log ('success')    


구현 세부정보



모든 항목(STATES 및 reanition 화살표)은 오리 블록과 같은 동일한 배열 구조에 저장됩니다. 항목 목록은 머신 내부의 모든 이름에 대한 쌍(이름) => [인덱스 배열]을 포함하는 조회 맵에 의해 두 배가 됩니다(STATES의 경우 대문자, 화살표의 경우 소문자). 이렇게 하면 상태/단어 블록을 찾는 데 사용되는 시간이 단축됩니다.

추가 단계



비동기 변환 작업에 대한 지원을 추가합니다.

    /// 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
    }
}


좋은 웹페이지 즐겨찾기