diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..80eaf39 --- /dev/null +++ b/Caddyfile @@ -0,0 +1,3 @@ +aidan-network.duckdns.org { + file_server +} \ No newline at end of file diff --git a/eval.js b/eval.mjs similarity index 70% rename from eval.js rename to eval.mjs index 5a75bb7..39f4afc 100644 --- a/eval.js +++ b/eval.mjs @@ -64,22 +64,36 @@ const pair = (_, args) => { return [{type:"pair", val:{fst:args[0], snd:args[1]}}]; } -const fst = (args) => [args[0].fst]; +const fst = (_, args) => [args[0].fst]; -const snd = (args) => [args[0].snd]; +const snd = (_, args) => [args[0].snd]; + +const tru = (_, args) => [args[0]]; +const fls = (_, args) => [args[1]]; + +const eq = (_, args) => { + if (args[0].type === args[1].type && args[0].val === args[1].val) { + return [{type:"ident", val:"true"}]; + } else { + return [{type:"ident", val:"false"}]; + } +} let builtinDefn = { "+": makeEval(["int", "int"], add), "-": makeEval(["int", "int"], sub), "/": makeEval(["int", "int"], div), "*": makeEval(["int", "int"], mult), + "true": makeEval(2, tru), + "false": makeEval(2, fls), + "==": makeEval(2, eq), "typeof": makeEval(1, type), "pair": makeEval(2, pair), "fst": makeEval(["pair"], fst), "snd": makeEval(["pair"], snd) } -const addDefn = (name, sign, func) => { +export const addDefn = (name, sign, func) => { builtinDefn[name] = makeEval(sign, func); } @@ -94,16 +108,38 @@ const makeLambda = (lambda) => { } const makeObj = (elem) => { - if (elem.type === "builtin") { - let fn = builtinDefn[elem.op]; - return {type:"closure", args:[], func:fn}; - } else if (elem.type === "func") { + if (elem.type === "func") { return {type:"closure", args:[], func:makeLambda(elem)}; } else { return elem; } } +const cloneElem = (elem) => { + if (elem.type === "closure") { + let argsClone = []; + for (let i = 0; i < elem.args.length; i++) { + argsClone.push(cloneElem(elem.args[i])); + } + return {type:"closure", args:argsClone, func:elem.func}; + } else { + return elem; + } +} + +const lookupScope = (name, scope) => { + let n = scope[name]; + if (n) { + return cloneElem(n); + } + n = builtinDefn[name]; + if (n) { + return {type:"closure", args:[], func:n}; + } else { + throw "var " + n + " not in scope" + } +} + const giveArg = (closure, arg, scope) => { closure.args.push(arg); if (closure.args.length === closure.func.nargs) { @@ -115,10 +151,14 @@ const giveArg = (closure, arg, scope) => { const apply = (elem, stack) => { if (elem.type === "closure") { - let out = giveArg(elem, stack.stack.pop(), stack.scope); - applyMany(out, stack); + if (stack.stack.length > 0) { + let out = giveArg(elem, stack.stack.pop(), stack.scope); + applyMany(out, stack); + } else { + stack.stack.push(elem); + } } else if (elem.type === "ident") { - let id = stack.scope[elem.val]; + let id = lookupScope(elem.val, stack.scope); apply(id, stack); } else { stack.stack.push(elem); @@ -133,7 +173,7 @@ const applyMany = (outstack, stack) => { const pushS = (elem, stack) => { if (elem.type === "ident") { - let id = stack.scope[elem.name]; + let id = lookupScope(elem.val, stack.scope); stack.stack.push(id); } else { stack.stack.push(elem); @@ -144,7 +184,7 @@ const defn = (elem, name, stack) => { stack.scope[name] = makeObj(elem); } -const doStep = (ins, stack) => { +export const doStep = (ins, stack) => { let instruction = ins.shift(); if (instruction.type === "push") { pushS(makeObj(instruction.elem), stack); @@ -155,7 +195,8 @@ const doStep = (ins, stack) => { } } -const execRPN = (scope, ins) => { +export const execRPN = (scope, i) => { + let ins = JSON.parse(JSON.stringify(i)); let stack = {scope:scope, stack:[]}; while (ins.length > 0) { doStep(ins, stack); diff --git a/index.html b/index.html new file mode 100644 index 0000000..71caa88 --- /dev/null +++ b/index.html @@ -0,0 +1,34 @@ + +
@ me (aidanprattewart@protonmail.com) if you have any errors, with console output please.
+
+Example Programs:
+
+(swap; a b -> a b) 1 2 swap
N.B. args are in reverse order
+
+1 + 2 (v partial -> 'v partial)
Evaluate a partial
+
+
(swap; a b -> b a)
+1 2 swap
+1 2 3 (a b c -> a b c)
+1 +
will push a partially-applied function onto the stack
+1 + (partial -> 2 partial)
+pair
+fst
or snd
+
+
\ No newline at end of file
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..04d5582
--- /dev/null
+++ b/main.js
@@ -0,0 +1,50 @@
+import {execRPN} from './eval.mjs';
+import {parseExprs} from './parse.mjs';
+import {tokenize} from './token.mjs';
+
+const inbox = document.getElementById("inbox")
+const outbox = document.getElementById("outbox")
+const submit = document.getElementById("submit")
+
+const show = (elem) => {
+ if (elem.type === "int") {
+ return elem.val
+ } else if (elem.type === "pair") {
+ return "{" + show(elem.val.fst) + ", " + show(elem.val.snd) + "}"
+ } else if (elem.type === "closure") {
+ return "(args: {" + prettyprint(elem.args) + "} of " + elem.func.nargs + ")"
+ } else if (elem.type === "string") {
+ return elem.val
+ }
+}
+
+const prettyprint = (out) => {
+ let str = "";
+ for (let i = 0; i < out.length; i++) {
+ str += show(out[i]);
+ if (i < out.length - 1) {
+ str += " ";
+ }
+ }
+ return str;
+}
+
+submit.onclick = (event) => {
+ const input = inbox.value;
+ let toks = tokenize(input);
+ if (!toks) {
+ outbox.innerHTML = "could not parse input: " + input;
+ return;
+ }
+ let ast = parseExprs(toks);
+ if (!ast.parsed) {
+ outbox.innerHTML = "incorrect syntax somewhere";
+ return;
+ }
+ let out = execRPN({}, ast.parsed);
+ if (!out) {
+ outbox.innerHTML = "failed to execute";
+ return;
+ }
+ outbox.innerHTML = prettyprint(out.stack);
+}
\ No newline at end of file
diff --git a/parse.js b/parse.mjs
similarity index 86%
rename from parse.js
rename to parse.mjs
index 1a039ce..97b9196 100644
--- a/parse.js
+++ b/parse.mjs
@@ -41,17 +41,6 @@ EXPORTED FUNCTIONS:
parseExprs - takes in tokenstream, outputs AST
*/
-const builtin = [
- "+",
- "-",
- "*",
- "/",
- "typeof",
- "pair",
- "fst",
- "snd"
-];
-
/* make parser safe */
const attempt = (parser) => (stream) => {
let streamclone = [...stream];
@@ -98,23 +87,6 @@ const many = (parser) => (stream) => {
return {parsed:parsed, stream:stream};
}
-/* takes in stream, outputs {parsed, stream} */
-const parseBuiltin = (stream) => {
- let e = stream[0];
- if (e === undefined) {
- return {parsed:null, stream:stream};
- }
- if (e.type !== "ident") {
- return {parsed:null, stream:stream};
- }
- if (builtin.includes(e.name)) {
- stream.shift();
- return {parsed:{type:"builtin", val:e.name}, stream:stream};
- } else {
- return {parsed:null, stream:stream};
- }
-}
-
/* takes in stream, outputs parsed item or null */
const parseIdent = (stream) => {
let e = stream[0];
@@ -173,10 +145,24 @@ const parseName = (stream) => {
return {parsed:id.parsed.val, stream:syn.stream};
}
+const parseString = (stream) => {
+
+ let syn = attempt(parseSyntax("\""))(stream);
+ if (syn.parsed === null) {
+ return {parsed:null, stream:syn.stream};
+ }
+ let id = parseIdent(syn.stream);
+ if (id.parsed === null) {
+ return {parsed:null, stream:id.stream};
+ }
+ return {parsed:{type:"string", val:id.parsed.val}, stream:id.stream};
+
+ //return {parsed:null, stream:stream};
+}
+
/* takes in stream, outputs parsed item or null - FAILABLE */
const parsePush = (stream) => {
let syn = attempt(parseSyntax("'"))(stream);
- console.log(syn);
if (syn.parsed === null) {
return {parsed:null, stream:syn.stream};
}
@@ -203,12 +189,12 @@ const parseLambda = (stream) => {
if (name.parsed === null) {
return {parsed:func, stream:body.stream};
} else {
- return {type:"defn", ident:name.parsed, defn:func};
+ return {parsed:{type:"defn", ident:name.parsed, defn:func}, stream:body.stream};
}
}
/* takes in stream, outputs parsed item or null */
-const parseExpr = or(parseBuiltin, or(parseIdent, or(parseInteger, or(parsePush, attempt(parens(parseLambda))))));
+const parseExpr = or(parseString, or(parseIdent, or(parseInteger, or(parsePush, attempt(parens(parseLambda))))));
/* takes in stream, outputs parsed items */
export const parseExprs = many(parseExpr);
\ No newline at end of file
diff --git a/token.js b/token.mjs
similarity index 68%
rename from token.js
rename to token.mjs
index 80914bb..7072776 100644
--- a/token.js
+++ b/token.mjs
@@ -1,7 +1,7 @@
-function tokenize(input) {
- var i;
- var inputWithAddedSpaces = "";
- const syntax = /[`;()]/; // -> is a special syntax (because it uses 2 characters) so is coded separately
+export function tokenize(input) {
+ let i;
+ let inputWithAddedSpaces = "";
+ const syntax = /['";()]/; // -> is a special syntax (because it uses 2 characters) so is coded separately
for(i = 0; i