Created
April 23, 2020 20:35
-
-
Save ske2004/459ebb2b1bfc4a86da274d1821c756dd to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const NodeType = | |
{ | |
SEXPR: 0, | |
BEXPR: 1, | |
SYMBOL: 2, | |
NUMBER: 3, | |
STRING: 4, | |
ERROR: 5, | |
FUNC: 6, | |
INVALID: 7 | |
} | |
class Node | |
{ | |
constructor(type, value) | |
{ | |
this.type = type; | |
this.value = value; | |
} | |
} | |
class FunctionNode | |
{ | |
constructor(builtin, params, ptr) | |
{ | |
this.builtin = builtin; | |
this.params = params; | |
this.ptr = ptr; | |
} | |
} | |
class Parser | |
{ | |
parseProgram(str) | |
{ | |
let context = { | |
pos: 0, | |
src: str | |
} | |
let res = []; | |
while (context.pos < context.src.length) | |
{ | |
let a; | |
a = this.determineAndParse(context); | |
if (a) | |
{ | |
res.push(a); | |
} | |
else | |
{ | |
break; | |
} | |
} | |
return res; | |
} | |
newError(context, text) | |
{ | |
console.log("Context", context.src.slice(context.pos)); | |
let err = new Node(NodeType.ERROR, "[Parser] // "+text); | |
throw new Error("[Parser] \" "+text) | |
} | |
determineAndParse(context) | |
{ | |
while (context.src.slice(context.pos).search(/[\s\n]/) == 0) | |
{ | |
context.pos += 1; | |
} | |
if (context.src[context.pos] == ")") | |
{ | |
this.newError(context, "Stray )") | |
} | |
if (context.src[context.pos] == "]") | |
{ | |
this.newError(context, "Stray ]") | |
} | |
if (context.pos >= context.src.length) return; | |
if (context.src[context.pos] == "\'" || context.src[context.pos] == "\"") | |
{ | |
context.pos += 1; | |
return this.parseString(context, context.src[context.pos-1]) | |
} | |
else if (context.src[context.pos] == "(") | |
{ | |
context.pos += 1; | |
return this.parseSexpr(context); | |
} | |
else if (context.src[context.pos] == "[") | |
{ | |
context.pos += 1; | |
return this.parseBexpr(context); | |
} | |
else if (context.src.slice(context.pos).search(/[0-9\.\-]/) == 0) | |
{ | |
return this.parseNumber(context); | |
} | |
else | |
{ | |
return this.parseSymbol(context) | |
} | |
return 234532; | |
} | |
parseSexpr(context) | |
{ | |
let res = []; | |
let found = false; | |
while (context.pos < context.src.length) | |
{ | |
while (context.src.slice(context.pos).search(/[\s\n]/) == 0) | |
{ | |
context.pos += 1; | |
} | |
if (context.src[context.pos] == ")") | |
{ | |
found = true; | |
context.pos += 1; | |
break; | |
} | |
res.push(this.determineAndParse(context)) | |
} | |
if (!found) return this.newError(context, "Unexpected EOF for S-expr"); | |
return new Node(NodeType.SEXPR, res); | |
} | |
parseBexpr(context) | |
{ | |
let res = []; | |
let found = false; | |
while (context.pos < context.src.length) | |
{ | |
while (context.src.slice(context.pos).search(/[\s\n]/) == 0) | |
{ | |
context.pos += 1; | |
} | |
if (context.src[context.pos] == "]") | |
{ | |
found = true; | |
context.pos += 1; | |
break; | |
} | |
res.push(this.determineAndParse(context)) | |
} | |
if (!found) return this.newError(context, "Unexpected EOF for B-expr"); | |
return new Node(NodeType.BEXPR, res); | |
} | |
parseSymbol(context) | |
{ | |
let r = context.src.slice(context.pos).search(/[\n\s\)\]]/); | |
if (r === -1) | |
{ | |
return this.newError(context, "Unexpected EOF For symbol"); | |
} | |
if (context.src.slice(context.pos, context.pos+r).search(/[\[\]]/) !== -1) | |
{ | |
return this.newError(context, "Disallowed characters for Symbol"); | |
} | |
let sym = context.src.slice(context.pos, context.pos+r); | |
context.pos += r; | |
return new Node(NodeType.SYMBOL, sym); | |
} | |
parseNumber(context) | |
{ | |
let r = context.src.slice(context.pos).search(/[\n\s\)\]]/); | |
if (r === -1) | |
{ | |
return this.newError(context, "Unexpected EOF For number"); | |
} | |
if (context.src.slice(context.pos, context.pos+r).search(/[^0-9\x\b\.\+\-]/) !== -1) | |
{ | |
return this.newError(context, "Disallowed characters for Number"); | |
} | |
let str = context.src.slice(context.pos, context.pos+r); | |
context.pos += r; | |
if (str.search("0x") == 0) | |
{ | |
return new Node(NodeType.NUMBER, parseInt(str.slice(2), 16)); | |
} | |
else if (str.search("0b") == 0) | |
{ | |
return new Node(NodeType.NUMBER, parseInt(str.slice(2), 2)); | |
} | |
return new Node(NodeType.NUMBER, parseFloat(str)); | |
} | |
parseString(context, strstart) | |
{ | |
let r = context.src.slice(context.pos).search(strstart); | |
if (r === -1) | |
{ | |
return this.newError(context, "Unexpected EOF For String"); | |
} | |
let str = context.src.slice(context.pos, context.pos+r); | |
context.pos += r+1; | |
return new Node(NodeType.STRING, str); | |
} | |
} | |
class Env | |
{ | |
constructor() | |
{ | |
this.runtime = null; | |
this.symbols = {}; | |
this.parent = null; | |
} | |
getSym(symname) | |
{ | |
let s = this.symbols[symname]; | |
if (!s) | |
{ | |
if (this.parent == null) | |
{ | |
return null; | |
} | |
else | |
{ | |
return this.parent.getSym(symname); | |
} | |
} | |
return s; | |
} | |
setSym(symname, value) | |
{ | |
this.symbols[symname] = value; | |
return value; | |
} | |
setFunction(symname, value) | |
{ | |
this.symbols[symname] = new Node(NodeType.FUNC, new FunctionNode(true, [], value)) | |
} | |
} | |
class Runtime | |
{ | |
constructor(env) | |
{ | |
this.env = env; | |
} | |
newError(env, err) | |
{ | |
throw new Error("[Runtime] \" "+err); | |
return null; | |
} | |
run(prog) | |
{ | |
let ret; | |
for (let i = 0; i < prog.length; i++) | |
{ | |
if (prog[i].type !== NodeType.SEXPR) | |
{ | |
this.newError(env, "Expected expression"); | |
} | |
ret = this.evalSexpr(this.env, prog[i]); | |
} | |
console.log(ret); | |
return ret; | |
} | |
evalSexpr(env, sexpr_) | |
{ | |
let sexpr = JSON.parse(JSON.stringify(sexpr_)); | |
for (let i = 0; i < sexpr.value.length; i++) | |
{ | |
if (sexpr.value[i].type === NodeType.SEXPR) | |
{ | |
sexpr.value[i] = this.evalSexpr(env, sexpr.value[i]) | |
} | |
else if (sexpr.value[i].type === NodeType.SYMBOL) | |
{ | |
let oldn = sexpr.value[i].value; | |
sexpr.value[i] = env.getSym(sexpr.value[i].value); | |
if (!sexpr.value[i]) | |
{ | |
this.newError(env, "Undefined symbol "+oldn+" if you want to output text, wrap it in '' like that '"+oldn+"'"); | |
} | |
} | |
} | |
if(sexpr.value.length < 2 && sexpr.value[0].type !== NodeType.FUNC) return sexpr.value[0]; | |
let sym; | |
sym = sexpr.value[0] | |
sym = sym.value; | |
if (sym === null || sym === undefined) | |
{ | |
this.newError(env, `Attempt to call on null`); | |
} | |
if (sexpr.value[0].type !== NodeType.FUNC) | |
{ | |
this.newError(env, "Expected function"); | |
} | |
if (sexpr.value[0].value.builtin) | |
return sym.ptr(env, sexpr); | |
else { | |
let _env = new Env(); | |
let ongoing = false; | |
_env.parent = env; | |
_env.runtime = env.runtime; | |
for (let i = 0; i < sym.params.value.length; i++) | |
{ | |
if (sym.params.value[i].value == '@') | |
{ | |
ongoing = true; | |
continue; | |
} | |
if (!ongoing) | |
_env.setSym(sym.params.value[i].value, sexpr.value[i+1]) | |
else | |
_env.setSym(sym.params.value[i].value, new Node(NodeType.BEXPR, sexpr.value.slice(i))) | |
} | |
return this.evalSexpr(_env, sym.ptr); | |
} | |
} | |
dump(node) | |
{ | |
let res = ""; | |
if (!node) return "null"; | |
if (node.type == NodeType.SEXPR) | |
{ | |
res += "("; | |
for (let i = 0; i < node.value.length; i++) | |
{ | |
if (i !== 0) res += " "; | |
res += this.dump(node.value[i]); | |
} | |
res += ")"; | |
} | |
else if (node.type == NodeType.BEXPR) | |
{ | |
res += "["; | |
for (let i = 0; i < node.value.length; i++) | |
{ | |
if (i !== 0) res += " "; | |
res += this.dump(node.value[i]); | |
} | |
res += "]"; | |
} | |
else if (node.type == NodeType.SYMBOL) | |
{ | |
res = node.value; | |
} | |
else if (node.type == NodeType.STRING) | |
{ | |
res = node.value; | |
} | |
else if (node.type == NodeType.NUMBER) | |
{ | |
res = node.value; | |
} | |
else if (node.type == NodeType.FUNC) | |
{ | |
if (node.value.builtin) | |
{ | |
return "<built-in function>"; | |
} | |
else | |
{ | |
res = "fn "+this.dump(node.value.params); | |
} | |
} | |
return res; | |
} | |
newInvalid() | |
{ | |
return new Node(NodeType.INVALID, null); | |
} | |
} | |
let fl = new Parser(); | |
let env = new Env(); | |
env.setFunction("puts", (env, list) => { | |
let v = env.runtime.dump(list.value[1])+"\n"; | |
console.log(v); | |
return new Node(NodeType.STRING, v); | |
}) | |
env.setFunction("+", (env, list) => { | |
return new Node(NodeType.STRING, list.value.slice(2).reduce((a, b) => a + b.value, list.value[1].value)); | |
}) | |
env.setFunction("-", (env, list) => { | |
return new Node(NodeType.STRING, list.value.slice(2).reduce((a, b) => a - b.value, list.value[1].value)); | |
}) | |
env.setFunction("*", (env, list) => { | |
return new Node(NodeType.STRING, list.value.slice(1).reduce((a, b) => a * b.value, 1)); | |
}) | |
env.setFunction("/", (env, list) => { | |
return new Node(NodeType.STRING, list.value.slice(2).reduce((a, b) => a / b.value, list.value[1].value)); | |
}) | |
env.setFunction("%", (env, list) => { | |
return new Node(NodeType.STRING, list.value.slice(2).reduce((a, b) => a % b.value, list.value[1].value)); | |
}) | |
env.setFunction("^", (env, list) => { | |
return new Node(NodeType.STRING, list.value.slice(2).reduce((a, b) => a ^ b.value, list.value[1].value)); | |
}) | |
env.setFunction("=", (env, list) => { | |
return env.setSym(list.value[1].value[0].value, list.value[2]); | |
}) | |
env.setFunction("<", (env, list) => { | |
return new Node(NodeType.NUMBER, list.value[1].value < list.value[2].value); | |
}) | |
env.setFunction(">", (env, list) => { | |
return new Node(NodeType.NUMBER, list.value[1].value > list.value[2].value); | |
}) | |
env.setFunction("==", (env, list) => { | |
return new Node(NodeType.NUMBER, list.value[1].value == list.value[2].value); | |
}) | |
env.setFunction("eval", (env, list) => { | |
return env.runtime.evalSexpr(env, list.value[1]); | |
}) | |
env.setFunction("list", (env, list) => { | |
return new Node(NodeType.BEXPR, list.value.slice(1)); | |
}) | |
env.setFunction("first", (env, list) => { | |
return new Node(NodeType.BEXPR, [list.value[1].value[0]]); | |
}) | |
env.setFunction("nth", (env, list) => { | |
return new Node(NodeType.BEXPR, [list.value[1].value[list.value[2].value]]); | |
}) | |
env.setFunction("len", (env, list) => { | |
return new Node(NodeType.NUMBER, list.value[1].value.length); | |
}) | |
env.setFunction("rest", (env, list) => { | |
return new Node(NodeType.BEXPR, list.value[1].value.slice(1)); | |
}) | |
env.setFunction("attach", (env, list) => { | |
list.value[1].value.push(list.value[2].value) | |
list.value[1].value = list.value[1].value.flat(); | |
return list.value[1] | |
}) | |
env.setFunction("rand", (env, list) => { | |
return new Node(NodeType.NUMBER, Math.random()); | |
}) | |
env.setFunction("if", (env, list) => { | |
if (env.runtime.evalSexpr(env, list.value[1]).value) | |
{ | |
return env.runtime.evalSexpr(env, list.value[2]); | |
} | |
else | |
{ | |
return env.runtime.evalSexpr(env, list.value[3]); | |
} | |
}) | |
env.setFunction("while", (env, list) => { | |
let iters = 0; | |
while (env.runtime.evalSexpr(env, list.value[1]).value) | |
{ | |
if (iters > 100000) throw new Error("#[Runtime] \" Too many loop iterations"); | |
env.runtime.evalSexpr(env, list.value[2]); | |
iters += 1; | |
} | |
return env.runtime.newInvalid(); | |
}) | |
env.setFunction("def", (_env, list) => { | |
let env = _env; | |
while (env.parent) | |
{ | |
env = env.parent; | |
} | |
return env.setSym(list.value[1].value[0].value, list.value[2]) | |
}) | |
env.setFunction("$", (env, list) => { | |
let idx = list.value[1].value.findIndex(e => e.value == '@'); | |
console.log(idx); | |
if (idx !== -1 && !list.value[1].value[idx+1]) env.runtime.newError(env, "Stray @"); | |
return new Node(NodeType.FUNC, new FunctionNode(false, list.value[1], list.value[2])); | |
}) | |
env.setFunction("nop", (env, list) => { | |
return env.runtime.newInvalid(); | |
}) | |
let rt = new Runtime(env); | |
env.runtime = rt; | |
let res = fl.parseProgram(` | |
(def [fn] ($ [params body] [def (first params) ($ (rest params) body)])) | |
(fn [do @ lst] [eval (nth lst (+ (len lst) -1))]) | |
(fn [randfrom lst] [nth lst (^ (* (rand) (len lst)) 0)]) | |
`); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment