Simulate nondeterministic automatons
continuous-integration/drone/push Build is passing Details

main
Clément FRÉVILLE 1 year ago
parent bc3cac2460
commit b0dd188d52

@ -1,7 +1,7 @@
import { Graph } from './d3/graph.ts'; import { Graph } from './d3/graph.ts';
type State = { type State = {
transitions: Record<string, number>; transitions: Record<string, number[]>;
start?: boolean; start?: boolean;
accepting?: boolean; accepting?: boolean;
}; };
@ -19,8 +19,10 @@ export function createGraph(states: State[]): Graph {
} }
for (let i = 0; i < states.length; i++) { for (let i = 0; i < states.length; i++) {
const state = states[i]; const state = states[i];
for (const [letter, target] of Object.entries(state.transitions)) { for (const [letter, targets] of Object.entries(state.transitions)) {
graph.createLink(i, target, letter); for (const target of targets) {
graph.createLink(i, target, letter);
}
} }
} }
return graph; return graph;
@ -43,7 +45,10 @@ export function createStateList(graph: Graph): State[] {
const source = reduced[link.source.id]; const source = reduced[link.source.id];
const target = reduced[link.target.id]; const target = reduced[link.target.id];
for (const label of link.transition.split('')) { 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; return states;

@ -1,6 +1,6 @@
/** /**
* @typedef State * @typedef State
* @property {Object.<string, number>} transitions * @property {Object.<string, number[]>} transitions
* @property {boolean} [start] * @property {boolean} [start]
* @property {boolean} [accepting] * @property {boolean} [accepting]
*/ */
@ -9,46 +9,46 @@
* @type {State[]} * @type {State[]}
*/ */
export const STARTS_ENDS_A = [ export const STARTS_ENDS_A = [
{ transitions: { a: 1, b: 3 } }, { transitions: { a: [1], b: [3] } },
{ transitions: { a: 2, b: 1 } }, { transitions: { a: [2], b: [1] } },
{ transitions: { a: 2, b: 1 }, accepting: true }, { transitions: { a: [2], b: [1] }, accepting: true },
{ transitions: { a: 3, b: 3 } }, { transitions: { a: [3], b: [3] } },
]; ];
/** /**
* @type {State[]} * @type {State[]}
*/ */
export const STARTS_BB = [ export const STARTS_BB = [
{ transitions: { a: 3, b: 1 } }, { transitions: { a: [3], b: [1] } },
{ transitions: { a: 3, b: 2 } }, { transitions: { a: [3], b: [2] } },
{ transitions: { a: 2, b: 2 }, accepting: true }, { transitions: { a: [2], b: [2] }, accepting: true },
{ transitions: { a: 3, b: 3 } }, { transitions: { a: [3], b: [3] } },
]; ];
/** /**
* @type {State[]} * @type {State[]}
*/ */
export const EXACTLY_ONE_B = [ export const EXACTLY_ONE_B = [
{ transitions: { a: 0, b: 1 } }, { transitions: { a: [0], b: [1] } },
{ transitions: { a: 1, b: 2 }, accepting: true }, { transitions: { a: [1], b: [2] }, accepting: true },
{ transitions: { a: 2, b: 2 } }, { transitions: { a: [2], b: [2] } },
]; ];
/** /**
* @type {State[]} * @type {State[]}
*/ */
export const ODD_NUMBER_OF_A = [ export const ODD_NUMBER_OF_A = [
{ transitions: { a: 1, b: 0 } }, { transitions: { a: [1], b: [0] } },
{ transitions: { a: 0, b: 1 }, accepting: true }, { transitions: { a: [0], b: [1] }, accepting: true },
]; ];
/** /**
* @type {State[]} * @type {State[]}
*/ */
export const ENDS_WITH_TWO_B = [ export const ENDS_WITH_TWO_B = [
{ transitions: { a: 0, b: 1 } }, { transitions: { a: [0], b: [1] } },
{ transitions: { a: 0, b: 2 } }, { transitions: { a: [0], b: [2] } },
{ transitions: { a: 0, b: 2 }, accepting: true }, { transitions: { a: [0], b: [2] }, accepting: true },
]; ];
export const AUTOMATONS = { export const AUTOMATONS = {

@ -47,7 +47,7 @@ export function openAutomaton(states, editable = false) {
*/ */
function updateUIState() { function updateUIState() {
wordInput.value = builder; 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.remove(IS_VALID);
light.classList.add(IS_INVALID); light.classList.add(IS_INVALID);
} else { } else {
@ -55,8 +55,10 @@ export function openAutomaton(states, editable = false) {
light.classList.add(IS_VALID); light.classList.add(IS_VALID);
} }
graph.forEach((node) => node.active = false); graph.forEach((node) => node.active = false);
if (state in graph.nodes) { for (const s of state) {
graph.nodes[state].active = true; if (s in graph.nodes) {
graph.nodes[s].active = true;
}
} }
viewer.restart(); viewer.restart();
} }
@ -79,11 +81,16 @@ export function openAutomaton(states, editable = false) {
* @param {string} letter * @param {string} letter
*/ */
function step(letter) { function step(letter) {
if (state === -1) { /** @type {number[]} */
return; const newStates = [];
}
builder += letter; 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(); updateUIState();
} }
@ -126,8 +133,17 @@ export function openAutomaton(states, editable = false) {
/** /**
* @param {import('./examples.js').State[]} states * @param {import('./examples.js').State[]} states
* @returns {number[]}
*/ */
function findStart(states) { function findStart(states) {
let state = states.findIndex(state => state.start); const starts = [];
return Math.max(state, 0); for (let i = 0; i < states.length; i++) {
if (states[i].start) {
starts.push(i);
}
}
if (starts.length === 0) {
starts.push(0);
}
return starts;
} }

Loading…
Cancel
Save