Skip to content

Instantly share code, notes, and snippets.

@XANOZOID
Created December 26, 2022 10:15
Show Gist options
  • Save XANOZOID/e71d936007fe779a2e819852ae089142 to your computer and use it in GitHub Desktop.
Save XANOZOID/e71d936007fe779a2e819852ae089142 to your computer and use it in GitHub Desktop.
interpreter for my language in progress
/// from StackOverflow
function isNumeric(str) {
if (typeof str != "string") return false // we only process strings!
return !isNaN(str) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)...
!isNaN(parseFloat(str)) // ...and ensure strings of whitespace fail
}
const types = {
STRING: "string",
NUMBER: "number",
ARRAY: "array",
PRIMITIVE: "primitive",
OBJECT: "object",
MESSAGE: "message",
BLOCK: "block"
};
// messages are objects which hold objects . . . so they hold themselves
function dObject() {
let native = {
type: types.OBJECT,
stringVal: "",
numberVal: 0,
arrayVal: [],
primitive: (ctx)=>{}, // function that operates on context
onBody: null, // is a message object . . .
doesNotUnderstand: null, // is a message object
messages: {}, // contains message blocks
};
return native;
}
function dPrimitive(fn) {
let obj = dObject();
obj.type = types.PRIMITIVE;
obj.primitive = fn;
return obj;
}
// TODO: add language level interface with this object structure
function dBlock(val) {
let obj = dObject();
obj.type = types.BLOCK;
if (val !== undefined) {
if (Array.isArray(val)) {
obj.arrayVal = val;
} else {
obj.arrayVal.push(val);
}
}
return obj;
}
function dMessage(key) {
let obj = dObject();
obj.type = types.MESSAGE;
obj.stringVal = key;
return obj;
}
function dMute() {
let obj = dObject();
obj.type = types.OBJECT;
obj.onBody = dBlock(dPrimitive((ctx)=> {
// same as "ignore this entire message-block"
ctx.stack.pop();
}));
obj.messages = {};
return obj;
}
function dBoolean(val) {
let obj = dObject();
obj.type = types.OBJECT;
if (val) {
obj.messages["if"] = dBlock(dObject());
obj.messages["else"] = dBlock(dMute());
} else {
obj.messages["if"] = dBlock(dMute());
obj.messages["else"] = dBlock(dObject());
}
// TODO: add boolean logic messages
return obj;
}
function dNumber(val) {
let obj = dObject();
obj.type = types.NUMBER;
obj.numberVal = val;
// TODO: complete the operations
obj.messages = {
"+": dBlock(dPrimitive((ctx) => {
ctx.stack.push(dNumber(val + (ctx.stack.pop()).numberVal));
})),
"-": dBlock(dPrimitive((ctx) => {
ctx.stack.push(dNumber(val - (ctx.stack.pop()).numberVal));
})),
"*": dBlock(dPrimitive((ctx) => {
ctx.stack.push(dNumber(val * (ctx.stack.pop()).numberVal));
})),
"/": dBlock(dPrimitive((ctx) => {
ctx.stack.push(dNumber(val / (ctx.stack.pop()).numberVal));
})),
"1-": dBlock(dPrimitive((ctx) => ctx.stack.push(dNumber(val - 1)) )),
"<": dBlock(dPrimitive((ctx) => {
ctx.stack.push(dBoolean(val < (ctx.stack.pop()).numberVal));
})),
">": dBlock(dPrimitive((ctx) => {
ctx.stack.push(dBoolean(val > (ctx.stack.pop()).numberVal));
})),
"=": dBlock(dPrimitive((ctx) => {
ctx.stack.push(dBoolean(val === (ctx.stack.pop()).numberVal));
})),
}
return obj;
}
// TODO: flesh out string object
function dString(val) {
let obj = dObject();
obj.type = types.STRING;
obj.stringVal = val;
obj.messages = {
};
return obj;
}
function dConsoleObject() {
let obj = dObject();
obj.messages = {
"say": dBlock(dPrimitive((ctx)=>{
var val = ctx.stack.pop();
switch (val.type) {
case types.NUMBER:
console.log(val.numberVal);
break;
case types.STRING:
console.log(val.stringVal);
break;
}
}))
};
return obj;
}
// TODO: Support prototypal inheritance
function dBaseObject() {
let obj = dObject();
// TODO: Support enclosing stack objects with special syntax
function colonObject() {
let obj = dObject();
obj.onBody = dBlock(dPrimitive((ctx)=>{
let wordBlock = ctx.stack.pop();
let name = wordBlock.arrayVal.shift();
let nameBlock = dBlock(name);
ctx.stack.push(nameBlock);
ctx.stack.push(wordBlock);
}));
return obj;
}
obj.messages = {
':': dBlock(dPrimitive((ctx)=>{
ctx.stack.push(colonObject());
})),
// TODO: Create setters based on name - IE ":name"
';': dBlock(dPrimitive((ctx)=>{
const def = ctx.stack.pop(); // block 2
const name = ctx.stack.pop(); // block 1
obj.messages[name.arrayVal[0].stringVal] = def;
})),
'self': dBlock(dPrimitive((ctx => ctx.stack.push(obj))))
};
return obj;
}
function dBase() {
let obj = dObject();
obj.type = types.OBJECT;
// push strings or numbers on to the stack. . .
obj.doesNotUnderstand = dBlock(dPrimitive((ctx)=>{
var arg1 = ctx.stack.pop();
if (arg1.type == types.MESSAGE) {
if (isNumeric(arg1.stringVal)) {
ctx.stack.push(dNumber(Number.parseFloat(arg1.stringVal)));
} else if (arg1.stringVal.startsWith('"')) {
ctx.stack.push(dString(arg1.stringVal.substr(1, arg1.stringVal.length -2)))
} else {
console.error("cannot respond to:", arg1);
}
} else {
console.error("can not interpret", arg1);
}
}));
obj.messages = {
"object": dBlock(dPrimitive((ctx)=>{
ctx.stack.push(dBaseObject());
})),
"console": dBlock(dPrimitive((ctx)=> {
ctx.stack.push(dConsoleObject());
})),
"drop": dBlock(dPrimitive((ctx)=> ctx.stack.pop())),
"dup": dBlock(dPrimitive((ctx)=>{
const n = ctx.stack.pop();
ctx.stack.push(n);
ctx.stack.push(n);
})),
"swap": dBlock(dPrimitive((ctx)=>{
const valb = ctx.stack.pop();
const vala = ctx.stack.pop();
ctx.stack.push(valb);
ctx.stack.push(vala);
})),
"over": dBlock(dPrimitive((ctx)=>{
const n2 = ctx.stack.pop();
const n1 = ctx.stack.pop();
ctx.stack.push(n1);
ctx.stack.push(n2);
ctx.stack.push(n1);
})),
"rot": dBlock(dPrimitive((ctx)=>{
const n3 = ctx.stack.pop();
const n2 = ctx.stack.pop();
const n1 = ctx.stack.pop();
ctx.stack.push(n2);
ctx.stack.push(n3);
ctx.stack.push(n1);
}))
};
return obj;
}
function stringToMessage(str) {
let i = 0;
var message = dBlock();
var stack = [message];
function getNextWord() {
var word = "";
var captured = false;
while (i < str.length && !captured) {
if (str[i].trim() == '') {
if (word.startsWith(`"`)) word += str[i];
else if (word !== "") {
captured = true;
}
}
else if (str[i] == '"') {
if (word.startsWith(`"`)) {
word += str[i];
captured = true;
} else {
word += str[i];
}
}
else if (str[i].trim() != '') {
word += str[i];
}
i ++;
}
return word;
}
var word = getNextWord();
while (word != "") {
switch (word) {
case "[":
var innerMessage = dBlock();
stack[stack.length - 1].arrayVal.push(innerMessage);
stack.push(innerMessage);
break;
case "]":
stack.pop();
break;
default:
stack[stack.length - 1].arrayVal.push(dMessage(word));
break;
}
word = getNextWord();
}
return message;
}
function run(script) {
let ctx = {
stack: [], // object stack
scope: [dBase()],
messageStack: [],
messageStackPosition: [],
};
const message = stringToMessage(script);
function scopedMessage(message, scopePosition, scopePop) {
return {
scopePosition,
message,
scopePop
};
}
ctx.messageStack.push(scopedMessage(message, 0, false));
ctx.messageStackPosition.push(0);
while (ctx.messageStack.length > 0) {
const scoped = ctx.messageStack[ctx.messageStack.length - 1];
const block = scoped.message;
const scope = scoped.scopePosition;
if (block.arrayVal.length == ctx.messageStackPosition[ctx.messageStackPosition.length - 1]) {
ctx.messageStack.pop();
ctx.messageStackPosition.pop();
if (scoped.scopePop) {
ctx.scope.pop();
}
} else {
const obj = block.arrayVal[ctx.messageStackPosition[ctx.messageStackPosition.length - 1]]
ctx.messageStackPosition[ctx.messageStackPosition.length - 1] = ctx.messageStackPosition[ctx.messageStackPosition.length - 1] + 1;
if (obj.type === types.BLOCK) {
const nextObj = ctx.stack.pop();
ctx.scope.push(nextObj);
// magic!
if (nextObj.onBody !== null) {
ctx.messageStack.push(scopedMessage(nextObj.onBody, ctx.scope.length - 1, true));
ctx.messageStackPosition.push(0);
ctx.stack.push(obj);
} else {
ctx.messageStack.push(scopedMessage(obj, ctx.scope.length - 1, true));
ctx.messageStackPosition.push(0);
}
}
else if (obj.type === types.PRIMITIVE) {
obj.primitive(ctx);
} else if (obj.type === types.MESSAGE) {
for (var i = scope; i >= 0; i--) {
if (ctx.scope[i].messages.hasOwnProperty(obj.stringVal)) {
ctx.messageStack.push(scopedMessage(ctx.scope[i].messages[obj.stringVal], i, false));
ctx.messageStackPosition.push(0);
break;
} else if (ctx.scope[i].doesNotUnderstand !== null) {
ctx.stack.push(obj);
ctx.messageStack.push(scopedMessage(ctx.scope[i].doesNotUnderstand, i, false));
ctx.messageStackPosition.push(0);
}
}
} else {
// put dat object on duh stack, mane
ctx.stack.push(obj);
}
}
}
}
run(`
object [
: [ factorial dup 1 [ < ] [
if [ dup [ 1- ] factorial [ * ] ]
] ] ;
5 factorial
]
console [ say ]
`);
@XANOZOID
Copy link
Author

This is my first attempt at creating an interpreter for my language. It's my first time writing one at all. The language is basically forth, but if it were purely message oriented.

While features are missing, the current implementation is easily extendible to support said features.

What's missing:

  • language level block interception: basically already implemented (technically used at a primitive level)
  • doesNotUnderstand: same as above
    • For the two above, only reason it hasn't been defined is because I haven't decided on the API
  • Prototypal inheritance: Should be easy to tack on, just haven't decided on how it'll work
  • No "String" interface yet
  • defining words doesn't support closures yet (should be easy though)
  • Numbers aren't completely implemented (just need to add more messages)
  • Arrays don't exist and unfortunately can't be simulated yet (but once closures are added this is false)
  • No looping constructs (should be easy)
  • No comments ( should be easy )

So there's probably around 300-400 more lines to add in total for a complete prototype, but this should suffice for toy applications.

@Capital-EX
Copy link

Is there away to show boolean values to the console?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment