Skip to content

Instantly share code, notes, and snippets.

@Yord
Last active May 17, 2024 07:26
Show Gist options
  • Save Yord/5097f63ada293b2f3b88737656534718 to your computer and use it in GitHub Desktop.
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...
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