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]}}];
|
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 = {
|
let builtinDefn = {
|
||||||
"+": makeEval(["int", "int"], add),
|
"+": makeEval(["int", "int"], add),
|
||||||
"-": makeEval(["int", "int"], sub),
|
"-": makeEval(["int", "int"], sub),
|
||||||
"/": makeEval(["int", "int"], div),
|
"/": makeEval(["int", "int"], div),
|
||||||
"*": makeEval(["int", "int"], mult),
|
"*": makeEval(["int", "int"], mult),
|
||||||
|
"true": makeEval(2, tru),
|
||||||
|
"false": makeEval(2, fls),
|
||||||
|
"==": makeEval(2, eq),
|
||||||
"typeof": makeEval(1, type),
|
"typeof": makeEval(1, type),
|
||||||
"pair": makeEval(2, pair),
|
"pair": makeEval(2, pair),
|
||||||
"fst": makeEval(["pair"], fst),
|
"fst": makeEval(["pair"], fst),
|
||||||
"snd": makeEval(["pair"], snd)
|
"snd": makeEval(["pair"], snd)
|
||||||
}
|
}
|
||||||
|
|
||||||
const addDefn = (name, sign, func) => {
|
export const addDefn = (name, sign, func) => {
|
||||||
builtinDefn[name] = makeEval(sign, func);
|
builtinDefn[name] = makeEval(sign, func);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,16 +108,38 @@ const makeLambda = (lambda) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const makeObj = (elem) => {
|
const makeObj = (elem) => {
|
||||||
if (elem.type === "builtin") {
|
if (elem.type === "func") {
|
||||||
let fn = builtinDefn[elem.op];
|
|
||||||
return {type:"closure", args:[], func:fn};
|
|
||||||
} else if (elem.type === "func") {
|
|
||||||
return {type:"closure", args:[], func:makeLambda(elem)};
|
return {type:"closure", args:[], func:makeLambda(elem)};
|
||||||
} else {
|
} else {
|
||||||
return elem;
|
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) => {
|
const giveArg = (closure, arg, scope) => {
|
||||||
closure.args.push(arg);
|
closure.args.push(arg);
|
||||||
if (closure.args.length === closure.func.nargs) {
|
if (closure.args.length === closure.func.nargs) {
|
||||||
@ -115,10 +151,14 @@ const giveArg = (closure, arg, scope) => {
|
|||||||
|
|
||||||
const apply = (elem, stack) => {
|
const apply = (elem, stack) => {
|
||||||
if (elem.type === "closure") {
|
if (elem.type === "closure") {
|
||||||
let out = giveArg(elem, stack.stack.pop(), stack.scope);
|
if (stack.stack.length > 0) {
|
||||||
applyMany(out, stack);
|
let out = giveArg(elem, stack.stack.pop(), stack.scope);
|
||||||
|
applyMany(out, stack);
|
||||||
|
} else {
|
||||||
|
stack.stack.push(elem);
|
||||||
|
}
|
||||||
} else if (elem.type === "ident") {
|
} else if (elem.type === "ident") {
|
||||||
let id = stack.scope[elem.val];
|
let id = lookupScope(elem.val, stack.scope);
|
||||||
apply(id, stack);
|
apply(id, stack);
|
||||||
} else {
|
} else {
|
||||||
stack.stack.push(elem);
|
stack.stack.push(elem);
|
||||||
@ -133,7 +173,7 @@ const applyMany = (outstack, stack) => {
|
|||||||
|
|
||||||
const pushS = (elem, stack) => {
|
const pushS = (elem, stack) => {
|
||||||
if (elem.type === "ident") {
|
if (elem.type === "ident") {
|
||||||
let id = stack.scope[elem.name];
|
let id = lookupScope(elem.val, stack.scope);
|
||||||
stack.stack.push(id);
|
stack.stack.push(id);
|
||||||
} else {
|
} else {
|
||||||
stack.stack.push(elem);
|
stack.stack.push(elem);
|
||||||
@ -144,7 +184,7 @@ const defn = (elem, name, stack) => {
|
|||||||
stack.scope[name] = makeObj(elem);
|
stack.scope[name] = makeObj(elem);
|
||||||
}
|
}
|
||||||
|
|
||||||
const doStep = (ins, stack) => {
|
export const doStep = (ins, stack) => {
|
||||||
let instruction = ins.shift();
|
let instruction = ins.shift();
|
||||||
if (instruction.type === "push") {
|
if (instruction.type === "push") {
|
||||||
pushS(makeObj(instruction.elem), stack);
|
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:[]};
|
let stack = {scope:scope, stack:[]};
|
||||||
while (ins.length > 0) {
|
while (ins.length > 0) {
|
||||||
doStep(ins, stack);
|
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
|
parseExprs - takes in tokenstream, outputs AST
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const builtin = [
|
|
||||||
"+",
|
|
||||||
"-",
|
|
||||||
"*",
|
|
||||||
"/",
|
|
||||||
"typeof",
|
|
||||||
"pair",
|
|
||||||
"fst",
|
|
||||||
"snd"
|
|
||||||
];
|
|
||||||
|
|
||||||
/* make parser safe */
|
/* make parser safe */
|
||||||
const attempt = (parser) => (stream) => {
|
const attempt = (parser) => (stream) => {
|
||||||
let streamclone = [...stream];
|
let streamclone = [...stream];
|
||||||
@ -98,23 +87,6 @@ const many = (parser) => (stream) => {
|
|||||||
return {parsed:parsed, stream: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 */
|
/* takes in stream, outputs parsed item or null */
|
||||||
const parseIdent = (stream) => {
|
const parseIdent = (stream) => {
|
||||||
let e = stream[0];
|
let e = stream[0];
|
||||||
@ -173,10 +145,24 @@ const parseName = (stream) => {
|
|||||||
return {parsed:id.parsed.val, stream:syn.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 */
|
/* takes in stream, outputs parsed item or null - FAILABLE */
|
||||||
const parsePush = (stream) => {
|
const parsePush = (stream) => {
|
||||||
let syn = attempt(parseSyntax("'"))(stream);
|
let syn = attempt(parseSyntax("'"))(stream);
|
||||||
console.log(syn);
|
|
||||||
if (syn.parsed === null) {
|
if (syn.parsed === null) {
|
||||||
return {parsed:null, stream:syn.stream};
|
return {parsed:null, stream:syn.stream};
|
||||||
}
|
}
|
||||||
@ -203,12 +189,12 @@ const parseLambda = (stream) => {
|
|||||||
if (name.parsed === null) {
|
if (name.parsed === null) {
|
||||||
return {parsed:func, stream:body.stream};
|
return {parsed:func, stream:body.stream};
|
||||||
} else {
|
} 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 */
|
/* 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 */
|
/* takes in stream, outputs parsed items */
|
||||||
export const parseExprs = many(parseExpr);
|
export const parseExprs = many(parseExpr);
|
@ -1,7 +1,7 @@
|
|||||||
function tokenize(input) {
|
export function tokenize(input) {
|
||||||
var i;
|
let i;
|
||||||
var inputWithAddedSpaces = "";
|
let inputWithAddedSpaces = "";
|
||||||
const syntax = /[`;()]/; // -> is a special syntax (because it uses 2 characters) so is coded separately
|
const syntax = /['";()]/; // -> is a special syntax (because it uses 2 characters) so is coded separately
|
||||||
for(i = 0; i<input.length; i++){
|
for(i = 0; i<input.length; i++){
|
||||||
if(syntax.test(input.charAt(i))){
|
if(syntax.test(input.charAt(i))){
|
||||||
if(input.charAt(i-1) != " "){
|
if(input.charAt(i-1) != " "){
|
||||||
@ -21,10 +21,10 @@ function tokenize(input) {
|
|||||||
} else {
|
} else {
|
||||||
inputWithAddedSpaces += "->"; // adds the two characters
|
inputWithAddedSpaces += "->"; // adds the two characters
|
||||||
}
|
}
|
||||||
} else if(input.charAt(i) != ">") { // if it is syntax, it was already detected at "-"
|
} else if(input.charAt(i) != ">") { // if it is syntax, it was already detected at "-"
|
||||||
inputWithAddedSpaces += input.charAt(i);
|
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.
|
} // 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++){
|
for(i = 0; i<input.length; i++){
|
||||||
if(syntax.test(input.charAt(i))){
|
if(syntax.test(input.charAt(i))){
|
||||||
@ -36,11 +36,11 @@ function tokenize(input) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
splitInput = inputWithAddedSpaces.split(" ");
|
let splitInput = inputWithAddedSpaces.split(" ");
|
||||||
var output = [];
|
let output = [];
|
||||||
for(i = 0; i<splitInput.length; i++){
|
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"
|
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:splitInput[i]}); // also, /[a-zA-Z]/ wasn't necessary as my code uses anything that isn't `;() or ->
|
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)
|
} 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]});
|
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
|
} 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