/**
* The full computer object class
*/
//Global variables
var COMPONENT_SPACING = "20px";
var ANIMATE_NONE = 0, ANIMATE_FLOW = 1, ANIMATE_DATA = 2;
var PROGRAM_FLOW_SPEED_COEFFICIENT = 400;
var VICSERVER_DIR = "../VICServer/";
var READ_ONLY_MEM = [ 98, 99 ];
//Instruction register label texts
var INSTRUCTIONS_TEXT = [];
INSTRUCTIONS_TEXT[0] = "Stop
";
INSTRUCTIONS_TEXT[1] = "Add value of memory cell {0}
to data register";
INSTRUCTIONS_TEXT[2] = "Subtract value of memory
cell {0} from data register";
INSTRUCTIONS_TEXT[3] = "Load value of memory cell {0}
to data register";
INSTRUCTIONS_TEXT[4] = "Store value of data register
in memory cell {0}";
INSTRUCTIONS_TEXT[5] = "Go to line number {0}
";
INSTRUCTIONS_TEXT[6] = "If data register value is
equals to 0, go to line {0}";
INSTRUCTIONS_TEXT[7] = "If data register value is
grater than 0, go to line {0}";
INSTRUCTIONS_TEXT[8] = "Read next input
to data register";
INSTRUCTIONS_TEXT[9] = "Write data register
value to output"; //"Set data register
value to {0}";
//Status bar texts
var MSG_PROGRAM_STOP = "Program terminated by Stop command";
var MSG_NO_INPUT = "Error: No input";
var MSG_OVERFLOW = "Error: No input";
var MSG_OVERFLOW_RAM = "a RAM";
var MSG_OVERFLOW_DATA = "the data";
var MSG_PROGRAM_TOO_LARGE = "Error: Program loading halted because the program is too large to fit in memory";
var PC_TOO_LARGE = "Error: Execution terminated because the program counter exceeded 99";
var MSG_INVALID_CODE = "Error: Invalid code";
var MSG_PROGRAM_LOADED = "Program loaded successfully";
var MSG_MEM_READONLY = "Error: Execution terminated because memory address {0} is write-protected";
var CLEAR_IO = 1;
var CLEAR_MEMORY = 2;
var CLEAR_HIGH_MEMORY = 3;
var CLEAR_ALL = 4;
/**
* Constructor
* container - A container object for the computer
*/
function Computer(container) {
//Build the HTML DOM
//Build frame
var frame = document.createElement("TABLE");
frame.cellPadding = "0";
frame.cellSpacing = "0";
var tr0 = frame.insertRow(0);
var htd = tr0.insertCell(0);
htd.style.backgroundColor = "#000080";
htd.colSpan = "3";
htd.innerHTML = "The Visual Computer"
htd.style.textAlign = "Left";
htd.style.fontSize = "12px";
htd.style.fontFamily = "Arial";
htd.style.fontWeight = "bold";
htd.style.padding = "3px";
htd.style.color = "#FFFFFF";
//htd.style.letterSpacing = "5px";
var row0 = frame.insertRow(1);
row0.style.height = "9px";
var r0d0 = row0.insertCell(0);
r0d0.style.width = "9px";
r0d0.style.backgroundImage = "url(../Borders/computer_topleft.gif)";
var r0d1 = row0.insertCell(1);
r0d1.style.backgroundImage = "url(../Borders/computer_top.gif)";
var r0d2 = row0.insertCell(2);
r0d2.style.width = "9px";
r0d2.style.backgroundImage = "url(../Borders/computer_topright.gif)";
var row1 = frame.insertRow(2);
row1.style.height = "9px";
var r1d0 = row1.insertCell(0);
r1d0.style.width = "9px";
r1d0.style.backgroundImage = "url(../Borders/computer_left.gif)";
var frameContent = row1.insertCell(1);
var r1d2 = row1.insertCell(2);
r1d2.style.width = "9px";
r1d2.style.backgroundImage = "url(../Borders/computer_right.gif)";
var row2 = frame.insertRow(3);
row2.style.height = "9px";
var r2d0 = row2.insertCell(0);
r2d0.style.width = "9px";
r2d0.style.backgroundImage = "url(../Borders/computer_bottomleft.gif)";
var r2d1 = row2.insertCell(1);
r2d1.style.backgroundImage = "url(../Borders/computer_bottom.gif)";
var r2d2 = row2.insertCell(2);
r2d2.style.width = "9px";
r2d2.style.backgroundImage = "url(../Borders/computer_bottomright.gif)";
container.appendChild(frame);
//Build computer GUI
var table = document.createElement("TABLE");
var tr = table.insertRow(0);
tr.style.verticalAlign = "top";
var ioContainer = tr.insertCell(0);
tr.insertCell(1).style.width = COMPONENT_SPACING;
tr.cells[1].style.borderRightWidth = "1";
tr.cells[1].style.borderRightStyle = "Dashed";
tr.cells[1].style.borderRightColor = "#000000";
tr.cells[1].innerHTML = " ";
var cpuContainer = tr.insertCell(2);
tr.insertCell(3).style.width = COMPONENT_SPACING;
tr.cells[3].style.borderLeftWidth = "1";
tr.cells[3].style.borderLeftStyle = "Dashed";
tr.cells[3].style.borderLeftColor = "#000000";
tr.cells[3].style.width = "20px";
tr.cells[3].innerHTML = " ";
var memoryContainer = tr.insertCell(4);
frameContent.appendChild(table);
this.parentElement = container;
//Components names
var r_names = table.insertRow(1);
var d_names = r_names.insertCell(0);
d_names.colSpan = "5";
d_names.style.borderColor = "#000000";
d_names.style.borderStyle = "Solid";
d_names.style.borderWidth = "1px";
d_names.style.backgroundColor = "#C0FFFF"
var dnt = document.createElement("TABLE");
dnt.style.width = "100%";
dnt.style.fontSize = "14px";
dnt.style.fontWeight = "bold";
var dntr = dnt.insertRow(0);
var dntr0 = dntr.insertCell(0);
dntr0.innerHTML = "I/O Units";
dntr0.style.width = "70px";
var dntr1 = dntr.insertCell(1);
dntr1.innerHTML = "CPU";
dntr1.style.width = "120px";
var dntr2 = dntr.insertCell(2);
dntr2.innerHTML = "Memory";
dntr2.style.width = "100px";
dntr0.style.textAlign =
dntr1.style.textAlign =
dntr2.style.textAlign = "Center";
d_names.appendChild(dnt);
//Status bar
var r_status = table.insertRow(2);
var d_status = r_status.insertCell(0);
d_status.colSpan = "5";
d_status.align = "center";
var statusBar = document.createElement("INPUT");
statusBar.type = "TEXT";
statusBar.style.fontSize = "12px";
statusBar.style.fontFamily = "Arial";
statusBar.style.fontWeight = "bolder";
statusBar.readOnly = true;
statusBar.style.backgroundColor = "#D4D0C8";
statusBar.style.width = "100%";
statusBar.style.textAlign = "left";
statusBar.dir = "ltr";
d_status.appendChild(statusBar);
/**
* Sets the status bar text and color
* message - Text to display
* color - Optional. A String with the color value (e.g. "#FF0000" or "Red")
*/
setStatus = function(message, color) {
var col = "#000000";
if (color) col = color;
statusBar.value = message;
statusBar.style.color = col;
}
//Define chips (IO, Memory, CPU)
var ioChip = new IOChip(ioContainer);
var cpuChip = new CPUChip(cpuContainer);
var memoryChip = new MemoryChip(memoryContainer);
cpuChip.setComputer(this);
this.width = 700;
var that = this;
var animation = ANIMATE_DATA;
var isRunning = false;
var nextStep;
var slider, controls;
/**
* Enables/disables controls and components
*/
function enable(value) {
controls.enabled = value;
ioChip.enable(value);
cpuChip.enable(value);
memoryChip.enable(value);
}
/**
* Sets the animation type for this computer
*/
this.setAnimation = function (animationType) {
if (animationType == ANIMATE_NONE ||
animationType == ANIMATE_FLOW ||
animationType == ANIMATE_DATA) animation = animationType;
}
this.getAnimation = function () {
return animation;
}
/*
* The following private functions handles highlighting of memory and CPU cells
* (Some of them have been disabled)
*/
var lastLowMemoryHighlight = null;
function highlightLowMemory(address) {
dehighlightLowMemory();
lastLowMemoryHighlight = memoryChip.getMemoryCell(address);
if (lastLowMemoryHighlight) lastLowMemoryHighlight.highlight(true);
}
function dehighlightLowMemory() {
if (lastLowMemoryHighlight) lastLowMemoryHighlight.highlight(false);
else {
for (var i = 0; i < 50; i++) memoryChip.getMemoryCell(i).highlight(false);
}
}
var lastHighMemoryHighlight = null;
function highlightHighMemory(address) {
if (address < 50) {
highlightLowMemory(address);
return;
}
dehighlightHighMemory();
lastHighMemoryHighlight = memoryChip.getMemoryCell(address);
lastHighMemoryHighlight.highlight(true);
dehighlightHighMemory();
}
function dehighlightHighMemory() {
if (lastHighMemoryHighlight) lastHighMemoryHighlight.highlight(false);
}
var lastCPUHighlight = null;
function highlightCPU(regid) {
return;
dehighlightCPU();
lastCPUHighlight = cpuChip.getCellAt(regid);
lastCPUHighlight.highlight(true);
}
function dehighlightCPU() {
return;
if (lastCPUHighlight) lastCPUHighlight.highlight(false);
}
/*
* End of private functions for handling highlighting
*/
_oldAnimation = null;
/**
* Performs a Fetch operation
* Reads the next instruction from memory and advances PC by 1
*/
this.fetch = function(single, oldAnimation, fly) {
if (single) _oldAnimation = oldAnimation;
else _oldAnimation = undefined;
if (animation == ANIMATE_NONE && _startTime) {
if (((new Date()) - _startTime) > 10000) {
var res = confirm("Program execution time seems to be unusually long. It is possible that program has entered an infinite loop. Click 'OK' to pause program execution, or 'Cancel' to continue running it.");
if (res) {
that.stop(false, true);
}
}
}
enable(false);
var address = cpuChip.getPCValue();
if (address >= 100) {
setStatus(PC_TOO_LARGE, "#FF0000");
that.stop();
return;
}
fetches++;
var memCell = memoryChip.getMemoryCell(address);
if (memCell) {
highlightLowMemory(address);
highlightCPU(0);
if (fly && animation == ANIMATE_DATA) {
var fdata = new FlyingData(memCell, cpuChip.getCellAt(0),
memCell.getAbsoluteLeft(), memCell.getAbsoluteTop(),
cpuChip.getCellAt(0).getAbsoluteLeft(), cpuChip.getCellAt(0).getAbsoluteTop(),
parseInt(CELL_HEIGHT) * 2 + "px", CELL_HEIGHT, false, slider.getValue());
fdata.fly(fetchDone);
}
else {
if (fetches > 50) {
setTimeout(fetchDone, 0);
fetches = 0;
}
else fetchDone();
}
}
function fetchDone() {
cpuChip.getCellAt(0).setValue(memCell.getValue());
cpuChip.advancePC();
if (!isRunning && !single) enable(true);
that.decode(false, single);
}
}
/**
* Performs a READ operation
* Reads data from input stream to data register
*/
this.read = function () {
var input = ioChip.read();
if (input && input.getValue() != undefined) {
highlightCPU(1);
if (animation == ANIMATE_DATA) {
var fdata = new FlyingData(input, cpuChip.getCellAt(1),
input.getAbsoluteLeft(), input.getAbsoluteTop(),
cpuChip.getCellAt(1).getAbsoluteLeft(), cpuChip.getCellAt(1).getAbsoluteTop(),
parseInt(CELL_HEIGHT) * 2 + "px", CELL_HEIGHT, false, slider.getValue());
fdata.fly(readDone);
}
else {
readDone();
}
}
else {
setStatus(MSG_NO_INPUT, "#FF0000");
that.stop();
}
function readDone() {
cpuChip.getCellAt(1).setValue(input.getValue());
executeDone();
}
}
/**
* Performs a WRITE operation
* Writes data from data register to output stream
*/
this.write = function () {
var output = ioChip.getCurrentOutput();
if (output) {
highlightCPU(1);
output.highlight(true);
if (animation == ANIMATE_DATA) {
var fdata = new FlyingData(cpuChip.getCellAt(1), output,
cpuChip.getCellAt(1).getAbsoluteLeft(), cpuChip.getCellAt(1).getAbsoluteTop(),
output.getAbsoluteLeft(), output.getAbsoluteTop(),
parseInt(CELL_HEIGHT) * 2 + "px", CELL_HEIGHT, false, slider.getValue());
fdata.fly(writeDone);
}
else writeDone();
}
function writeDone() {
ioChip.write(cpuChip.getCellAt(1).getValue());
executeDone();
}
}
/**
* Resets the computer
* Resets I/O, program counter, registers
*/
this.reset = function() {
var anim = animation;
animation = ANIMATE_DATA;
ioChip.reset();
//memoryChip.reset();
for (var i = 0; i <= 2; i++)
cpuChip.getCellAt(i).setValue(0);
cpuChip.setInstructionLabel("
");
setStatus("", null);
dehighlightLowMemory();
dehighlightHighMemory();
dehighlightCPU();
lastLowMemoryHighlight = null;
lastHighMemoryHighlight = null;
lastCPUHighlight = null;
memoryChip.getMemoryCell(98).setValue(0);
memoryChip.getMemoryCell(98).enabled = false;
memoryChip.getMemoryCell(99).setValue(1);
memoryChip.getMemoryCell(99).enabled = false;
highlightLowMemory(0);
nextStep = that.fetch;
animation = anim;
}
/**
* Performs LOAD operation
* Loads a value from the specified memory address to the data register
* address - address to read
*/
this.load = function(address) {
var memCell = memoryChip.getMemoryCell(address);
if (memCell) {
highlightHighMemory(address);
highlightCPU(1);
if (animation == ANIMATE_DATA) {
var fdata = new FlyingData(memCell, cpuChip.getCellAt(1),
memCell.getAbsoluteLeft(), memCell.getAbsoluteTop(),
cpuChip.getCellAt(1).getAbsoluteLeft(), cpuChip.getCellAt(1).getAbsoluteTop(),
parseInt(CELL_HEIGHT) * 2 + "px", CELL_HEIGHT, false, slider.getValue());
fdata.fly(loadDone);
}
else loadDone();
}
function loadDone() {
cpuChip.getCellAt(1).setValue(memCell.getValue());
executeDone();
}
}
/**
* Performs STORE operation
* Stores a value from the data register to the specified memory address
* address - address to store the data in
*/
this.store = function(address) {
for (var i = 0; i < READ_ONLY_MEM.length; i++) {
if (READ_ONLY_MEM[i] == address) {
setStatus(MSG_MEM_READONLY.replace("{0}", address), "#FF0000");
that.stop();
return;
}
}
var memCell = memoryChip.getMemoryCell(address);
if (memCell) {
highlightHighMemory(address);
highlightCPU(1);
if (animation == ANIMATE_DATA) {
var fdata = new FlyingData(cpuChip.getCellAt(1), memCell,
cpuChip.getCellAt(1).getAbsoluteLeft(), cpuChip.getCellAt(1).getAbsoluteTop(),
memCell.getAbsoluteLeft(), memCell.getAbsoluteTop(),
parseInt(CELL_HEIGHT) * 2 + "px", CELL_HEIGHT, false, slider.getValue());
fdata.fly(storeDone);
}
else storeDone();
}
function storeDone() {
memCell.setValue(cpuChip.getCellAt(1).getValue());
executeDone();
}
}
/**
* Performs ADD operation
* Adds a value from the specified memory address to the current data register value
* address - address to read
*/
this.add = function(address) {
var memCell = memoryChip.getMemoryCell(address);
if (memCell) {
highlightHighMemory(address);
highlightCPU(1);
if (animation == ANIMATE_DATA) {
var fdata = new FlyingData(memCell, cpuChip.getCellAt(1),
memCell.getAbsoluteLeft(), memCell.getAbsoluteTop(),
cpuChip.getCellAt(1).getAbsoluteLeft(), cpuChip.getCellAt(1).getAbsoluteTop(),
parseInt(CELL_HEIGHT) * 2 + "px", CELL_HEIGHT, false, slider.getValue());
fdata.fly(addDone);
}
else addDone();
}
function addDone() {
var res = cpuChip.getCellAt(1).setValue(cpuChip.getCellAt(1).getValue() + memCell.getValue());
if (res && res.overflow) {
setStatus(MSG_OVERFLOW.replace("{0}", MSG_OVERFLOW_DATA), "#FF0000");
that.stop();
}
executeDone();
}
}
/**
* Performs SUB operation
* Subtract the value in the specified memory address from the current data register value
* address - address to read
*/
this.sub = function(address) {
var memCell = memoryChip.getMemoryCell(address);
if (memCell) {
highlightHighMemory(address);
highlightCPU(1);
if (animation == ANIMATE_DATA) {
var fdata = new FlyingData(memCell, cpuChip.getCellAt(1),
memCell.getAbsoluteLeft(), memCell.getAbsoluteTop(),
cpuChip.getCellAt(1).getAbsoluteLeft(), cpuChip.getCellAt(1).getAbsoluteTop(),
parseInt(CELL_HEIGHT) * 2 + "px", CELL_HEIGHT, false, slider.getValue());
fdata.fly(subDone);
}
else subDone();
}
function subDone() {
var res = cpuChip.getCellAt(1).setValue(cpuChip.getCellAt(1).getValue() - memCell.getValue());
if (res && res.overflow) {
setStatus(MSG_OVERFLOW.replace("{0}", MSG_OVERFLOW_DATA), "#FF0000");
that.stop();
}
executeDone();
}
}
/**
* Highlights instruction memory according to PC
* (To be called after manual change of PC)
*/
this.highlightPCAddress = function() {
highlightLowMemory(cpuChip.getCellAt(2).getValue());
nextStep = that.fetch;
if (controls) controls.setFetch();
}
/**
* Sets the next step to 'Execute'
* (To be called after manual change of instruction register)
*/
this.setExecute = function() {
nextStep = that.execute;
if (controls) controls.setExecute();
}
/**
* Performs GOTO operation
* Jumps to the specified instruction address by changing PC
*/
this.goto = function(address) {
highlightCPU(1);
highlightLowMemory(address);
cpuChip.getCellAt(2).setValue(address);
executeDone();
}
/**
* Performs GTZ operation
* If data register value is zero, jumps to the specified instruction address by changing PC
*/
this.gotoZero = function(address) {
if (cpuChip.getCellAt(1).getValue() == 0) that.goto(address);
else executeDone();
}
/**
* Performs GTP operation
* If data register value is grater than zero, jumps to the specified instruction address
* by changing PC
*/
this.gotoGreater = function(address) {
if (cpuChip.getCellAt(1).getValue() > 0) that.goto(address);
else executeDone();
}
/**
* Performs SET operation
* Sets data register to the specified value
*/
this.set = function(value) {
highlightCPU(1);
if (animation == ANIMATE_DATA)
setTimeout(setDone, 500);
else
setDone();
function setDone() {
cpuChip.getCellAt(1).setValue(value);
executeDone();
}
}
/**
* Performs STOP operation
* Stops execution of current program
*/
this.stop = function(normal, fromPanel) {
isRunning = false;
if (normal) setStatus(MSG_PROGRAM_STOP, null);
if (controls) controls.setFetch(true);
nextStep = that.fetch;
if (_oldAnimation) animation = _oldAnimation;
if (animation == ANIMATE_NONE) showValues();
enable(true);
if (controls && (!fromPanel)) controls.stopAll();
}
/**
* Decodes instruction in instruction register
*/
this.decode = function(autoExecute, single) {
var instruction = cpuChip.getCellAt(0).getValue();
var opCode = Math.floor(instruction / 100);
var imm = instruction % 100;
var callee;
var param;
var text = "";
try {
text = INSTRUCTIONS_TEXT[opCode];
} catch (e) { }
switch (opCode) {
case 1: //ADD
callee = that.add;
param = imm;
break;
case 2: //SUB
callee = that.sub;
param = imm;
break;
case 3: //LOAD
callee = that.load;
param = imm;
break;
case 4: //STORE
callee = that.store;
param = imm;
break;
case 5: //GOTO
callee = that.goto;
param = imm;
break;
case 6: //GTZ
callee = that.gotoZero;
param = imm;
break;
case 7: //GTP
callee = that.gotoGreater;
param = imm;
break;
case 8: //READ / WRITE
/*
if (imm == 0) callee = that.read;
else if (imm == 1) callee = that.write;
try {
text = text[imm];
} catch (e) { }
*/
callee = that.read;
param = imm;
break;
case 9: //SET
callee = that.write; //that.set;
param = imm;
break;
case 0: //STOP
callee = that.stop;
param = true;
break;
}
text = text.replace("{0}", imm);
if (animation != ANIMATE_NONE) {
//cpuChip.setInstructionLabel(text);
setStatus("Current instruction: " + text.replace(/
/g, ' ').replace(/\ \;/g, ' '));
}
if (autoExecute) that.execute(callee, param);
else {
nextStep = that.execute;
runNext(callee, param, single);
}
}
/**
* Performs EXECUTE operation
*/
this.execute = function(callee, param) {
if (callee) {
enable(false);
callee(param);
if (controls) controls.setFetch();
}
}
/**
* Function to call when finished executing an instruction
*/
function executeDone() {
if (!isRunning) enable(true);
highlightLowMemory(cpuChip.getCellAt(2).getValue());
nextStep = that.fetch;
if (_oldAnimation) that.setAnimation(_oldAnimation);
runNext();
}
/**
* Automatically runs the program if on "run" mode
*/
function runNext(callee, param, single) {
function _runNext() {
if (nextStep) nextStep(callee, param);
}
if (isRunning || single) {
if (animation == ANIMATE_DATA || animation == ANIMATE_FLOW) {
setTimeout(_runNext, PROGRAM_FLOW_SPEED_COEFFICIENT * (1 / slider.getValue()));
}
else _runNext();
}
}
_startTime = null;
/**
* Starts fetching and exeuting automatically according to the selected animation type
*/
this.run = function() {
isRunning = true;
_startTime = new Date();
if (nextStep == that.execute) nextStep = that.decode;
if (!nextStep) nextStep = that.fetch;
runNext();
}
/**
* Receives numeric source code (rows separated with an '|') and loads it into the
* instruction memory.
*/
this.loadProgram = function(code) {
//Prepare code for splitting - remove whitespaces and comments
code = '|' + code + '|';
code = code.replace(/\s/g, "");
code = code.replace(/\/\*([^*]|[\|]|(\*+([^*/]|[\|])))*\*+\//gm, "").
replace(/(\/\/)([^\|]*)(\|)/gm,"|");
code = code.replace(/\|{2,}/g,"|").replace(/^\|/,"").replace(/\|$/,"");
var rows = code.split("|");
//Check code validity
var validRows = 0;
for (var i = 0; i < rows.length; i++) {
numcheck = /\d{1,3}/;
if (!numcheck.test(rows[i])) {
if (rows[i] != "") {
//Invalid code
setStatus(MSG_INVALID_CODE, "#FF0000");
return;
}
}
else validRows++;
}
if (validRows > 100) {
setStatus(MSG_PROGRAM_TOO_LARGE, "#FF0000");
return;
}
//Read code to memory
memoryChip.reset();
for (var i = 0; i < rows.length; i++) {
if (rows[i] != "") {
var value = parseInt(rows[i]);
memoryChip.store(i, value);
}
}
setStatus(MSG_PROGRAM_LOADED, null);
that.reset();
}
/**
* Sets slider speed control for the computer
*/
this.setControls = function(obj) {
slider = obj.slider;
controls = obj;
}
/**
* Shows latest values of all data cells in system
* (To be used after running on "no animation")
*/
function showValues() {
if (ioChip) ioChip.showValues();
if (cpuChip) cpuChip.showValues();
if (memoryChip) memoryChip.showValues();
}
/**
* Clears a buffer and resets it
*/
this.clearBuffers = function(opcode) {
if (opcode != CLEAR_IO &&
opcode != CLEAR_MEMORY &&
opcode != CLEAR_HIGH_MEMORY &&
opcode != CLEAR_ALL) return;
if (opcode == CLEAR_IO || opcode == CLEAR_ALL) ioChip.clear();
if (opcode == CLEAR_MEMORY || opcode == CLEAR_ALL) memoryChip.reset();
if (opcode == CLEAR_HIGH_MEMORY || opcode == CLEAR_ALL) memoryChip.clearHighMemory();
that.reset();
}
//On load, reset computer
this.reset();
}