Last active
December 3, 2017 21:08
-
-
Save Heimdell/f1fce236c6cb4c1f187749c799db1bb8 to your computer and use it in GitHub Desktop.
Forth-machine
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.exports.Struct = (thunk) => { | |
let klass = null | |
return class { | |
constructor(args) { | |
Object.assign(this, args) | |
} | |
static of(args) { | |
if (!klass) { | |
klass = thunk() | |
} | |
return new klass(args) | |
} | |
with(delta) { | |
return Object.assign(this, delta) | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let { Struct } = require("./adt.js") | |
let { defined, log } = require("./util.js") | |
/* | |
* FORTH-Machine | |
*/ | |
class Machine extends Struct(() => Machine) { | |
constructor(args) { | |
let { | |
data, // data stack, place for all args & results | |
program, // string with "remaining program"" | |
words, // linked list of named wordGroups | |
wordGroups, // linked list of named definition groups | |
compilation // true if compiling, false if evaluating | |
} = args | |
super(args) | |
} | |
/* | |
* Machine.addWord(Word) : Nothing | |
* | |
* Adds a word to the machine. | |
*/ | |
addWord(word) { | |
this.words = addWord(this.words, word) | |
} | |
/* | |
* Machine.findWord(Name) : Word | |
* | |
* Returns a pointer to the word. | |
* | |
* If word cannot be found, its an error. | |
*/ | |
findWord(name) { | |
for | |
( var i = this.words | |
; i != empty && i.name != name | |
; i = i.previous | |
) ; | |
if (i == empty) { | |
let num = Number(name) | |
if (!isNaN(num)) { | |
return word(num, function() { | |
this.data.push(num) | |
}) | |
} | |
throw `no such word - ${name}` | |
} | |
return i | |
} | |
/* | |
* Machine.forget(Name) : Nothing | |
* | |
* Removes word of Name and all words declared after it. | |
* | |
* Notice: we cannot use findWord, because we must delete wordGroups too. | |
* | |
* (Name is taken from FORTH) | |
*/ | |
forget(name) { | |
var prev = '' | |
for | |
( var word = this.words | |
; word != empty && prev != name | |
; word = word.previous | |
) | |
{ | |
if (word.name == name && word.isProtected) { | |
throw `cannot remove protected word - ${name}` | |
} | |
if (word.isProtected) { | |
throw `cannot remove word ${name} below protected word ${word.name}` | |
} | |
if (this.wordGroups.word == word) { | |
this.wordGroups = this.wordGroups.previous | |
} | |
prev = word.name | |
} | |
if (word == empty && prev != name) { | |
throw `no such word - ${name}` | |
} | |
this.words = word | |
} | |
/* | |
* Machine.nameWordGroup(Name) : Nothing | |
* | |
* Sets name for group of words declared after last word group was closed. | |
*/ | |
nameWordGroup(name) { | |
this.wordGroups = WordGroup.of({ | |
name, | |
word: this.words, | |
previous: this.wordGroups | |
}) | |
} | |
/* | |
* Machine.findWordGroup(Name) : Nothing | |
* | |
* Returns a pointer to the word group with Name. | |
* | |
* If there is no such word group, its an error. | |
*/ | |
findWordGroup(name) { | |
for | |
( var def = this.wordGroups | |
; def != empty && def.name != name | |
; def = def.previous | |
) ; | |
if (def == empty) { | |
throw `no such wordGroups - ${name}` | |
} | |
return def | |
} | |
/* | |
* Machine.definitions(Name) : Nothing | |
* | |
* Forgets all words declared after given word group. | |
* | |
* If there is no such word group, its an error. | |
* | |
* (Name is taken from FORTH) | |
*/ | |
definitions(name) { | |
this.wordGroups = this.findWordGroup(name) | |
this.words = this.wordGroups.word | |
} | |
/* | |
* Construct array of {[group]name, words} from machine state. | |
*/ | |
parseWordGroups() { | |
let groups = [{name: "local", words: []}] | |
let group = this.wordGroups | |
for | |
( let i = this.words | |
; i != empty | |
; i = i.previous | |
) | |
{ | |
if (group != empty && i == group.word) { | |
groups.unshift({name: group.name, words: []}) | |
group = group.previous | |
} | |
groups[0].words.push(i.name) | |
} | |
groups.reverse() | |
return groups | |
} | |
/* | |
* Pretty-print machine state into the console. | |
*/ | |
dump() { | |
let data = show(this.data) | |
let program = show(this.program) | |
let wordGroups = this.parseWordGroups() | |
log("DATA\t:", data) | |
log(this.compilation? "COMPILE\t:" : "EVAL\t:", program) | |
log("--------") | |
wordGroups.forEach(group => { | |
log(group.name.slice(0,7) + "\t:", ...group.words) | |
}) | |
log() | |
} | |
loop() { | |
while (!this.halt) { | |
this.next() | |
} | |
} | |
next() { | |
this.word() | |
this.dispatch() | |
this.dump() | |
} | |
word() { | |
this.program = this.program.trimLeft() | |
let words = this.program.split(/\s+/, 1) | |
let word | |
if (words.length) { | |
word = words[0] | |
this.program = this.program.slice(word.length) | |
} else { | |
word = this.program | |
this.program = '' | |
} | |
this.data.push(word) | |
} | |
dispatch() { | |
let name = this.data.pop() | |
let word = this.findWord(name) | |
if (word.immediate || !this.compilation) { | |
word.body.apply(this) | |
} else { | |
let acc = this.data.pop() | |
acc.push(word.body) | |
this.data.push(acc) | |
} | |
} | |
compile() { | |
this.compilation = true | |
} | |
evaluate() { | |
this.compilation = false | |
} | |
} | |
/* | |
* Forth-machine state. | |
*/ | |
// let Machine = struct("Machine", | |
// "data", // data stack, place for all args & results | |
// "program", // string with "remaining program"" | |
// "words", // linked list of named wordGroups | |
// "wordGroups", // linked list of named definition groups | |
// "compilation" // true if compiling, false if evaluating | |
// ) | |
/* | |
* Function definition. Its called "word" in FORTH. | |
*/ | |
class Word extends Struct(() => Word) { | |
constructor(args) { | |
let { | |
name, // word name | |
body, // function to be invoked | |
immediate, // that means, function will be invoked even in compilation mode | |
previous, // link to the previously defined function | |
isProtected // if true, cannot be removed with FORGET | |
} = args | |
super(args) | |
} | |
} | |
/* | |
* Group of words. | |
*/ | |
class WordGroup extends Struct(() => WordGroup) { | |
constructor(args) { | |
let { | |
name, // group name | |
word, // link to the la[te]st word in group | |
previous // link to previous group | |
} = args | |
super(args) | |
} | |
} | |
/* | |
* Empty list type. | |
*/ | |
class Empty extends Struct(() => Empty) {} | |
/* | |
* Empty list singleton. | |
*/ | |
let empty = Empty.of({}) | |
/* | |
* addEntry(Words, Word) : Words | |
* | |
* Adds a word to a list of words. | |
*/ | |
function addWord(previous, word) { | |
return word.with({previous}) | |
} | |
/* | |
* words(...Word) : Words | |
* | |
* Pack a list of words into the vocabulary. | |
*/ | |
function words(...words) { | |
return words.reduce(addWord, empty) | |
} | |
function word(...args) { | |
switch (args.length) { | |
case 1: { | |
let func = args[0] | |
return Word.of({name: func.name, body: func}) | |
} | |
case 2: { | |
let name = args[0] | |
let func = args[1] | |
return Word.of({name: name, body: func}) | |
} | |
case 3: { | |
let name = args[0] | |
let func = args[1] | |
let immediate = args[2].immediate | |
return Word.of({name: name, body: func, immediate}) | |
} | |
default: | |
throw "function `word` must be called with 1, 2 or 3 params" | |
} | |
} | |
/* | |
* Custom pretty-printer. | |
*/ | |
function show(data) { | |
switch (typeof data) { | |
case "string": return data | |
case "function": return data.name || "<func>" | |
case "undefined": return "undefined" | |
case "object": | |
switch (data.constructor) { | |
case Array: | |
return "[" + data.map(show).join(" ") + "]" | |
case Word: | |
return data.name | |
+ (data.immediate? " [I]" : "") | |
+ ": " + show(data.body) + " " | |
+ show(data.previous) | |
case WordGroup: | |
return data.name + " -> " + data.word.name + " " + show(data.previous) | |
case Empty: | |
return "." | |
default: | |
return data.toString() | |
} | |
default: | |
return data.toString() | |
} | |
} | |
function squash(funcs) { | |
return function() { | |
funcs.forEach(func => func.apply(this)) | |
} | |
} | |
let stdlib = words( | |
word(function dup() { | |
let x = this.data.pop() | |
this.data.push(x, x) | |
}), | |
word(function swap(params) { | |
let x = this.data.pop() | |
let y = this.data.pop() | |
this.data.push(x, y) | |
}), | |
word(function() { | |
this.halt = true | |
}), | |
word(":", function () { | |
this.word() | |
this.compile() | |
this.data.push([]) | |
}), | |
word(";", function() { | |
let body = this.data.pop() | |
let name = this.data.pop() | |
this.addWord(word(name, squash(body))) | |
this.evaluate() | |
}, {immediate: true}), | |
word("[", function () { | |
this.evaluate() | |
}, {immediate: true}), | |
word("]", function () { | |
this.compile() | |
let name = this.data.pop() | |
let func = this.findWord(name).body | |
let acc = this.data.pop() | |
acc.push(func) | |
this.data.push(acc) | |
}), | |
word("+", function () { | |
let x = this.data.pop() | |
let y = this.data.pop() | |
this.data.push(x + y) | |
}) | |
) | |
/* | |
* FORTH-Machine instance. | |
*/ | |
var forth = Machine.of({ | |
data: [], | |
program: ": lol [ 1 3 + ] 2 ; lol lol", | |
words: stdlib, | |
wordGroups: WordGroup.of({name: "forth", word: stdlib, previous: empty}), | |
compilation: false | |
}) | |
forth.dump() | |
forth.loop() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module.exports.defined = x => x !== undefined | |
module.exports.log = console.log |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment