diff --git a/src/editor/mapper.ts b/src/editor/mapper.ts index 0520fde..cb3cbf8 100644 --- a/src/editor/mapper.ts +++ b/src/editor/mapper.ts @@ -1,7 +1,7 @@ import { Graph } from './d3/graph.ts'; type State = { - transitions: Record; + transitions: Record; start?: boolean; accepting?: boolean; }; @@ -19,8 +19,10 @@ export function createGraph(states: State[]): Graph { } for (let i = 0; i < states.length; i++) { const state = states[i]; - for (const [letter, target] of Object.entries(state.transitions)) { - graph.createLink(i, target, letter); + for (const [letter, targets] of Object.entries(state.transitions)) { + for (const target of targets) { + graph.createLink(i, target, letter); + } } } return graph; @@ -43,7 +45,10 @@ export function createStateList(graph: Graph): State[] { const source = reduced[link.source.id]; const target = reduced[link.target.id]; for (const label of link.transition.split('')) { - states[source].transitions[label] = target; + if (!states[source].transitions[label]) { + states[source].transitions[label] = []; + } + states[source].transitions[label].push(target); } } return states; diff --git a/src/examples.js b/src/examples.js index 1e9c471..d5b1589 100644 --- a/src/examples.js +++ b/src/examples.js @@ -1,6 +1,6 @@ /** * @typedef State - * @property {Object.} transitions + * @property {Object.} transitions * @property {boolean} [start] * @property {boolean} [accepting] */ @@ -9,46 +9,46 @@ * @type {State[]} */ export const STARTS_ENDS_A = [ - { transitions: { a: 1, b: 3 } }, - { transitions: { a: 2, b: 1 } }, - { transitions: { a: 2, b: 1 }, accepting: true }, - { transitions: { a: 3, b: 3 } }, + { transitions: { a: [1], b: [3] } }, + { transitions: { a: [2], b: [1] } }, + { transitions: { a: [2], b: [1] }, accepting: true }, + { transitions: { a: [3], b: [3] } }, ]; /** * @type {State[]} */ export const STARTS_BB = [ - { transitions: { a: 3, b: 1 } }, - { transitions: { a: 3, b: 2 } }, - { transitions: { a: 2, b: 2 }, accepting: true }, - { transitions: { a: 3, b: 3 } }, + { transitions: { a: [3], b: [1] } }, + { transitions: { a: [3], b: [2] } }, + { transitions: { a: [2], b: [2] }, accepting: true }, + { transitions: { a: [3], b: [3] } }, ]; /** * @type {State[]} */ export const EXACTLY_ONE_B = [ - { transitions: { a: 0, b: 1 } }, - { transitions: { a: 1, b: 2 }, accepting: true }, - { transitions: { a: 2, b: 2 } }, + { transitions: { a: [0], b: [1] } }, + { transitions: { a: [1], b: [2] }, accepting: true }, + { transitions: { a: [2], b: [2] } }, ]; /** * @type {State[]} */ export const ODD_NUMBER_OF_A = [ - { transitions: { a: 1, b: 0 } }, - { transitions: { a: 0, b: 1 }, accepting: true }, + { transitions: { a: [1], b: [0] } }, + { transitions: { a: [0], b: [1] }, accepting: true }, ]; /** * @type {State[]} */ export const ENDS_WITH_TWO_B = [ - { transitions: { a: 0, b: 1 } }, - { transitions: { a: 0, b: 2 } }, - { transitions: { a: 0, b: 2 }, accepting: true }, + { transitions: { a: [0], b: [1] } }, + { transitions: { a: [0], b: [2] } }, + { transitions: { a: [0], b: [2] }, accepting: true }, ]; export const AUTOMATONS = { diff --git a/src/fsm.js b/src/fsm.js index f3a3b9a..e0ac974 100644 --- a/src/fsm.js +++ b/src/fsm.js @@ -47,7 +47,7 @@ export function openAutomaton(states, editable = false) { */ function updateUIState() { wordInput.value = builder; - if (state === -1 || states.length === 0 || !states[state].accepting) { + if (typeof Array.from(state).find((state) => states[state].accepting) === 'undefined') { light.classList.remove(IS_VALID); light.classList.add(IS_INVALID); } else { @@ -55,8 +55,10 @@ export function openAutomaton(states, editable = false) { light.classList.add(IS_VALID); } graph.forEach((node) => node.active = false); - if (state in graph.nodes) { - graph.nodes[state].active = true; + for (const s of state) { + if (s in graph.nodes) { + graph.nodes[s].active = true; + } } viewer.restart(); } @@ -79,11 +81,16 @@ export function openAutomaton(states, editable = false) { * @param {string} letter */ function step(letter) { - if (state === -1) { - return; - } + /** @type {number[]} */ + const newStates = []; builder += letter; - state = states[state].transitions[letter] ?? -1; + for (const s of state) { + if (s in states) { + const transitions = states[s].transitions[letter]; + newStates.push(...transitions); + } + } + state = newStates; updateUIState(); } @@ -126,8 +133,17 @@ export function openAutomaton(states, editable = false) { /** * @param {import('./examples.js').State[]} states + * @returns {number[]} */ function findStart(states) { - let state = states.findIndex(state => state.start); - return Math.max(state, 0); + const starts = []; + for (let i = 0; i < states.length; i++) { + if (states[i].start) { + starts.push(i); + } + } + if (starts.length === 0) { + starts.push(0); + } + return starts; }