import mermaid from 'mermaid'; import { ENDS_WITH_TWO_B } from './examples.js'; const IS_VALID = 'is-valid'; const IS_INVALID = 'is-invalid'; const wordInput = /** @type {HTMLInputElement} */ (document.getElementById('word-input')); const buttons = /** @type {HTMLDivElement} */ (document.getElementById('input-buttons')); const clearButton = /** @type {HTMLButtonElement} */ (document.getElementById('clear-button')); const light = /** @type {HTMLDivElement} */ (document.getElementById('light')); mermaid.initialize({ theme: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'default', startOnLoad: false, }); const states = ENDS_WITH_TWO_B; let state = 0; let builder = ''; // Build the mermaid graph definition let graphDefinition = 'stateDiagram-v2'; for (let i = 0; i < states.length; ++i) { graphDefinition += `\n s${i} : ${i}`; for (const [transition, destination] of Object.entries(states[i].transitions)) { graphDefinition += `\n s${i} --> s${destination}: ${transition}`; } } const graph = /** @type {HTMLDivElement} */ (document.getElementById('pen')); const { svg } = await mermaid.render('state-graph', graphDefinition); graph.innerHTML = svg; const nodes = graph.querySelectorAll('.label-container'); /** * Updates the UI to reflect the current state. */ function updateUIState() { wordInput.value = builder; if (state === -1 || !states[state].accepting) { light.classList.remove(IS_VALID); light.classList.add(IS_INVALID); } else { light.classList.remove(IS_INVALID); light.classList.add(IS_VALID); } nodes.forEach((node) => node.classList.remove('current-node')); if (state in nodes) { nodes[state].classList.add('current-node'); } } /** * Steps the FSM with the given letter. * * @param {string} letter */ function step(letter) { if (state === -1) { return; } builder += letter; state = states[state].transitions[letter] ?? -1; updateUIState(); } // Dynamically create buttons for each letter in the alphabet /** * @type {string[]} */ const alphabet = Array.from(states.reduce((acc, current) => { Object.keys(current.transitions).forEach(current => acc.add(current)); return acc; }, new Set())).sort(); for (const letter of alphabet) { const button = document.createElement('button'); button.innerText = letter; button.addEventListener('click', () => step(letter)); buttons.appendChild(button); } // Reacts to input in the text box wordInput.addEventListener('input', () => { const value = wordInput.value; builder = ''; state = 0; for (const letter of value) { step(letter); } if (!value.length) { updateUIState(); } }); clearButton.addEventListener('click', () => { wordInput.value = ''; builder = ''; state = 0; updateUIState(); }); updateUIState();