diff options
Diffstat (limited to 'turing-machine/js')
-rw-r--r-- | turing-machine/js/main.js | 129 | ||||
-rw-r--r-- | turing-machine/js/turing_machine.js | 18 |
2 files changed, 84 insertions, 63 deletions
diff --git a/turing-machine/js/main.js b/turing-machine/js/main.js index 9ace6f4..6cd1bd7 100644 --- a/turing-machine/js/main.js +++ b/turing-machine/js/main.js @@ -1,3 +1,7 @@ +const TAPE_LEN = 150; +const BLANK_VAL = "B"; +const SIMULATION_INTERVAL = 200; + const MESSAGES = { SET_CELL: "SET_CELL", PAUSE: "PAUSE", @@ -9,16 +13,13 @@ const MESSAGES = { NEW_TURING_STATE: "NEW_TURING_STATE", }; +// -- the "real" code + const state = new Observable(); const inputCellId = (cellId) => `${cellId}-input`; -const setCellFromInput = (cellId, inputId) => { - const input = document.getElementById(inputId); - tape[cellId] = input.value; -}; - -const cell = (cellId, initValue = "NULL") => { +const cell = (cellId, initValue = "B") => { const cellDiv = document.createElement("div"); cellDiv.classList.add("cell"); cellDiv.id = cellId; @@ -38,7 +39,8 @@ const cell = (cellId, initValue = "NULL") => { input.addEventListener("focusout", () => state.notify({ type: MESSAGES.SET_CELL, cell: cellId, value: input.value }) ); - state.subscribe((msg) => { + + const msgListener = (msg) => { if (msg.type == MESSAGES.SET_CELL && msg.cell == cellId) { input.value = msg.value; } @@ -50,7 +52,11 @@ const cell = (cellId, initValue = "NULL") => { }); } else cellDiv.classList.remove("reading"); } - }); + if (msg.type == MESSAGES.COMPILE) { + state.unsubscribe(msgListener); + } + }; + state.subscribe(msgListener); cellDiv.appendChild(input); cellDiv.appendChild(readingHead); @@ -73,7 +79,7 @@ const parseRules = (rules, beginState) => { .map((x) => x.trim()) .filter((x) => x); if (items.length != 4) { - throw new Error(`Invalid instruction on line ${line}`); + throw new Error(`Invalid instruction on line ${line + 1}`); } instructions.push(items); }); @@ -85,32 +91,58 @@ const parseRules = (rules, beginState) => { return instructions; }; -// -- +// -- a bit of some hacky ui code -- const tapeEl = document.getElementById("tape"); const compileButton = document.getElementById("compile"); -const instructionsEl = document.getElementById("instructions"); +const resetButton = document.getElementById("reset"); const controlsEl = document.getElementById("controls"); const nextStepButton = document.getElementById("next_step"); const togglePlayButton = document.getElementById("toggle_play"); const compileStatusEl = document.getElementById("compile_status"); const turingMachineStateEl = document.getElementById("turing_state"); -const resetButton = document.getElementById("reset"); +const copyStateButton = document.getElementById("copy_state"); -resetButton.addEventListener("click", () => { - state.notify({ type: MESSAGES.PAUSE }); - state.notify({ type: MESSAGES.COMPILE, value: instructionsEl.value }); +// init instructions from params +const instructionsEl = document.getElementById("instructions"); +const editorEl = CodeMirror.fromTextArea(instructionsEl, { + lineNumbers: true, }); +const urlParams = new URLSearchParams(window.location.search); +if (urlParams.get("instructions")) { + editorEl.setValue(atob(urlParams.get("instructions"))); +} -compileButton.addEventListener("click", () => { - state.notify({ type: MESSAGES.COMPILE, value: instructionsEl.value }); -}); +[compileButton, resetButton].forEach((button) => + button.addEventListener("click", () => { + state.notify({ type: MESSAGES.PAUSE }); + state.notify({ type: MESSAGES.COMPILE, value: editorEl.getValue() }); + }) +); nextStepButton.addEventListener("click", () => { state.notify({ type: MESSAGES.PAUSE }); state.notify({ type: MESSAGES.NEXT_STEP }); }); +// hackily copy the program state and put it in the clipboard +copyStateButton.addEventListener("click", () => { + const start = Array(TAPE_LEN) + .fill(BLANK_VAL) + .map((blank, i) => document.getElementById(inputCellId(i))?.value ?? blank) + .join("") + .replaceAll(new RegExp(`(${BLANK_VAL})*\$`, "g"), ""); + const instructions = btoa(editorEl.getValue()); + + navigator.clipboard + .writeText( + window.location.href.split("?")[0] + + `?start=${start}&instructions=${instructions}` + ) + .then(() => alert("copied to clipboard")); +}); + +// simulate / pause button let playButton_simulationStatus = "paused"; togglePlayButton.addEventListener("click", function () { if (playButton_simulationStatus == "paused") { @@ -119,6 +151,16 @@ togglePlayButton.addEventListener("click", function () { state.notify({ type: MESSAGES.PAUSE }); } }); +state.subscribe((msg) => { + if (msg.type == MESSAGES.PAUSE) { + togglePlayButton.innerHTML = "🔁 Begin"; + playButton_simulationStatus = "paused"; + } + if (msg.type == MESSAGES.SIMULATE) { + togglePlayButton.innerHTML = "⏸️ Pause"; + playButton_simulationStatus = "simulate"; + } +}); state.subscribe((msg) => { if (msg.type == MESSAGES.COMPILE_STATUS) { @@ -142,23 +184,12 @@ state.subscribe((msg) => { if (error) { controlsEl.style.display = "none"; } else if (success) { - controlsEl.style.display = "block"; + controlsEl.style.display = "flex"; } } }); state.subscribe((msg) => { - if (msg.type == MESSAGES.PAUSE) { - togglePlayButton.innerHTML = "🔁 Begin"; - playButton_simulationStatus = "paused"; - } - if (msg.type == MESSAGES.SIMULATE) { - togglePlayButton.innerHTML = "⏸️ Pause"; - playButton_simulationStatus = "simulate"; - } -}); - -state.subscribe((msg) => { if (msg.type == MESSAGES.NEW_TURING_STATE) { turingMachineStateEl.innerHTML = msg.value; } @@ -167,35 +198,35 @@ state.subscribe((msg) => { // - const main = () => { - const blank = "B"; - const beginState = "q0"; - const acceptState = "f"; - const tape = Array(200).fill(blank); - const cells = tape.map((_, cellId) => cell(cellId, blank)); - for (const cell of cells) { - tapeEl.appendChild(cell); - } - let turingMachine; + const startString = urlParams.get("start"); state.subscribe((msg) => { if (msg.type == MESSAGES.SET_CELL) { const { value, cell } = msg; - tape[cell] = value; if (turingMachine) turingMachine.setTapeAtCell(cell, value); } if (msg.type == MESSAGES.COMPILE) { const { value } = msg; try { + const beginState = "q0"; + const acceptState = "f"; + const tape = Array(TAPE_LEN) + .fill(BLANK_VAL) + .map((x, i) => + startString && i < startString.length ? startString[i] : x + ); + + // put the cells into the tape + const cells = tape.map((_, cellId) => cell(cellId, tape[cellId])); + tapeEl.innerHTML = ""; + for (const cell of cells) { + tapeEl.appendChild(cell); + } + const rules = parseRules(value, beginState, acceptState); + turingMachine = new TuringMachine(tape, rules, beginState, acceptState); - turingMachine = new TuringMachine( - [...tape], - rules, - beginState, - blank, - acceptState - ); state.notify({ type: MESSAGES.SET_READER, cell: 0 }); state.notify({ type: MESSAGES.NEW_TURING_STATE, @@ -209,7 +240,7 @@ const main = () => { if (msg.type == MESSAGES.SIMULATE) { const interval = setInterval(() => { state.notify({ type: MESSAGES.NEXT_STEP }); - }, 300); + }, SIMULATION_INTERVAL); const subscriptionFn = (msg) => { if (msg.type == MESSAGES.PAUSE) { clearInterval(interval); @@ -231,7 +262,7 @@ const main = () => { type: MESSAGES.NEW_TURING_STATE, value: accepting ? `<span class='success'>Accept(${status})</span>` - : `<span class='error'>Fail(${status}}</span>`, + : `<span class='error'>Fail(${status})</span>`, }); } else { state.notify({ diff --git a/turing-machine/js/turing_machine.js b/turing-machine/js/turing_machine.js index 9c21983..a61b43a 100644 --- a/turing-machine/js/turing_machine.js +++ b/turing-machine/js/turing_machine.js @@ -1,16 +1,9 @@ class TuringMachine { - constructor( - tape = [], - rules = [], - initialState = "q0", - blankSymbol = "B", - acceptState = "f" - ) { + constructor(tape = [], rules = [], initialState = "q0", acceptState = "f") { this.tape = tape; this.rules = this.parseRules(rules); this.state = initialState; this.head = 0; - this.blankSymbol = blankSymbol; this.acceptState = acceptState; this.iteration = 0; @@ -51,7 +44,7 @@ class TuringMachine { } step() { - const currentSymbol = this.tape[this.head] || this.blankSymbol; + const currentSymbol = this.tape[this.head]; const key = `${this.state},${currentSymbol}`; if (!(key in this.rules)) { return false; @@ -60,6 +53,7 @@ class TuringMachine { const [newState, action] = rule.split(","); this.state = newState; + this.iteration++; if (action === "R") { this.head += 1; @@ -73,10 +67,6 @@ class TuringMachine { return false; } - if (this.head >= 0 && this.head < this.tape.length) { - this.iteration++; - return true; - } - return false; + return this.head >= 0 && this.head < this.tape.length; } } |