function-automata
Version:
This is a package for creating and using finite automita
104 lines (99 loc) • 4.57 kB
JavaScript
const State = require('./state');
/**
* This is the constructor for the {Automata} object
* @param {Array~String} states The list of valid state names
* @param {Array~Array} transitions The list of valid transitions
* @member {transition}
* @type {Array~Mixed}
* @param {String} index_0 The name of the state which will be the origin of the transition
* @param {String} index_1 The name of the state which will be the destination of the transition
* @param {function} index_2 The accept function for a given token to take this transition
* @function {index_2}
* @param {String} token The token to be checked and if correct will the transition will be taken
* @param {Object} state The automata state object that can be used to store counters and check their values later
* @return {Boolean} Weather or not the token is accepted
* @param {Array~String} accept_states The list of accept states
* @param {String} start_state The name of the state to start the automata
* @constructor
*/
function Automata(states, transitions, accept_states, start_state) {
if (!Array.isArray(states)) {
throw new Error("states is not an array");
}
if (!Array.isArray(transitions)) {
throw new Error("transitions is not an array");
}
if (!Array.isArray(accept_states)) {
throw new Error("accept_states is not an array");
}
states.forEach(state => {
if (typeof state !== "string") {
throw new Error("Not every state is a string");
}
});
states.sort((a, b) => {
if (a < b) {
return -1;
}
if (a === b) {
throw new Error("States have been defined multiple times: " + a);
}
return 1;
});
transitions.forEach(transition => {
if (!Array.isArray(transition)) {
throw new Error("Not every transition is an array");
}
if (transition.length !== 3) {
throw new Error("Not every transition has only a `from`, a `to` state, and a transitional token function");
}
if (states.indexOf(transition[0]) < 0 || states.indexOf(transition[1]) < 0) {
throw new Error("Not every transition's states are valid");
}
if (typeof transition[2] !== "function" || (transition[2].length != 1 && transition[2].length != 2)) {
throw new Error("Not every transition's transitional token is a function with the correct number of parameters");
}
});
transitions.sort((a, b) => {
if (a[0] < b[0]) {
return -1;
}
if (a[0] === b[0]) {
if (a[1] < b[1]) {
return -1;
}
if (a[1] === b[1] && Object.is(a, b)) {
throw new Error("Not every transition is a unique arrow: " + a[1] + " -> " + b[1]);
}
}
return 1;
})
accept_states.forEach(state => {
if (typeof state !== "string") {
throw new Error("Not every accept_state is a string");
}
if (states.indexOf(state) < 0) {
throw new Error("Not every accept_state is a valid state");
}
});
if (states.indexOf(start_state) < 0) {
throw new Error("start_state is not a valid state");
}
let all_states = states.map(state => new State(state, accept_states));
all_states.forEach(state => state.setUpTransitions(all_states, transitions));
this.start = all_states.find(state => state.name === start_state);
}
var _ = Automata.prototype;
/**
* This function goes through the provided tokens to check if they are valid according to the dfa
* @param {Array} tokens This is a list of tokens in forward order
* @param {Function} cb The function used to pass information back to caller
* @function {cb}
* @param {Boolean} isValid Whether or not the list of tokens is valid by the DFA
* @param {String} error {optional} This error string on why the validation is false
* @param {Object} state {optional} This is the state object for context sensitive data. If you don't want to use it then don't pass in a variable yourself
*/
_.validate = function (tokens, cb, state = {}) {
this.start.transition(tokens.reverse(), cb, state);
};
module.exports = Automata;