`;
const START_BUTTON = ``
const STOP_BUTTON = ``
// -------------------------------------------------------------------------------------
// MANIPULATION OF TURING MACHINE OBJECT
// Adds a new tuple to the TM object. If an instruction with the same
// prefix exists, then it doesn't add it.
function tm_add_instruction (tuple) {
prefix_key = tuple[0] + "/" + tuple[1];
if (prefix_key in TM["instructions"]) {
// -- error: instruction prefix already present!
return -1;
}
TM["instructions"][prefix_key] = [tuple[2], tuple[3], tuple[4]];
}
// Deletes a tuple from the TM object. If the tuple-prefix is not
// present in the object, it doesn't delete it.
function tm_remove_instruction (tuple) {
prefix_key = tuple[0] + "/" + tuple[1];
if (!(prefix_key in TM["instructions"])) {
// -- error: instruction prefix not present!
return -1;
}
delete TM["instructions"][prefix_key];
}
// Updates information regarding the tape, such as the tape contents
// and the current position of the head.
function tm_update_tape (tape) {
TM["tape"] = tape;
}
function tm_update_head (head_pos) {
// TODO: maybe extra check, for example if the pos is valid given
// current tape?
TM["head_pos"] = head_pos;
}
function tm_update_state (state) {
TM["current_state"] = state == "" ? null : state;
}
// Returns all possible state of the machine. The states are computed
// dynamically based on the current set of instructions.
function tm_get_possible_states() {
// A state is something that can either happen in...
const states = new Set();
for (const ins in TM["instructions"]) {
// ... the first part prefix-key
states.add(ins.split("/")[0]);
// ... or in the first element of the value list
states.add(TM["instructions"][ins][0]);
}
return states;
}
// -------------------------------------------------------------------------------------
// INSTRUCTION TABLE FUNCTIONS
function extract_instruction (trow) {
cols = trow.getElementsByTagName("td");
if (cols.length < 7) {
alert("[ERROR]: Row is malfored: not enough TDs!");
return null;
}
ins = [ cols[1].firstChild.value, cols[2].firstChild.value,
cols[4].firstChild.value, cols[5].firstChild.value,
cols[6].firstChild.value ];
for(i = 0; i < ins.length; i++) {
if (ins[i] == "") {
alert("[ERROR]: Row data is not enough!");
return null;
}
}
return ins;
}
function disable_add_instruction_button () {
rows = document.getElementById("item-button")
.parentNode
.parentNode
.getElementsByTagName("tr");
button = rows[rows.length - 1]
.getElementsByTagName("td")[0]
.getElementsByTagName("button")[0]
button.setAttribute("disabled", true);
}
function enable_add_instruction_button () {
rows = document.getElementById("item-button")
.parentNode
.parentNode
.getElementsByTagName("tr");
button = rows[rows.length - 1]
.getElementsByTagName("td")[0]
.getElementsByTagName("button")[0]
button.removeAttribute("disabled");
}
// Executed when user adds new row by clicking on the '+' button. This
// function checks if the row is valid, and if it is it adds the newly
// added instruction to the current TM state. A new empty row is then
// added to the DOM for further instructions.
//
// The *trow* param refers to the row being added.
//
function extract_new_instruction (trow) {
tuple = extract_instruction(trow);
if (!tuple) {
return;
}
// update TM internal state
if (tm_add_instruction(tuple) < 0) {
alert("[ERROR]: Instruction prefix already exists!");
return;
}
// update DOM to reflect new instruction
// -- make all input fields read-only
make_row_readonly(trow);
// -- change button style
button = trow.getElementsByTagName("td")[0].getElementsByTagName("button")[0];
button.innerHTML = '-';
button.className = "btn btn-danger";
// -- update onclick function
button.setAttribute("onClick", "remove_instruction(this.parentNode.parentNode)");
// -- add new row below
trow.insertAdjacentHTML("afterend", INSTRUCTION_TEMPLATE);
// -- update state selection
update_state_selection_options(TM);
}
// Executed when user deletes an already existing row by clicking on
// the '-' button. This function checks if the row is valid, and if it
// is it deletes the instruction contained in the selected row from
// the current TM state.
//
// The *trow* param refers to the row being deleted.
//
function remove_instruction (trow) {
tuple = extract_instruction(trow);
if (!tuple) {
return;
}
// update TM internal state
tm_remove_instruction(tuple);
// update DOM to reflect deletion
trow.innerHTML = "";
trow.remove();
update_state_selection_options(TM);
}
function make_row_readonly (trow) {
input_box = trow.getElementsByTagName("input");
for (i = 0; i < input_box.length; i++) {
input_box[i].setAttribute("readonly", true);
}
select_box = trow.getElementsByTagName("select")[0];
select_box.setAttribute("disabled", true);
}
// Sets the instruction table based on the given TM object.
function set_instruction_table (TM) {
var table = document.getElementById("mt-instruction-table")
.getElementsByTagName("tbody")[0];
var table_row_header = table.getElementsByTagName("tr")[0];
table.innerHTML = "";
table.insertAdjacentHTML("afterbegin", table_row_header.innerHTML);
for (ins in TM["instructions"]) {
var curr_state = ins.split("/")[0];
var curr_symbol = ins.split("/")[1];
var new_state = TM["instructions"][ins][0];
var new_symbol = TM["instructions"][ins][1];
var movement = TM["instructions"][ins][2];
// TODO: make this a global variable and format it somehow?
var new_row = `
`
table.insertAdjacentHTML("beforeend", new_row);
}
table.insertAdjacentHTML("beforeend", INSTRUCTION_TEMPLATE);
}
// -------------------------------------------------------------------------------------
// TAPE FUNCTIONS
function extract_tape() {
// -- extract tape contents
var tape = [];
var tape_row = document.getElementById("mt-tape-content")
.getElementsByTagName("td");
// -- get tape contents
for (let i = 0; i < tape_row.length; i++) {
tape.push(tape_row[i].firstChild.value);
}
// -- update TM internal state
tm_update_tape(tape);
}
// Add new cell on the right side of the left-most cell.
function add_tape_cell_left () {
console.log("[INFO] - add_tape_cell_left ()");
const tape_row = document.getElementById("mt-tape-content")
.getElementsByTagName("td");
const meta_tape_row = document.getElementById("mt-arrow-tape")
.getElementsByTagName("td");
tape_row[0].insertAdjacentHTML("afterend", EMPTY_TAPE_CELL);
meta_tape_row[0].insertAdjacentHTML("afterend", `
`);
}
// Add new cell on the left side of the right-most cell.
function add_tape_cell_right() {
console.log("[INFO] - add_tape_cell_right ()");
const tape_row = document.getElementById("mt-tape-content")
.getElementsByTagName("td");
const meta_tape_row = document.getElementById("mt-arrow-tape")
.getElementsByTagName("td");
tape_row[tape_row.length - 1].insertAdjacentHTML("beforebegin", EMPTY_TAPE_CELL);
meta_tape_row[meta_tape_row.length - 1].insertAdjacentHTML("beforebegin", `
`);
}
function update_tape(pos) {
var tape_row = document.getElementById("mt-tape-content")
.getElementsByTagName("td");
tape_row[pos].firstChild.value = TM["tape"][pos];
}
function get_head_pos () {
var head_pos = null;
var tape_meta_row = document.getElementById("mt-arrow-tape")
.getElementsByTagName("td");
// -- get old head pos
for (let i = 0; i < tape_meta_row.length; i++) {
if (tape_meta_row[i].innerHTML != "") {
head_pos = i;
break;
}
}
return head_pos;
}
function extract_head_pos() {
// -- extract head pos in tape
var head_pos = null;
head_pos = get_head_pos();
// -------------------------------------
// update TM internal state
tm_update_head(head_pos);
}
function make_tape_readonly() {
var tape_row = document.getElementById("mt-tape-content")
.getElementsByTagName("td");
for (let i = 0; i < tape_row.length; i++) {
tape_row[i].firstChild.setAttribute("readonly", true);
}
}
function make_tape_writable() {
var tape_row = document.getElementById("mt-tape-content")
.getElementsByTagName("td");
for (let i = 0; i < tape_row.length; i++) {
tape_row[i].firstChild.removeAttribute("readonly");
}
}
function update_head_pos () {
var tape_meta_row = document.getElementById("mt-arrow-tape")
.getElementsByTagName("td");
var old_head_pos = get_head_pos();
// -- update head pos
tape_meta_row[old_head_pos].innerHTML = "";
tape_meta_row[TM["head_pos"]].innerHTML = ARROW_HTML;
}
// This function can be used in the setting up of the TM to change the
// initial position of the head. Everytime the user clicks above a
// cell, this function is called.
function update_head (td) {
// NOTE: shall we take care of concurrency?
if (EXECUTING) {
return;
}
const head_pos = TM["head_pos"] ? TM["head_pos"] : get_head_pos();
// 1. Remove previous arrow
document.getElementById("mt-arrow-tape")
.getElementsByTagName("td")[head_pos].innerHTML = "";
// 2. Set new arrow
td.innerHTML = ARROW_HTML;
// 3. Update TM state
extract_head_pos();
}
function set_tape_and_head_pos (TM) {
// TODO: implement this
const tape = TM["tape"];
const head_pos = TM["head_pos"];
const meta_row = document.getElementById("mt-arrow-tape");
const data_row = document.getElementById("mt-tape-content");
meta_row.innerHTML = "";
data_row.innerHTML = "";
// add first cols
meta_row.insertAdjacentHTML("afterbegin", `
`);
data_row.insertAdjacentHTML("afterbegin", `
`);
// add actual data cols
for (let i = 0; i < tape.length; i++) {
// check if need to insert arrow
if (i == head_pos) {
meta_row.insertAdjacentHTML("beforeend", `
`);
}
// add last cols
meta_row.insertAdjacentHTML("beforeend", `
`);
data_row.insertAdjacentHTML("beforeend", `
`);
}
// -------------------------------------------------------------------------------------
// STATE SELECTION FUNCTIONS
function extract_current_state () {
const select = document.getElementById("current-state-select-box");
tm_update_state(select.value);
}
// This function updates the option values used in the selection of
// the current state based on the instructions added so far to the TM
// object.
function update_state_selection_options (TM) {
const select = document.getElementById("current-state-select-box");
const states = tm_get_possible_states(TM);
// -- reset previous state
select.innerHTML = "";
// -- add new states
for (const s of states) {
select.insertAdjacentHTML("beforeend", ``);
}
if (!(TM["current_state"])) {
// -- extract new state
extract_current_state();
}
}
function update_state_selection (TM) {
const select = document.getElementById("current-state-select-box");
select.value = TM["current_state"];
}
function make_state_selection_readonly () {
const select = document.getElementById("current-state-select-box");
select.setAttribute("disabled", true);
}
function make_state_selection_writable () {
const select = document.getElementById("current-state-select-box");
select.removeAttribute("disabled");
}
// -------------------------------------------------------------------------------------
// OTHER FUNCTIONS
function update_execution_velocity(new_vel) {
// console.log("[INFO]: update_execution_velocity()");
TIME_STEP = new_vel;
}
function change_main_button() {
const form = document.getElementById("mt-control-panel-form");
form.innerHTML = '';
if (EXECUTING) {
console.log("Hello");
// -- transform 'Calcola!' to 'Ferma!'
form.insertAdjacentHTML("afterbegin", STOP_BUTTON);
} else {
// -- transform 'Ferma!' to 'Calcola!'
console.log("Hellooo");
form.insertAdjacentHTML("afterbegin", START_BUTTON);
}
}
// -------------------------------------------------------------------------------------
// EXECUTION FUNCTIONS
var DONE = false;
var ABORT = false;
var EXECUTING = false;
var STEPS_TAKEN = 0;
// time between the execution of each step in the computation.
var TIME_STEP = 1 * 1000;
// NOTE: this is initialized in main()
var STEPS_COUNTER = null;
function stop () {
ABORT = true;
}
function execute_single_step() {
// is there an instruction for the given (STATE, SYMBOL)?
prefix = TM["current_state"] + "/" + TM["tape"][TM["head_pos"]];
if (!(prefix in TM["instructions"])) {
return -1;
}
// instruction found, now we execute it!
// console.log("[INFO] instruction found: " + TM["instructions"][prefix]);
[new_state, symbol_to_write, movement] = TM["instructions"][prefix];
// -------------------------------------
// update TM internal state
const old_pos = TM["head_pos"];
// ---- 1. Write symbol on tape
TM["tape"][TM["head_pos"]] = symbol_to_write;
// ---- 2. Do the movement
// NOTE: the last and first cells are reserved for the add-cell button
if (movement == 'S') {
// S for Sinistra, in english 'left'
if (TM["head_pos"] > 1) {
TM["head_pos"] -= 1;
}
} else if (movement == 'D') {
// D for Destra, in italian 'right'
if (TM["head_pos"] < TM["tape"].length - 2) {
TM["head_pos"] += 1;
}
}
// ---- 3. Change internal state
TM["current_state"] = new_state;
// -------------------------------------
// update DOM to reflect deletion
// 1. update state shown in state selection
update_state_selection(TM);
// 2. update tape contents
update_tape(old_pos);
// 3. update head pos
update_head_pos();
// 4. update steps counter
// update_execution_steps_counter();
STEPS_TAKEN += 1;
STEPS_COUNTER.value = STEPS_TAKEN;
return 0;
}
async function execute() {
console.log("[INFO] - Starting execution!");
// prepare for execution:
DONE = false;
EXECUTING = true;
STEPS_TAKEN = 0;
make_state_selection_readonly();
make_tape_readonly();
disable_add_instruction_button();
// -- change buttom from 'Calcula!' to 'Ferma!'
change_main_button();
// -- extract tape contents to TM
extract_tape();
extract_head_pos();
while (!DONE && !ABORT) {
await new Promise(done => setTimeout(
() => {
DONE = execute_single_step() < 0;
done();
} ,TIME_STEP));
}
// epilogue
EXECUTING = false;
ABORT = false;
// -- change buttom from 'Ferma!' to 'Calcola!'
change_main_button();
make_state_selection_writable();
make_tape_writable();
enable_add_instruction_button();
console.log("[INFO] - Execution is over!");
console.log("[INFO] - Total steps: " + STEPS_TAKEN);
}
// -------------------------------------------------------------------------------------
// LOAD PRESETS
// Loads in the HTML DOM the various buttons to load the various TM
// presets.
function load_presets() {
const list = document.getElementById("example-list");
for (id in TM_PRESETS) {
var list_elem = ``;
list.insertAdjacentHTML("beforeend", list_elem);
}
}
function set_DOM(id) {
// -- get proper TM
TM = TM_PRESETS[id];
// 1. Set instructions in table
set_instruction_table(TM);
// 2. Set tape position and tape contents
set_tape_and_head_pos(TM);
// 3. Set current state
update_state_selection_options(TM);
update_state_selection(TM);
}
// -------------------------------------------------------------------------------------
function main() {
console.log("BEGIN main()");
load_presets();
STEPS_COUNTER = document.getElementById("execution-steps-display-box");
STEPS_COUNTER.value = 0;
console.log("END main()");
}
window.onload = () => {
main();
}