mirror of
https://github.com/Baidicoot/rpncalc-v4
synced 2024-12-04 23:39:56 +00:00
did thinfs & debugged others
This commit is contained in:
parent
95cba6f712
commit
285910c551
@ -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") {
|
||||
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);
|
34
index.html
Normal file
34
index.html
Normal file
@ -0,0 +1,34 @@
|
||||
<html>
|
||||
<h1>RPNCalc V4 Public Testing</h1>
|
||||
<p>@ me (aidanprattewart@protonmail.com) if you have any errors, with console output please.</p>
|
||||
<p>
|
||||
Example Programs:
|
||||
<br>
|
||||
<code>(swap; a b -> a b) 1 2 swap</code> N.B. args are in reverse order
|
||||
<br>
|
||||
<code>1 + 2 (v partial -> 'v partial)</code> Evaluate a partial
|
||||
</p>
|
||||
<input type="text" id="inbox">
|
||||
<button id="submit">execute</button>
|
||||
<p id="outbox"></p>
|
||||
<script src="./main.js" type="module"></script>
|
||||
<p>
|
||||
<h2>Docs</h2>
|
||||
<h3>Lambda/Function Syntax</h3>
|
||||
named function (added to scope, not applied): <code>(swap; a b -> b a)</code>
|
||||
<br>
|
||||
can be called later like: <code>1 2 swap</code>
|
||||
<br>
|
||||
unnamed function, applied immediately: <code>1 2 3 (a b c -> a b c)</code>
|
||||
<br>
|
||||
when lambdas return, they return entire stacks, which are sequentially applied back onto the stack
|
||||
<h3>Partial Application</h3>
|
||||
<code>1 +</code> will push a partially-applied function onto the stack
|
||||
<br>
|
||||
this could be called with: <code>1 + (partial -> 2 partial)</code>
|
||||
<h3>Pairs</h3>
|
||||
pairs of 2 values can be created with <code>pair</code>
|
||||
<br>
|
||||
they can be deconstructed using <code>fst</code> or <code>snd</code>
|
||||
</p>
|
||||
</html>
|
50
main.js
Normal file
50
main.js
Normal file
@ -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);
|
||||
}
|
@ -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);
|
@ -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<input.length; i++){
|
||||
if(syntax.test(input.charAt(i))){
|
||||
if(input.charAt(i-1) != " "){
|
||||
@ -36,11 +36,11 @@ function tokenize(input) {
|
||||
}
|
||||
}
|
||||
*/
|
||||
splitInput = inputWithAddedSpaces.split(" ");
|
||||
var output = [];
|
||||
let splitInput = inputWithAddedSpaces.split(" ");
|
||||
let output = [];
|
||||
for(i = 0; i<splitInput.length; i++){
|
||||
if(parseInt(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:splitInput[i]}); // also, /[a-zA-Z]/ wasn't necessary as my code uses anything that isn't `;() or ->
|
||||
if(/^\d+$/.test(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:parseInt(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
|
Loading…
Reference in New Issue
Block a user