Skip to content

Commit

Permalink
Add ReadLn (for issue #6).
Browse files Browse the repository at this point in the history
  • Loading branch information
lkesteloot committed Apr 14, 2018
1 parent 380ca7f commit 55f2c59
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 15 deletions.
7 changes: 7 additions & 0 deletions IDE.js
Expand Up @@ -327,6 +327,13 @@ define(["Stream", "Token", "Lexer", "CommentStripper", "Parser",
self.screen.print(line);
self.screen.newLine();
});
machine.setInputCallback(function (callback) {
self.screen.addCursor();
self._setInputMode(INPUT_STRING, function (line) {
self._setInputMode(INPUT_RUNNING);
callback(line);
});
});

this._setInputMode(INPUT_RUNNING);
machine.run();
Expand Down
91 changes: 77 additions & 14 deletions Machine.js
Expand Up @@ -54,42 +54,76 @@ define(["inst", "PascalError", "utils"], function (inst, PascalError, utils) {
// line of output, and the line is the only parameter.
this.outputCallback = null;

// Callback that gets a line of input from the user. It is called with
// a function that will be called with the line of input.
this.inputCallback = null;

// The number of ms that the program is expecting us to delay now.
this.pendingDelay = 0;

// Control object for native functions to manipulate this machine.
var self = this;
this.control = {
// Stop the machine.
stop: function () {
self.stopProgram();
},
// Suspend the machine (stop processing instructions).
suspend: function () {
self.state = Machine.STATE_SUSPENDED;
},
// Resume the machine (un-suspend).
resume: function () {
self.resume();
},
// Wait "ms" milliseconds.
delay: function (ms) {
self.pendingDelay = ms;
},
// Write the line to the output.
writeln: function (line) {
if (self.outputCallback !== null) {
self.outputCallback(line);
}
},
// Read a line from the user. The parameter is a function that
// will be called with the line. The machine must first be suspended.
readln: function (callback) {
if (self.inputCallback !== null) {
self.inputCallback(callback);
} else {
callback("no input");
}
},
// Read a value from memory.
readDstore: function (address) {
return self.dstore[address];
},
// Write a value to memory.
writeDstore: function (address, value) {
self.dstore[address] = value;
},
// Push a value onto the stack.
push: function (value) {
self._push(value);
},
// Allocate some memory from the heap.
malloc: function (size) {
return self._malloc(size);
},
// Free some memory from the heap.
free: function (p) {
return self._free(p);
},
// Check whether a key has been pressed.
keyPressed: function () {
if (self.keyboard) {
return self.keyboard.keyPressed();
} else {
return false;
}
},
// Read a key from the keyboard, or 0 for none.
readKey: function () {
if (self.keyboard) {
return self.keyboard.readKey();
Expand All @@ -103,28 +137,41 @@ define(["inst", "PascalError", "utils"], function (inst, PascalError, utils) {
// Various machine states.
Machine.STATE_STOPPED = 0;
Machine.STATE_RUNNING = 1;
Machine.STATE_SUSPENDED = 2;

// Run the bytecode.
Machine.prototype.run = function () {
// Reset the machine.
this._reset();

// Start the machine.
this.state = Machine.STATE_RUNNING;
this.startTime = new Date().getTime();

this.resume();
};

// Continue running the program.
Machine.prototype.resume = function () {
// Run the program.
this.state = Machine.STATE_RUNNING;
this._dumpState();

// Define a function that will run a finite number of instructions,
// then temporarily return control to the browser for display update
// and input processing.
var self = this;
var stepAndTimeout = function () {
self.step(100000);

// If we're still running, schedule another brief run.
if (self.state === Machine.STATE_RUNNING) {
var delay = self.pendingDelay;
self.pendingDelay = 0;
setTimeout(stepAndTimeout, delay);
}
};

// Kick it off.
stepAndTimeout();
};

Expand All @@ -133,18 +180,23 @@ define(["inst", "PascalError", "utils"], function (inst, PascalError, utils) {
for (var i = 0; i < count && this.state === Machine.STATE_RUNNING &&
this.pendingDelay === 0; i++) {

try {
this._executeInstruction();
} catch (e) {
if (e instanceof PascalError) {
console.log(e.getMessage());
}
console.log(e.stack);
console.log(this._getState());
this.stopProgram();
this.stepOnce();
}
};

// Step one instruction. The machine *must* be running.
Machine.prototype.stepOnce = function () {
try {
this._executeInstruction();
} catch (e) {
if (e instanceof PascalError) {
console.log(e.getMessage());
}
this._dumpState();
console.log(e.stack);
console.log(this._getState());
this.stopProgram();
}
this._dumpState();
};

// Set a callback for debugging. The callback is called with a string that should
Expand All @@ -165,6 +217,12 @@ define(["inst", "PascalError", "utils"], function (inst, PascalError, utils) {
this.outputCallback = outputCallback;
};

// Set a callback for standard input. The callback is called with a function
// that takes the line that was entered.
Machine.prototype.setInputCallback = function (inputCallback) {
this.inputCallback = inputCallback;
};

// Dump the state of the machine to the debug callback.
Machine.prototype._dumpState = function () {
if (this.debugCallback != null) {
Expand Down Expand Up @@ -313,11 +371,16 @@ define(["inst", "PascalError", "utils"], function (inst, PascalError, utils) {
// control this machine.
parameters.unshift(this.control);

// Call the built-in function.
var returnValue = nativeProcedure.fn.apply(null, parameters);

// Push result if we're a function.
if (!nativeProcedure.returnType.isSimpleType(inst.P)) {
this._push(returnValue);
// See if we're still running. The function might have stopped
// or suspended us.
if (this.state === Machine.STATE_RUNNING) {
// Push result if we're a function.
if (!nativeProcedure.returnType.isSimpleType(inst.P)) {
this._push(returnValue);
}
}
break;
case inst.ENT:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -8,4 +8,6 @@ See the [full write-up](http://www.teamten.com/lawrence/projects/turbo_pascal_co
To run it locally, run the "GO" script to start a web server and go to
http://localhost:8000

Go to http://localhost:8000/unit.html to run the unit tests.

See the LICENSE file for the BSD 2-clause license.
13 changes: 13 additions & 0 deletions builtin.js
Expand Up @@ -76,6 +76,19 @@ define(["Node", "Token", "inst"], function (Node, Token, inst) {
}
ctl.writeln(elements.join(" "));
});
symbolTable.addNativeFunction("ReadLn", Node.stringType, [], function (ctl) {
// Suspend the machine so that the browser can get keys to us.
ctl.suspend();

// Ask the IDE to read a line for us.
ctl.readln(function (line) {
ctl.push(line);
ctl.resume();
});

// We're a function, so we should return something, but we've
// suspended the machine, so it doesn't matter.
});
symbolTable.addNativeFunction("Halt", Node.voidType, [], function (ctl) {
// Halt VM.
ctl.stop();
Expand Down
2 changes: 1 addition & 1 deletion inst.js
Expand Up @@ -171,7 +171,7 @@ define(["PascalError"], function (PascalError) {
return this.opcodeToName[opcode] + " " + operand1 + " " + operand2;
},

// Converts a type code like inst.I to "integer", or null if not valid.
// Converts a type code like inst.I to "integer", or throw if not valid.
typeCodeToName: function (typeCode) {
switch (typeCode) {
case this.A:
Expand Down
8 changes: 8 additions & 0 deletions unit.html
Expand Up @@ -375,6 +375,14 @@
end.
</script>

<script id="readln" type="text/pascal" data-expected="no input">
program TestProgram;
var s : String;
begin
WriteLn(ReadLn);
end.
</script>

<style>
body {
font-family: sans-serif;
Expand Down

0 comments on commit 55f2c59

Please sign in to comment.