Last active
May 17, 2024 07:26
-
-
Save Yord/5097f63ada293b2f3b88737656534718 to your computer and use it in GitHub Desktop.
This is a LISP that is also valid JSON 😮. Don't ask me what I was thinking when I wrote it...
This file contains 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
console.log('["lisp",{"dot":"json"}]\n') | |
// import { and, apply, assoc, concat, contains, dissoc, equals, head, init, is, isEmpty, isNil, keys, last, map, o, pick, pipe, prepend, propOr, reduce, reverse, tail, type } from 'ramda' | |
// HELPERS | |
const isUndefined = a => typeof(a) === 'undefined' | |
//const may = (err, res) => ({ err, res }) | |
const res = res => ({ res }) | |
const err = err => ({ err }) | |
const flatMap = f => may => ( | |
isUndefined(may.err) && !isUndefined(may.res) ? f(may.res) : may | |
) | |
const flatMap2 = f => may1 => may2 => ( | |
flatMap(val1 => flatMap(val2 => f(val1, val2))(may2))(may1) | |
) | |
const lookup = env => sym => { | |
if(isNil(env)) return null | |
if(env[sym]) return env[sym] | |
return lookup(env.__proto__)(sym) | |
} | |
// ENV | |
const Env = function () {} | |
Env.prototype = { | |
"+": flatMap2((x, y) => res(x + y)), | |
"*": flatMap2((x, y) => res(x * y)), | |
"-": flatMap2((x, y) => res(x - y)), | |
"=": flatMap2((x, y) => res(equals(x, y))), | |
"eval": f => flatMap(args => { | |
if(!is(Array, args)) return err('Arguments in eval must be an array!') | |
if(is(Function, f)) return evalExpr(env)({ args })(res(f)) | |
if(is(Object, f)) return flatMap(funcName => | |
evalExpr(env)({ funcName, args })(res({ [funcName]: args })) | |
)(f) | |
return err('Error in eval!') | |
}), | |
"concat": flatMap2((ls1, ls2) => res(concat(ls1, ls2))), | |
"cons": flatMap2((elem, ls) => res(prepend(elem, ls))), | |
"first": flatMap(list => { | |
if(list.length < 1) return err('Empty list has no first element!') | |
return res([list[0]]) | |
}), | |
"head": flatMap(list => { | |
if(list.length < 1) return err('Empty list has no head!') | |
return res(list[0]) | |
}), | |
"length": flatMap(list => res(list.length)), | |
"nil": res([]), | |
"println": flatMap(line => (console.log(line), res(null))), | |
"prop": flatMap2((name, obj) => res(map(propOr(null, name), obj))), | |
"tail": flatMap(list => { | |
if(list.length < 1) return err('Empty list has no tail!') | |
return res(tail(list)) | |
}) | |
} | |
const controlStructures = { | |
"def": env => args => { | |
if(!is(Array, args) || args.length !== 2) return err('First parameter in def must be an array of length 2!') | |
const val = args[0] | |
const expr = args[1] | |
const result = evalExpr(env)({})(res(expr)) | |
env[val] = result // MUTATING ENVIRONMENT IS BAD! | |
return { res: null } | |
}, | |
"do": env => args => { | |
const Env2 = function() {} | |
Env2.prototype = env | |
const env2 = new Env2() | |
return reduce( | |
(acc, arg) => ({ | |
env: acc.env, | |
result: evalExpr(acc.env)({})(res(arg)) | |
}), | |
{ env: env2 }, | |
args | |
).result | |
}, | |
"fn": env => args => { | |
function helper(env2, elems, i) { | |
const val = head(elems) | |
if(i > 0) { | |
return function() { | |
const argument = arguments[0]; | |
if(is(Function, argument)) { | |
const env3 = assoc(val, argument, env2) | |
return helper(env3, tail(elems), i - 1) | |
} | |
return flatMap(arg => { | |
const env3 = assoc(val, res(arg), env2) | |
return helper(env3, tail(elems), i - 1) | |
})(argument) | |
} | |
} else { | |
return evalExpr(env2)({})(res(val)) | |
} | |
} | |
const Env2 = function() {} | |
Env2.prototype = env | |
const env2 = new Env2() | |
return helper(env2, args, args.length - 1) | |
}, | |
"if": env => args => { | |
const pred = args[0] | |
if(isUndefined(pred)) return err('No predicate given in if!') | |
const aBool = evalExpr(env)({})(res(pred)) | |
return flatMap(bool => { | |
if(bool !== true && bool !== false) return err(`Predicate expected, but ${pred} given!`) | |
const choice = bool ? args[1] : args[2] | |
if(isUndefined(choice)) return err(`${bool ? 'Then' : 'Else'} branch in if is not defined!`) | |
return evalExpr(env)({})(res(choice)) | |
})(aBool) | |
} | |
} | |
// REPL | |
const read = JSON.parse | |
const print = may => { | |
if(may.err) return console.error(may.err) | |
if(may.res) return console.log(may.res) | |
return console.error('Error in print!') | |
} | |
const evalExpr = env => context => flatMap(val => { | |
switch(true) { | |
case and(is(String, val), !isNil(context.args)): | |
const controlStructure = controlStructures[val] | |
if(isUndefined(controlStructure)) return err(`Control structure ${val} not found!`) | |
return controlStructure(env)(context.args) | |
case is(Function, val): | |
try { | |
const args = context.args | |
if(args && !isEmpty(args)) { | |
const f = context.acc || val | |
const acc = f(evalExpr(env)({})(res(head(args)))) // NOT TAIL RECURSIVE! | |
return evalExpr(env)({ ...context, acc, args: tail(args) })(res(val)) | |
} | |
if(context.acc) return context.acc | |
return err(`Function ${context.funcName} cannot be resolved!`) | |
} catch(e) { | |
return err(`Error while evaluating function ${context.funcName}: ${e}`) | |
} | |
case is(Array, val): | |
return res(val) | |
case is(Object, val): | |
const funcName = head(keys(val)) | |
const args = val[funcName] || [] | |
if(contains(funcName, ["def", "do", "fn", "if"])) { | |
return evalExpr(env)({ args })(res(funcName)) | |
} | |
const func = lookup(env)(funcName) | |
if(!func) return err(`Function ${funcName} cannot be found in environment!`) | |
return evalExpr(env)({ funcName, args })(res(func)) | |
case is(String, val): | |
const value = lookup(env)(val) | |
return isNil(value) ? res(val) : value | |
case is(Number, val) || is(Boolean, val) || isNil(val): | |
return res(val) | |
default: | |
return err(`Expressions of type ${type(val)} cannot be evaluated!`) | |
} | |
}) | |
const evil = env => expr => evalExpr(env)({})(res(expr)) | |
// DEBUG | |
const standardLibrary = [ | |
{"def":["default", | |
{"if":[{"=":["nullable",null]}]}]}, | |
{"def":["identity",{"fn":["n","n"]}]}, | |
{"def":["fold", | |
{"fn":["f","id","list", | |
{"if":[{"=":[{"length":["list"]},0]}, | |
"id", | |
{"fold":["f", | |
{"f":["id",{"head":["list"]}]}, | |
{"tail":["list"]}]}]}]}]}, | |
{"def":["inc",{"+":[1]}]}, | |
{"def":["flatMap", | |
{"fn":["f","ls", | |
{"fold":[{"fn":["acc","elem", | |
{"concat":["acc", | |
{"f":["elem"]}]}]}, | |
"nil", | |
"ls"]}]}]}, | |
{"def":["map", | |
{"fn":["f", | |
"ls", | |
{"if":[{"=":[{"length":["ls"]},0]}, | |
"nil", | |
{"cons":[{"f":[{"head":["ls"]}]}, | |
{"map":["f", | |
{"tail":["ls"]}]}]}]}]}]}, | |
{"def":["o", | |
{"fn":["f","g","x", | |
{"f":[{"g":["x"]}]}]}]}, | |
{"def":["range", | |
{"fn":["n","max", | |
{"if":[{"=":["n","max"]}, | |
"n", | |
{"cons":["n", | |
{"range":[{"+":["n",1]},"max"]}]}]}]}]}, | |
{"def":["reverse", | |
{"fn":["ls", | |
{"fold":[{"fn":["acc","x", | |
{"cons":["x","acc"]}]}, | |
"nil", | |
"ls"]}]}]} | |
] | |
const env = new Env() | |
pipe( | |
map(([exp, expr]) => { | |
const result = evil(env)(expr) | |
return equals(result, exp) ? true : result | |
}), | |
console.log | |
)({ | |
"84": [ | |
{ res: 0 }, | |
{"do":[{"def":["tailcall", | |
{"fn":["n", | |
{"if":[{"=":["n",0]}, | |
"n", | |
{"tailcall":[{"-":["n",1]}]}]}]}]}, | |
{"tailcall":[1000]}]} | |
], | |
"83": [ | |
{ res: true }, | |
{"do":[{"def":["empty?", | |
{"o":[{"=":[0]},{"length":[]}]}]}, | |
{"empty?":[[]]}]} | |
], | |
"82": [ | |
{ res: [{"+":["x","y"]}] }, | |
{"do":[{"def":["map", | |
{"fn":["f","ls", | |
{"if":[{"=":[{"length":["ls"]},0]}, | |
"nil", | |
{"cons":[{"f":[{"head":["ls"]}]}, | |
{"map":["f", | |
{"tail":["ls"]}]}]}]}]}]}, | |
{"def":["let", | |
{"fn":["definitions","body", | |
{"do":[{"map":[{"fn":["arr",{"eval":["def","arr"]}]}, | |
"definitions"]}, | |
{"head":["body"]}]}]}]}, | |
{"let":[[["x",13], | |
["y",29]], | |
[{"+":["x","y"]}]]}]} | |
], | |
"81": [ | |
{ res: 29 }, | |
{"do":[{"def":["default", | |
{"fn":["ins","nullable", | |
{"if":[{"=":["nullable",null]}, | |
"ins", | |
"nullable"]}]}]}, | |
{"default":[42,29]}]} | |
], | |
"80": [ | |
{ res: 42 }, | |
{"do":[{"def":["default", | |
{"fn":["ins","nullable", | |
{"if":[{"=":["nullable",null]}, | |
"ins", | |
"nullable"]}]}]}, | |
{"default":[42,null]}]} | |
], | |
"79": [ | |
{ res: [1337] }, | |
{"do":[{"def":["fold", | |
{"fn":["f","id","list", | |
{"if":[{"=":[{"length":["list"]},0]}, | |
"id", | |
{"fold":["f", | |
{"f":["id",{"head":["list"]}]}, | |
{"tail":["list"]}]}]}]}]}, | |
{"def":["map", | |
{"fn":["f","ls", | |
{"fold":[{"fn":["acc","elem", | |
{"cons":[{"f":["elem"]}, | |
"acc"]}]}, | |
"nil", | |
"ls"]}]}]}, | |
{"def":["propOr", | |
{"fn":["instead","name","obj", | |
{"do":[{"def":["default", | |
{"fn":["ins","nullable", | |
{"if":[{"=":["nullable",null]}, | |
"ins", | |
"nullable"]}]}]}, | |
{"def":["result", | |
{"prop":["name","obj"]}]}, | |
{"map":[{"default":["instead"]}, | |
"result"]}]}]}]}, | |
{"propOr":[1337,"foo",[{"bar":42}]]}]} | |
], | |
"78": [ | |
{ res: [null] }, | |
{"prop":["bar",[{"foo":42}]]} | |
], | |
"77": [ | |
{ res: [42] }, | |
{"prop":["bar",{"prop":["foo",[{"foo":{"bar":42}}]]}]} | |
], | |
"76": [ | |
{ res: [{"bar":42}] }, | |
{"prop":["foo",[{"foo":{"bar":42}}]]} | |
], | |
"75": [ | |
{ res: 42 }, | |
{"head":[{"prop":["foo",[{"foo":42}]]}]} | |
], | |
"74": [ | |
{ res: [42] }, | |
{"prop":["foo",[{"foo":42}]]} | |
], | |
"73": [ | |
{ res: null }, | |
{"println":["Hello world!"]} | |
], | |
"72": [ | |
{ res: [1,2,3,4,5,6] }, | |
{"do":[{"def":["identity",{"fn":["n","n"]}]}, | |
{"def":["fold", | |
{"fn":["f","id","list", | |
{"if":[{"=":[{"length":["list"]},0]}, | |
"id", | |
{"fold":["f", | |
{"f":["id",{"head":["list"]}]}, | |
{"tail":["list"]}]}]}]}]}, | |
{"def":["flatMap", | |
{"fn":["f","ls", | |
{"fold":[{"fn":["acc","elem", | |
{"concat":["acc", | |
{"f":["elem"]}]}]}, | |
"nil", | |
"ls"]}]}]}, | |
{"flatMap":["identity", | |
[[1,2,3],[4,5,6]]]}]} | |
], | |
"71": [ | |
{ res: -2 }, | |
{"eval":[{"-":[1]},[3]]} | |
], | |
"70": [ | |
{ res: 8 }, | |
{"-":[{"+":[{"*":[2,4]},1]},1]} | |
], | |
"69": [ | |
{ res: -8 }, | |
{"-":[1,{"+":[1,{"*":[2,4]}]}]} | |
], | |
"68": [ | |
{ err: 'foo' }, | |
{"do":[...standardLibrary, | |
{"def":["compose", | |
{"fn":["fs","x", | |
{"fold":[{"fn":["acc","f",{"f":["acc"]}]}, | |
"x", | |
{"reverse":["fs"]}]}]}]}, | |
{"compose":[{"-":[1]},{"+":[1]},{"*":[2]},4]} | |
// {"reverse":[[1,2,3]]} | |
// {"eval":[{"fold":["o", | |
// "identity", | |
// [{"-":[1]}, | |
// {"+":[1]}, | |
// {"*":[2]}]]}, | |
// [4]]} | |
// {"def":["compose",{"fold":["o","identity"]}]}, | |
// {"eval":[{"compose":[{"-":[1]}, | |
// {"+":[1]}, | |
// {"*":[2]}]}, | |
// [4]]} | |
]} | |
], | |
"67": [ | |
{ res: -3 }, | |
{"do":[{"def":["o", | |
{"fn":["f","g","x", | |
{"f":[{"g":["x"]}]}]}]}, | |
{"o":[{"-":[1]},{"*":[2]},2]}]} | |
], | |
"66": [ | |
{ res: 5 }, | |
{"do":[{"def":["o", | |
{"fn":["f","g","x", | |
{"f":[{"g":["x"]}]}]}]}, | |
{"eval":[{"o":[{"+":[1]}, | |
{"*":[2]}]}, | |
[2]]}]} | |
], | |
"65": [ | |
{ res: [[2,3,4],[5,6,7]] }, | |
{"do":[{"def":["map", | |
{"fn":["f", | |
"ls", | |
{"if":[{"=":[{"length":["ls"]},0]}, | |
"nil", | |
{"cons":[{"f":[{"head":["ls"]}]}, | |
{"map":["f", | |
{"tail":["ls"]}]}]}]}]}]}, | |
{"map":[{"map":[{"+":[1]}]},[[1,2,3],[4,5,6]]]}]} | |
], | |
"64": [ | |
{ res: 15 }, | |
{"do":[{"def":["fold", | |
{"fn":["f","id","list", | |
{"if":[{"=":[{"length":["list"]},0]}, | |
"id", | |
{"fold":["f", | |
{"f":["id",{"head":["list"]}]}, | |
{"tail":["list"]}]}]}]}]}, | |
{"fold":["+",0,[1,2,3,4,5]]}]} | |
], | |
"63": [ | |
{ res: [2,3,4,5,6] }, | |
{"do":[{"def":["map", | |
{"fn":["f", | |
"ls", | |
{"if":[{"=":[{"length":["ls"]},0]}, | |
"nil", | |
{"cons":[{"f":[{"head":["ls"]}]}, | |
{"map":["f", | |
{"tail":["ls"]}]}]}]}]}]}, | |
{"map":[{"+":[1]}, | |
[1,2,3,4,5]]}]} | |
], | |
"62": [ | |
{ res: 3 }, | |
{"do":[{"def":["foo", | |
{"fn":["g", | |
{"g":[1,2]}]}]}, | |
{"foo":["+"]}]} | |
], | |
"61": [ | |
{ res: 2 }, | |
{"do":[{"def":["inc",{"+":[1]}]}, | |
{"inc":[{"head":[[1,2,3]]}]}]} | |
], | |
"60": [ | |
{ res: [ 1, 2, 3, 4 ] }, | |
{"do":[{"def":["range", | |
{"fn":["n","max", | |
{"if":[{"=":["n","max"]}, | |
"n", | |
{"cons":["n", | |
{"range":[{"+":["n",1]},"max"]}]}]}]}]}, | |
{"range":[1,5]}]} | |
], | |
"59": [ | |
{ res: 0 }, | |
{"length": [[]]} | |
], | |
"58": [ | |
{ res: 3 }, | |
{"length": [[1,2,3]]} | |
], | |
"57": [ | |
{ res: [1,2,3] }, | |
{"cons": [1, {"cons": [2, {"cons": [3, "nil"]}]}]} | |
], | |
"56": [ | |
{ res: [1,2,3] }, | |
{"cons": [1, [2,3]]} | |
], | |
"55": [ | |
{ res: [] }, | |
"nil" | |
], | |
"54": [ | |
{ res: [1,2,3] }, | |
{"concat":[[1],[2,3]]} | |
], | |
"53": [ | |
{ res: 11 }, | |
{"do":[{"def":["inc",{"+":[1]}]}, | |
{"inc":[10]}]} | |
], | |
"52": [ | |
{ res: 23 }, | |
{"do":[{"def":["add", | |
{"fn":["n","m", | |
{"+":["n","m"]}]}]}, | |
{"add":[10,13]}]} | |
], | |
"51": [ | |
{ res: 11 }, | |
{"do":[{"def":["inc", | |
{"fn":["n",{"+":["n",1]}]}]}, | |
{"inc":[10]}]} | |
], | |
"50": [ | |
{ err: 'Arguments in eval must be an array!' }, | |
{"eval":[{"fn":["n","n"]},1]}, | |
], | |
"49": [ | |
{ res: 36 }, | |
{"eval":[{"fn":["n", | |
{"+":["n",1]}]}, | |
[35]]} | |
], | |
"48": [ | |
{ res: 1 }, | |
{"eval":[{"fn":["n","n"]},[1]]} | |
], | |
"47": [ | |
{ res: [2,3] }, | |
{"tail":[[1,2,3]]} | |
], | |
"46": [ | |
{ res: [] }, | |
{"tail":[[1]]} | |
], | |
"45": [ | |
{ err: 'Empty list has no tail!' }, | |
{"tail":[[]]} | |
], | |
"44": [ | |
{ err: 'Empty list has no head!' }, | |
{"head":[[]]} | |
], | |
"43": [ | |
{ res: 1 }, | |
{"head":[[1,2,3]]} | |
], | |
"42": [ | |
{ err: 'Function first cannot be resolved!' }, | |
{"first":[]} | |
], | |
"41": [ | |
{ err: 'Empty list has no first element!' }, | |
{"first":[[]]} | |
], | |
"40": [ | |
{ res: [1] }, | |
{"first":[[1,2,3]]} | |
], | |
"39": [ | |
{ err: 'Function sfgh cannot be found in environment!' }, | |
{"eval":["sfgh",[1,2]]} | |
], | |
"38": [ | |
{ res: false }, | |
{"eval":["=",[1,2]]} | |
], | |
"37": [ | |
{ res: 3 }, | |
{"eval":["+",[1,2]]} | |
], | |
"36": [ | |
{ res: 'x' }, | |
{"do":[{"do":[{"def":["x",27]}]}, | |
"x"]} | |
], | |
"35": [ | |
{ res: 27 }, | |
{"do":[{"def":["x",27]}, | |
{"do":["x"]}]} | |
], | |
"34": [ | |
{ res: 41 }, | |
{"do":[{"def":["x",27]}, | |
{"def":["y",14]}, | |
{"+":["x","y"]}]} | |
], | |
"33": [ | |
{ res: 27 }, | |
{"do":[{"def":["x",27]},"x"]} | |
], | |
"32": [ | |
{ res: 14 }, | |
{"do":[27,14]} | |
], | |
"31": [ | |
{ res: 27 }, | |
{"do":[27]} | |
], | |
"30": [ | |
{ err: 'First parameter in def must be an array of length 2!' }, | |
{"def":[]} | |
], | |
"29": [ | |
{ err: 'First parameter in def must be an array of length 2!' }, | |
{"def":null} | |
], | |
"28": [ | |
{ err: 'No predicate given in if!' }, | |
{"if":[]} | |
], | |
"27": [ | |
{ res: 7 }, | |
{"if":[{"=":[1,2]},{"+":[1,2]},{"+":[3,4]}]} | |
], | |
"26": [ | |
{ res: 3 }, | |
{"if":[{"=":[1,1]},{"+":[1,2]}]} | |
], | |
"25": [ | |
{ res: 15 }, | |
{"if":[{"=":[1,1]},15,65]} | |
], | |
"24": [ | |
{ err: 'Predicate expected, but =,1,2 given!' }, | |
{"if":[["=",1,2],15,65]} | |
], | |
"23": [ | |
{ res: 65 }, | |
{"if":[{"=":[1,2]},15,65]} | |
], | |
"22": [ | |
{ res: null }, | |
{"if":[false,undefined,null]} | |
], | |
"21": [ | |
{ res: null }, | |
{"if":[true,null]} | |
], | |
"20": [ | |
{ err: 'Else branch in if is not defined!' }, | |
{"if":[false]} | |
], | |
"19": [ | |
{ err: 'Then branch in if is not defined!' }, | |
{"if":[true]} | |
], | |
"18": [ | |
{ res: 23 }, | |
{"if":[false,42,23]} | |
], | |
"17": [ | |
{ res: 42 }, | |
{"if":[true,42,23]} | |
], | |
"16": [ | |
{ err: 'Function unknown cannot be found in environment!' }, | |
{"unknown":[1]} | |
], | |
"15": [ | |
{ res: true }, | |
{"=":[{"+":[1,2]},{"+":[1,2]}]} | |
], | |
"14": [ | |
{ res: true }, | |
{"=":[[1,2],[1,2]]} | |
], | |
"13": [ | |
{ res: false }, | |
{"=":[1,2]} | |
], | |
"12": [ | |
{ res: true }, | |
{"=":[1,1]} | |
], | |
"11": [ | |
{ res: 3 }, | |
{"+":[1,2]} | |
], | |
"01": [ | |
{ res: 1 }, | |
1 | |
], | |
"02": [ | |
{ res: true }, | |
true | |
], | |
"03": [ | |
{ res: false }, | |
false | |
], | |
"04": [ | |
{ res: null }, | |
null | |
], | |
"05": [ | |
{ res: 42 }, | |
42 | |
], | |
"06": [ | |
{ res: -42 }, | |
-42 | |
], | |
"07": [ | |
{ res: 1.337 }, | |
1.337 | |
], | |
"08": [ | |
{ res: 42e5 }, | |
42e5 | |
], | |
"09": [ | |
{ res: 42E-5 }, | |
42E-5 | |
], | |
"10": [ | |
{ res: 42e+5 }, | |
42e+5 | |
] | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment