mirror of
https://github.com/Baidicoot/rpncalc-v4
synced 2024-12-04 15:29:54 +00:00
testing new engine
This commit is contained in:
parent
f6e5f70d03
commit
5f7a6f0516
@ -1,3 +1,9 @@
|
|||||||
aidan-network.duckdns.org {
|
aidan-network.duckdns.org {
|
||||||
|
root ./old/
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
localhost:8443 {
|
||||||
|
root ./new/
|
||||||
file_server
|
file_server
|
||||||
}
|
}
|
107
new/builtin.mjs
Normal file
107
new/builtin.mjs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import {addDefn, addRPNASTDefn, makeFn, cloneElem} from './shiny.mjs';
|
||||||
|
import {parseExprs} from './parse.mjs';
|
||||||
|
import {tokenize} from './token.mjs';
|
||||||
|
|
||||||
|
const addRPNDefn = (name, def) => {
|
||||||
|
let toks = tokenize(def);
|
||||||
|
if (!toks) {
|
||||||
|
throw 'could not load builtin'
|
||||||
|
}
|
||||||
|
let ast = parseExprs(toks);
|
||||||
|
if (!ast.parsed) {
|
||||||
|
throw 'could not load builtin'
|
||||||
|
}
|
||||||
|
addRPNASTDefn(name, ast.parsed[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const add = (_, args) => {
|
||||||
|
return [{type:"int", val:args[0] + args[1]}];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sub = (_, args) => {
|
||||||
|
return [{type:"int", val:args[1] - args[0]}];
|
||||||
|
}
|
||||||
|
|
||||||
|
const div = (_, args) => {
|
||||||
|
return [{type:"int", val:args[1] / args[0]}];
|
||||||
|
}
|
||||||
|
|
||||||
|
const mult = (_, args) => {
|
||||||
|
return [{type:"int", val:args[0] * args[1]}];
|
||||||
|
}
|
||||||
|
|
||||||
|
const pow = (_, args) => {
|
||||||
|
return [{type:"int", val:Math.pow(args[1], args[0])}];
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = (_, args) => {
|
||||||
|
return [{type:"int", val:Math.sqrt(args[0])}];
|
||||||
|
}
|
||||||
|
|
||||||
|
const type = (_, args) => {
|
||||||
|
return [{type:"type", val:args[0].type}];
|
||||||
|
}
|
||||||
|
|
||||||
|
const pair = (_, args) => {
|
||||||
|
return [{type:"pair", val:{fst:args[1], snd:args[0]}}];
|
||||||
|
}
|
||||||
|
|
||||||
|
const fst = (_, args) => [args[0].fst];
|
||||||
|
|
||||||
|
const snd = (_, args) => [args[0].snd];
|
||||||
|
|
||||||
|
const eq = (_, args) => {
|
||||||
|
if (args[0].type === args[1].type && args[0].val === args[1].val) {
|
||||||
|
console.log(args[0], args[1])
|
||||||
|
return [{type:"ident", val:"true"}];
|
||||||
|
} else {
|
||||||
|
return [{type:"ident", val:"false"}];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tuple = (_, args) => {
|
||||||
|
return [makeFn(args[0], (_, args) => {return [{type:"tuple", val:args}]})];
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = (_, args) => {
|
||||||
|
return [args[1][args[0]]];
|
||||||
|
}
|
||||||
|
|
||||||
|
const len = (_, args) => {
|
||||||
|
return [{type:"int", val:args[0].length}];
|
||||||
|
}
|
||||||
|
|
||||||
|
const coerce = (_, args) => {
|
||||||
|
if (args[0].type === "type") {
|
||||||
|
let o = cloneElem(args[1]);
|
||||||
|
o.type = args[0].val;
|
||||||
|
return o;
|
||||||
|
} else {
|
||||||
|
throw 'typeerror'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addDefn("+", ["int", "int"], add);
|
||||||
|
addDefn("-", ["int", "int"], sub);
|
||||||
|
addDefn("/", ["int", "int"], div);
|
||||||
|
addDefn("*", ["int", "int"], mult);
|
||||||
|
addDefn("^", ["int", "int"], pow);
|
||||||
|
addDefn("sqrt", ["int"], root);
|
||||||
|
addDefn("==", 2, eq);
|
||||||
|
addDefn("typeof", 1, type);
|
||||||
|
addDefn("pair", 2, pair);
|
||||||
|
addDefn("fst", ["pair"], fst);
|
||||||
|
addDefn("snd", ["pair"], snd);
|
||||||
|
addDefn("tuple", ["int"], tuple);
|
||||||
|
addDefn("!!", ["int", "tuple"], index);
|
||||||
|
addDefn("len", ["tuple"], len);
|
||||||
|
addDefn("unsafeCoerce", 2, coerce);
|
||||||
|
//addRPNDefn("unit", "(-> 0 arr)");
|
||||||
|
//addRPNDefn("mono", "(-> 1 arr)");
|
||||||
|
//addRPNDefn("unwrap", "(-> 0 !!)");
|
||||||
|
addRPNDefn("true", "(a b -> a)");
|
||||||
|
addRPNDefn("false", "(a b -> b)");
|
||||||
|
addRPNDefn("stop", "\"stop");
|
||||||
|
//addRPNDefn("id", "(a -> a)");
|
||||||
|
addRPNDefn("inv", "(x -> 1 x /)");
|
||||||
|
addRPNDefn("fold", "(x acc fn -> acc '(-> x acc fn 'fn fold) 'x \"stop ==)");
|
6
new/index.html
Normal file
6
new/index.html
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<html>
|
||||||
|
<textarea type="text" id="inbox"></textarea>
|
||||||
|
<button id="submit">execute</button>
|
||||||
|
<pre id="outbox"></pre>
|
||||||
|
<script src="./main.js" type="module"></script>
|
||||||
|
</html>
|
58
new/main.js
Normal file
58
new/main.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import {execRPN} from './shiny.mjs';
|
||||||
|
import {parseExprs} from './parse.mjs';
|
||||||
|
import {tokenize} from './token.mjs';
|
||||||
|
//import './builtin.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 "(needs: " + elem.val.args.join(", ") + ")"
|
||||||
|
} else if (elem.type === "type") {
|
||||||
|
return elem.val
|
||||||
|
} else if (elem.type === "array") {
|
||||||
|
return "[" + prettyprint(elem.val) + "]"
|
||||||
|
} else if (elem.val) {
|
||||||
|
return "(" + elem.type + ": " + elem.val + ")"
|
||||||
|
} else {
|
||||||
|
return elem.type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
//console.log(out.stacks);
|
||||||
|
outbox.innerHTML = prettyprint(out.stacks[0]);
|
||||||
|
}
|
145
new/shiny.mjs
Normal file
145
new/shiny.mjs
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
EVAL.js
|
||||||
|
-------
|
||||||
|
|
||||||
|
reads exprs from AST and executes them
|
||||||
|
|
||||||
|
types of elem:
|
||||||
|
all have type
|
||||||
|
all have val
|
||||||
|
|
||||||
|
TYPE VAL
|
||||||
|
"closure" {scope:{}, args:['x', 'y'], defn:<function-like>}
|
||||||
|
|
||||||
|
<function-like>:
|
||||||
|
{type:"ins", ins:[]}
|
||||||
|
{type:"builtin", fn:(scope) => {stack}}
|
||||||
|
|
||||||
|
exported functionality:
|
||||||
|
defnOp(name, function) - define a built-in operator
|
||||||
|
evalRPN(scope, ast)
|
||||||
|
*/
|
||||||
|
|
||||||
|
let builtins = {};
|
||||||
|
|
||||||
|
export const defnOp = (name, data) => {
|
||||||
|
builtins[name] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lookup = (name, scope) => {
|
||||||
|
let n = scope[name];
|
||||||
|
if (n) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
n = builtins[n];
|
||||||
|
if (n) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
console.log(scope);
|
||||||
|
throw '"' + name + '" not in scope'
|
||||||
|
}
|
||||||
|
|
||||||
|
const extend = (scope, name, elems) => {
|
||||||
|
let o = Object.create(scope);
|
||||||
|
o[name] = elems;
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
|
||||||
|
const runFn = (defn, state) => {
|
||||||
|
if (defn.type === "ins") {
|
||||||
|
state.calls.push(defn.ins);
|
||||||
|
state.stacks.push([]);
|
||||||
|
} else if (defn.type === "builtin") {
|
||||||
|
let scope = state.scopes.pop();
|
||||||
|
let out = defn.fn(scope);
|
||||||
|
state.calls.push([]);
|
||||||
|
state.stacks.push(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const giveArg = (arg, elem) => {
|
||||||
|
let argN = elem.val.args[elem.val.args.length-1];
|
||||||
|
let newscope = extend(elem.val.scope, argN, [arg]);
|
||||||
|
return {type:elem.type, val:{scope:newscope, args:elem.val.args.slice(0,-1), defn:elem.val.defn}};
|
||||||
|
}
|
||||||
|
|
||||||
|
const apply = (elem, state) => {
|
||||||
|
if (elem.type === "closure") {
|
||||||
|
if (elem.val.args.length === 0) {
|
||||||
|
state.scopes.push(elem.val.scope);
|
||||||
|
runFn(elem.val.defn, state);
|
||||||
|
} else if (state.stacks[state.stacks.length-1].length > 0) {
|
||||||
|
apply(giveArg(state.stacks[state.stacks.length-1].pop(), elem), state);
|
||||||
|
} else {
|
||||||
|
state.stacks[state.stacks.length-1].push(elem);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.stacks[state.stacks.length-1].push(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const applyMany = (elems, state) => {
|
||||||
|
for (let i = 0; i < elems.length; i++) {
|
||||||
|
apply(elems[i], state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const makeStackElems = (ins, state) => {
|
||||||
|
if (ins.type === "push") {
|
||||||
|
throw 'nested push error'
|
||||||
|
} else if (ins.type === "ident") {
|
||||||
|
return lookup(ins.val, state.scopes[state.scopes.length-1]);
|
||||||
|
} else if (ins.type === "func") {
|
||||||
|
return [{type:"closure", val:{scope:state.scopes[state.scopes.length-1], args:ins.args, defn:{type:"ins", ins:ins.body}}}];
|
||||||
|
} else {
|
||||||
|
return [ins];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const doIns = (ins, state) => {
|
||||||
|
if (ins.type === "push") {
|
||||||
|
state.stacks[state.stacks.length-1] = state.stacks[state.stacks.length-1].concat(makeStackElems(ins.elem, state));
|
||||||
|
} else if (ins.type === "defn") {
|
||||||
|
// initialize definition scope
|
||||||
|
let defscope = Object.create(state.scopes[state.scopes.length-1]);
|
||||||
|
// parse into closure for definition's scope (to allow for recursion)
|
||||||
|
let fnForm = {type:"closure", val:{scope:defscope, args:[], defn:{type:"ins", ins:ins.defn}}};
|
||||||
|
// add fnForm to definition scope
|
||||||
|
defscope[ins.ident] = [fnForm];
|
||||||
|
// evaluate fnForm
|
||||||
|
let out = execRPN(defscope, ins.defn);
|
||||||
|
defscope[ins.ident] = out.stacks[0];
|
||||||
|
state.scopes[state.scopes.length-1] = defscope;
|
||||||
|
} else {
|
||||||
|
applyMany(makeStackElems(ins, state), state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const step = (state) => {
|
||||||
|
if (state.calls[state.calls.length-1].length === 0) {
|
||||||
|
if (state.calls.length === 1) {
|
||||||
|
throw 'finished execution'
|
||||||
|
}
|
||||||
|
if (state.stacks.length < 2) {
|
||||||
|
throw 'nothing to return'
|
||||||
|
}
|
||||||
|
state.calls.pop();
|
||||||
|
let out = state.stacks.pop();
|
||||||
|
applyMany(out, state);
|
||||||
|
} else {
|
||||||
|
let ins = state.calls[state.calls.length-1][0];
|
||||||
|
state.calls[state.calls.length-1] = state.calls[state.calls.length-1].slice(1);
|
||||||
|
doIns(ins, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const execRPN = (scope, ins) => {
|
||||||
|
let state = {scopes:[scope], stacks:[[]], calls:[ins]};
|
||||||
|
while (state.calls[0].length > 0 || state.calls.length > 1) {
|
||||||
|
step(state);
|
||||||
|
if (state.stacks.length > 4096) {
|
||||||
|
throw 'max recursion depth exceeded'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import {addDefn, addRPNASTDefn, makeFn} from './eval.mjs';
|
import {addDefn, addRPNASTDefn, makeFn, cloneElem} from './eval.mjs';
|
||||||
import {parseExprs} from './parse.mjs';
|
import {parseExprs} from './parse.mjs';
|
||||||
import {tokenize} from './token.mjs';
|
import {tokenize} from './token.mjs';
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ const eq = (_, args) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tuple = (_, args) => {
|
const tuple = (_, args) => {
|
||||||
return [makeFn(args[0], (_, args) => {return [{type:"array", val:args}]})];
|
return [makeFn(args[0], (_, args) => {return [{type:"tuple", val:args}]})];
|
||||||
}
|
}
|
||||||
|
|
||||||
const index = (_, args) => {
|
const index = (_, args) => {
|
||||||
@ -73,7 +73,9 @@ const len = (_, args) => {
|
|||||||
|
|
||||||
const coerce = (_, args) => {
|
const coerce = (_, args) => {
|
||||||
if (args[0].type === "type") {
|
if (args[0].type === "type") {
|
||||||
return [{type:args[0].val, val:args[1].val}];
|
let o = cloneElem(args[1]);
|
||||||
|
o.type = args[0].val;
|
||||||
|
return o;
|
||||||
} else {
|
} else {
|
||||||
throw 'typeerror'
|
throw 'typeerror'
|
||||||
}
|
}
|
||||||
@ -91,15 +93,15 @@ addDefn("pair", 2, pair);
|
|||||||
addDefn("fst", ["pair"], fst);
|
addDefn("fst", ["pair"], fst);
|
||||||
addDefn("snd", ["pair"], snd);
|
addDefn("snd", ["pair"], snd);
|
||||||
addDefn("tuple", ["int"], tuple);
|
addDefn("tuple", ["int"], tuple);
|
||||||
addDefn("!!", ["int", "array"], index);
|
addDefn("!!", ["int", "tuple"], index);
|
||||||
addDefn("len", ["array"], len);
|
addDefn("len", ["tuple"], len);
|
||||||
addDefn("unsafeCoerce", 2, coerce);
|
addDefn("unsafeCoerce", 2, coerce);
|
||||||
//addRPNDefn("unit", "(-> 0 arr)");
|
//addRPNDefn("unit", "(-> 0 arr)");
|
||||||
//addRPNDefn("mono", "(-> 1 arr)");
|
//addRPNDefn("mono", "(-> 1 arr)");
|
||||||
//addRPNDefn("unwrap", "(-> 0 !!)");
|
//addRPNDefn("unwrap", "(-> 0 !!)");
|
||||||
addRPNDefn("true", "(a b -> a)");
|
addRPNDefn("true", "(a b -> a)");
|
||||||
addRPNDefn("false", "(a b -> b)");
|
addRPNDefn("false", "(a b -> b)");
|
||||||
addRPNDefn("stop", "(-> \"stop)");
|
addRPNDefn("stop", "\"stop");
|
||||||
//addRPNDefn("id", "(a -> a)");
|
//addRPNDefn("id", "(a -> a)");
|
||||||
addRPNDefn("inv", "(x -> 1 x /)");
|
addRPNDefn("inv", "(x -> 1 x /)");
|
||||||
addRPNDefn("fold", "(x acc fn -> 'acc '(-> x acc fn 'fn fold) 'x \"stop ==)");
|
addRPNDefn("fold", "(x acc fn -> acc '(-> x acc fn 'fn fold) 'x \"stop ==)");
|
@ -72,7 +72,7 @@ const makeObj = (elem) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cloneElem = (elem) => {
|
export const cloneElem = (elem) => {
|
||||||
if (Array.isArray(elem)) {
|
if (Array.isArray(elem)) {
|
||||||
return elem.map(cloneElem);
|
return elem.map(cloneElem);
|
||||||
} if (elem.type === "closure") {
|
} if (elem.type === "closure") {
|
||||||
@ -81,6 +81,8 @@ const cloneElem = (elem) => {
|
|||||||
argsClone.push(cloneElem(elem.args[i]));
|
argsClone.push(cloneElem(elem.args[i]));
|
||||||
}
|
}
|
||||||
return {type:"closure", args:argsClone, func:elem.func};
|
return {type:"closure", args:argsClone, func:elem.func};
|
||||||
|
} else if (elem.val) {
|
||||||
|
return {type:elem.type, val:elem.val};
|
||||||
} else {
|
} else {
|
||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
@ -148,7 +150,6 @@ const pushS = (elem, stack) => {
|
|||||||
} else {
|
} else {
|
||||||
if (elem.type === "ident") {
|
if (elem.type === "ident") {
|
||||||
let id = lookupScope(elem.val, stack.scope);
|
let id = lookupScope(elem.val, stack.scope);
|
||||||
console.log(id);
|
|
||||||
pushS(id, stack);
|
pushS(id, stack);
|
||||||
} else {
|
} else {
|
||||||
stack.stack.push(elem);
|
stack.stack.push(elem);
|
||||||
@ -176,10 +177,15 @@ const showIns = (curr) => {
|
|||||||
return curr.val;
|
return curr.val;
|
||||||
} else if (curr.type === "push") {
|
} else if (curr.type === "push") {
|
||||||
return "'" + showIns(curr.elem)
|
return "'" + showIns(curr.elem)
|
||||||
|
} else if (curr.type === "func") {
|
||||||
|
return "anon"
|
||||||
|
} else if (curr.type === "defn") {
|
||||||
|
return curr.ident;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const execRPN = (scope, i) => {
|
export const execRPN = (scope, i) => {
|
||||||
|
console.log(scope);
|
||||||
let ins = JSON.parse(JSON.stringify(i));
|
let ins = JSON.parse(JSON.stringify(i));
|
||||||
let stack = {scope:scope, stack:[]};
|
let stack = {scope:scope, stack:[]};
|
||||||
while (ins.length > 0) {
|
while (ins.length > 0) {
|
@ -18,6 +18,10 @@ const show = (elem) => {
|
|||||||
return elem.val
|
return elem.val
|
||||||
} else if (elem.type === "array") {
|
} else if (elem.type === "array") {
|
||||||
return "[" + prettyprint(elem.val) + "]"
|
return "[" + prettyprint(elem.val) + "]"
|
||||||
|
} else if (elem.val) {
|
||||||
|
return "(" + elem.type + ": " + elem.val + ")"
|
||||||
|
} else {
|
||||||
|
return elem.type
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
212
old/parse.mjs
Normal file
212
old/parse.mjs
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
/*
|
||||||
|
parse.js
|
||||||
|
--------
|
||||||
|
|
||||||
|
Parse tokenstream into AST
|
||||||
|
|
||||||
|
convert:
|
||||||
|
[
|
||||||
|
{type:"int",val:1},
|
||||||
|
{type:"int",val:2},
|
||||||
|
{type:"ident",name:"+"},
|
||||||
|
{type:"syntax",val:"'"},
|
||||||
|
{type:"syntax",val:"("},
|
||||||
|
{type:"ident",name:"name"},
|
||||||
|
{type:"syntax",val:";"},
|
||||||
|
{type:"ident",name:"args"},
|
||||||
|
{type:"syntax",val:"->"},
|
||||||
|
{type:"int",val:2},
|
||||||
|
{type:"ident",name:"args"},
|
||||||
|
{type:"ident",name:"+"},
|
||||||
|
{type:"syntax",val:")"}
|
||||||
|
]
|
||||||
|
|
||||||
|
(for c+p)
|
||||||
|
[{type:"int",val:1},{type:"int",val:2},{type:"ident",name:"+"},{type:"syntax",val:"'"},{type:"syntax",val:"("},{type:"ident",name:"name"},{type:"ident",name:"args"},{type:"syntax",val:"->"},{type:"int",val:2},{type:"ident",name:"args"},{type:"ident",name:"+"},{type:"syntax",val:")"}]
|
||||||
|
|
||||||
|
to:
|
||||||
|
[
|
||||||
|
{type:"int", val:1},
|
||||||
|
{type:"int", val:2},
|
||||||
|
{type:"builtin", op:"+"},
|
||||||
|
{type:"push", elem:
|
||||||
|
{type:"func", args:["args"], body:[
|
||||||
|
{type:"int", val:1},
|
||||||
|
{type:"ident", val:"args"},
|
||||||
|
{type:"builtin", op:"+"},
|
||||||
|
}}]}}
|
||||||
|
]
|
||||||
|
|
||||||
|
EXPORTED FUNCTIONS:
|
||||||
|
parseExprs - takes in tokenstream, outputs AST
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* make parser safe */
|
||||||
|
const attempt = (parser) => (stream) => {
|
||||||
|
let streamclone = [...stream];
|
||||||
|
try {
|
||||||
|
let out = parser(stream);
|
||||||
|
return out;
|
||||||
|
} catch(err) {
|
||||||
|
return {parsed:null,stream:streamclone};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* chain */
|
||||||
|
const or = (a, b) => (stream) => {
|
||||||
|
let aout = a(stream);
|
||||||
|
if (aout.parsed === null) {
|
||||||
|
return b(aout.stream);
|
||||||
|
} else {
|
||||||
|
return aout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (parser) */
|
||||||
|
const parens = (parser) => (stream) => {
|
||||||
|
let a = parseSyntax("(")(stream);
|
||||||
|
if (a.parsed === null) {
|
||||||
|
return {parsed:null, stream:a.stream};
|
||||||
|
}
|
||||||
|
let dat = parser(a.stream);
|
||||||
|
a = parseSyntax(")")(dat.stream);
|
||||||
|
if (a.parsed === null) {
|
||||||
|
throw 'mismatched parens!';
|
||||||
|
}
|
||||||
|
return {parsed:dat.parsed, stream:a.stream};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* [parser] */
|
||||||
|
const many = (parser) => (stream) => {
|
||||||
|
let parsed = [];
|
||||||
|
for (let i = parser(stream); i.parsed !== null; i = parser(stream)) {
|
||||||
|
parsed.push(i.parsed);
|
||||||
|
stream = i.stream;
|
||||||
|
}
|
||||||
|
return {parsed:parsed, stream:stream};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* takes in stream, outputs parsed item or null */
|
||||||
|
const parseIdent = (stream) => {
|
||||||
|
let e = stream[0];
|
||||||
|
if (e === undefined) {
|
||||||
|
return {parsed:null, stream:stream};
|
||||||
|
}
|
||||||
|
if (e.type !== "ident") {
|
||||||
|
return {parsed:null, stream:stream};
|
||||||
|
} else {
|
||||||
|
stream.shift();
|
||||||
|
return {parsed:{type:e.type, val:e.name}, stream:stream};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* takes in stream, outputs parsed item or null */
|
||||||
|
const parseInteger = (stream) => {
|
||||||
|
let e = stream[0];
|
||||||
|
if (e === undefined) {
|
||||||
|
return {parsed:null, stream:stream};
|
||||||
|
}
|
||||||
|
if (e.type !== "int") {
|
||||||
|
return {parsed:null, stream:stream};
|
||||||
|
} else {
|
||||||
|
stream.shift();
|
||||||
|
return {parsed:{type:e.type, val:e.val}, stream:stream};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* takes in stream, outputs parsed item or null */
|
||||||
|
const parseSyntax = (syntax) => (stream) => {
|
||||||
|
let e = stream[0];
|
||||||
|
if (e === undefined) {
|
||||||
|
return {parsed:null, stream:stream};
|
||||||
|
}
|
||||||
|
if (e.type !== "syntax") {
|
||||||
|
return {parsed:null, stream:stream};
|
||||||
|
}
|
||||||
|
if (e.val !== syntax) {
|
||||||
|
return {parsed:null, stream:stream};
|
||||||
|
} else {
|
||||||
|
stream.shift();
|
||||||
|
return {parsed:{type:"syntax", val:syntax}, stream:stream};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* takes in stream, outputs string or null - FAILABLE */
|
||||||
|
const parseName = (stream) => {
|
||||||
|
let id = parseIdent(stream);
|
||||||
|
if (id.parsed === null) {
|
||||||
|
return {parsed:null, stream:id.stream};
|
||||||
|
}
|
||||||
|
let syn = parseSyntax(";")(id.stream);
|
||||||
|
if (syn.parsed === null) {
|
||||||
|
throw 'could not parse name!'
|
||||||
|
}
|
||||||
|
return {parsed:id.parsed.val, stream:syn.stream};
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseType = (stream) => {
|
||||||
|
let syn = attempt(parseSyntax("\""))(stream);
|
||||||
|
if (syn.parsed === null) {
|
||||||
|
return {parsed:null, stream:syn.stream};
|
||||||
|
}
|
||||||
|
let id = or(parseIdent, parseInteger)(syn.stream);
|
||||||
|
if (id.parsed === null) {
|
||||||
|
return {parsed:null, stream:id.stream};
|
||||||
|
}
|
||||||
|
return {parsed:{type:"type", 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);
|
||||||
|
if (syn.parsed === null) {
|
||||||
|
return {parsed:null, stream:syn.stream};
|
||||||
|
}
|
||||||
|
let id = parseExpr(syn.stream);
|
||||||
|
if (id.parsed === null) {
|
||||||
|
return {parsed:null, stream:id.stream};
|
||||||
|
}
|
||||||
|
return {parsed:{type:"push", elem:id.parsed}, stream:id.stream};
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseDefn = (stream) => {
|
||||||
|
let name = parseName(stream);
|
||||||
|
if (name.parsed === null) {
|
||||||
|
throw 'no name found!'
|
||||||
|
}
|
||||||
|
let expr = parseExprs(stream);
|
||||||
|
if (expr.parsed === null) {
|
||||||
|
throw 'no body found!'
|
||||||
|
}
|
||||||
|
return {parsed:{type:"defn", ident:name.parsed, defn:expr.parsed}, stream:expr.stream}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* takes in stream, outputs parsed item or null - FAILABLE */
|
||||||
|
const parseLambda = (stream) => {
|
||||||
|
let args = many(parseIdent)(stream);
|
||||||
|
let syn = parseSyntax("->")(args.stream);
|
||||||
|
if (syn.parsed === null) {
|
||||||
|
throw 'no lambda body found!';
|
||||||
|
}
|
||||||
|
let body = parseExprs(syn.stream); // .parsed should never be null, but anyway...
|
||||||
|
if (body.parsed === null) {
|
||||||
|
throw 'no lambda body found!';
|
||||||
|
}
|
||||||
|
return {parsed:{type:"func", args:args.parsed.map(x => x.val), body:body.parsed}, stream:body.stream};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* takes in stream, outputs parsed item or null */
|
||||||
|
const parseExpr = or(
|
||||||
|
attempt(parens(parseDefn)), or(
|
||||||
|
attempt(parens(parseLambda)), or(
|
||||||
|
attempt(parseLambda), or(
|
||||||
|
parseType, or(
|
||||||
|
parseIdent, or(
|
||||||
|
parseInteger,
|
||||||
|
parsePush
|
||||||
|
))))));
|
||||||
|
|
||||||
|
/* takes in stream, outputs parsed items */
|
||||||
|
export const parseExprs = many(parseExpr);
|
51
old/token.mjs
Normal file
51
old/token.mjs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
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<input.length; i++){
|
||||||
|
if(syntax.test(input.charAt(i))){
|
||||||
|
if(input.charAt(i-1) != " "){
|
||||||
|
inputWithAddedSpaces += " "; // adds a space
|
||||||
|
}
|
||||||
|
if(input.charAt(i+1) != " "){
|
||||||
|
inputWithAddedSpaces += input.charAt(i) + " "; // adds a space after the character
|
||||||
|
} else {
|
||||||
|
inputWithAddedSpaces += input.charAt(i); // adds the character
|
||||||
|
}
|
||||||
|
} else if(input.charAt(i) === "-" && input.charAt(i+1) === ">"){
|
||||||
|
if(input.charAt(i-1) != " "){
|
||||||
|
inputWithAddedSpaces += " "; // adds a space
|
||||||
|
}
|
||||||
|
if(input.charAt(i+2) != " "){
|
||||||
|
inputWithAddedSpaces += "->" + " "; // adds a space after the two characters
|
||||||
|
} else {
|
||||||
|
inputWithAddedSpaces += "->"; // adds the two characters
|
||||||
|
}
|
||||||
|
} else if(input.charAt(i) != ">") { // if it is syntax, it was already detected at "-"
|
||||||
|
inputWithAddedSpaces += input.charAt(i);
|
||||||
|
}
|
||||||
|
} // i feel like this for loop is inefficient and could just add spaces around syntax all the time, so the following code has more writes to memory but may be more efficient. replace lines 5-27 with 29-37 by commenting out lines 5-27 and removing the /* and */ around lines 29-37. Note: i have not tested the code but it probably works.
|
||||||
|
/*
|
||||||
|
for(i = 0; i<input.length; i++){
|
||||||
|
if(syntax.test(input.charAt(i))){
|
||||||
|
inputWithAddedSpaces += " " + input.charAt(i) + " ";
|
||||||
|
} else if(input.charAt(i) === "-" && input.charAt(i+1) === ">"){
|
||||||
|
inputWithAddedSpaces += " -> ";
|
||||||
|
} else {
|
||||||
|
inputWithAddedSpaces += input.charAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
let splitInput = inputWithAddedSpaces.split(" ").filter((s) => s !== "");
|
||||||
|
let output = [];
|
||||||
|
for(i = 0; i<splitInput.length; i++){
|
||||||
|
if(!isNaN(splitInput[i])){ // didn't need /[0-9]/, but that would be helpful to stop numbers from being in identifiers, but the ability to call a function "function_add_1" is easier to type than "function_add_one"
|
||||||
|
output.push({type: "int", val:Number(splitInput[i])}); // also, /[a-zA-Z]/ wasn't necessary as my code uses anything that isn't `;() or ->
|
||||||
|
} else if(syntax.test(splitInput[i]) || splitInput[i] === "->"){// needs a || as -> wasn't included in the syntax regexp. it wasn't in because -> uses two characters so i wanted to have separate code for it. (also because regexps are confusing)
|
||||||
|
output.push({type: "syntax", val:splitInput[i]});
|
||||||
|
} else if(splitInput[i] != '') { // if syntax is next to the end of the string or other bits of syntax the two spaces are in inputWithAddedSpaces so .split returns '' as one element. this makes sure that it is not read as an identifier
|
||||||
|
output.push({type: "ident", name:splitInput[i]});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(output)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user