Created
September 7, 2019 20:22
-
-
Save matthewp/225b01c33895225e1fb02f7871a3bec5 to your computer and use it in GitHub Desktop.
FSM backup
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function valueEnumerable(value) { | |
return { enumerable: true, value }; | |
} | |
function valueEnumerableWritable(value) { | |
return { enumerable: true, writable: true, value }; | |
} | |
let truthy = () => true; | |
let empty = () => ({}); | |
let create = (a, b) => Object.freeze(Object.create(a, b)); | |
function stack(fns) { | |
return fns.reduce((par, fn) => { | |
return function(...args) { | |
return fn.apply(this, args); | |
}; | |
}, truthy); | |
} | |
function fnType(fn) { | |
return create(this, { fn: valueEnumerable(fn) }); | |
} | |
let actionType = {}; | |
export let action = fnType.bind(actionType); | |
let reduceType = {}; | |
export let reduce = fnType.bind(reduceType); | |
let guardType = {}; | |
export let guard = fnType.bind(guardType); | |
function filter(Type, arr) { | |
return arr.filter(value => Type.isPrototypeOf(value)); | |
} | |
export function transition(from, to, ...args) { | |
let reducers = stack(filter(reduceType, args).map(t => t.fn)); | |
let guards = stack(filter(guardType, args).map(t => t.fn)); | |
return { from, to, guards, reducers }; | |
} | |
function transitionsToObject(transitions) { | |
return Object.fromEntries(transitions.map(t => [t.from, t])); | |
} | |
export function state(...transitions) { | |
return { | |
transitions: transitionsToObject(transitions) | |
}; | |
} | |
// TODO fill this out | |
export function immediate(to, ...args) { | |
return { to }; | |
} | |
let invokeType = {}; | |
export function invoke(fn, ...transitions) { | |
return create(invokeType, { | |
fn: valueEnumerable(fn), | |
transitions: valueEnumerable(transitionsToObject(transitions)) | |
}); | |
} | |
let machine = { | |
get state() { | |
return { | |
name: this.current, | |
value: this.states[this.current] | |
}; | |
} | |
}; | |
export function createMachine(states, contextFn) { | |
let current = Object.keys(states)[0]; | |
return create(machine, { | |
context: valueEnumerable(contextFn || empty), | |
current: valueEnumerable(current), | |
states: valueEnumerable(states) | |
}); | |
} | |
export function send(service, event) { | |
let eventName = event.type || event; | |
let { machine, context } = service; | |
let { value: state } = machine.state; | |
if(eventName in state.transitions) { | |
let { to, guards, reducers } = state.transitions[eventName]; | |
if(guards(context)) { | |
service.context = reducers.call(service, event, context); | |
let original = machine.original || machine; | |
let newMachine = create(original, { | |
current: valueEnumerable(to), | |
original: { value: original } | |
}); | |
let state = newMachine.state.value; | |
if(invokeType.isPrototypeOf(state)) { | |
run(service, state, event); | |
} | |
return newMachine; | |
} | |
} | |
return machine; | |
} | |
function run(service, invoker, event) { | |
invoker.fn.call(service, service.context, event) | |
.then(data => service.send({ type: 'done', data })) | |
.catch(error => service.send({ type: 'error', error })); | |
} | |
let service = { | |
send(event) { | |
this.machine = send(this, event); | |
// TODO detect change | |
this.onChange(this); | |
} | |
}; | |
export function interpret(machine, onChange) { | |
let s = Object.create(service, { | |
machine: valueEnumerableWritable(machine), | |
context: valueEnumerableWritable(machine.context()), | |
onChange: valueEnumerable(onChange) | |
}); | |
s.send = s.send.bind(s); | |
return s; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment