// JSON LISP - https://www.merrickchristensen.com/articles/json-lisp/
import { expect } from 'https://deno.land/x/expect/mod.ts'
type Json =
| null
| boolean
| number
| string
| Json[]
| { [prop: string]: Json };
type Environment = {
[name: string]: JSON | Function
};
const add = (...args) => args.reduce((x,y) => x + y)
const subtract = (...args) =>
(args.length === 1 ? [0, args[0]] : args).reduce((x,y) => x - y)
const division = (...args) =>
(args.length === 1 ? [1, args[0]] : args).reduce((x,y) => x / y)
const multiplication = (...args) => args.reduce((x,y) => x * y, 1)
// We provide the environment that references read from here.
const defaultEnvironment = {
"+": add,
"-": subtract,
"/": division,
"*": multiplication,
"uppercase": (str) => str.toUpperCase(),
}
////////////////////////////////////////
export const parse = (source: string): Json => JSON.parse(source)
export const evaluate = (
expression: Json,
environment: Environment
) => {
// When we encounter an expression `[ ]`
if(Array.isArray(expression)){
const procedure = expression[0]
switch(procedure){
// Check if we have a special form!
case "if": {
// Retrieve the predicate
const predicate = expression[1]
// Evaluate the predicate in the environment
if(evaluate(predicate, environment)){
// If it is true, evaluate the first branch
return evaluate(expression[2], environment)
} else {
// If it is false, evaluate the false branch, if one is provided.
if(expression[3]){
return evaluate(expression[3], environment)
} else {
return null;
}
}
}
default: {
// Evaluate each of the sub-expressions
const result = expression.map((expression) => evaluate(expression, environment))
// If there is nothing to apply, we have a value.
if(result.length === 1){
return result[0]
} else {
// Retrieve the procedure from the environment
const procedure = result[0]
// Apply it with the evaluated arguments
return procedure(...result.slice(1))
}
}
}
} else {
// Look up strings in the environment as references.
if(typeof expression === "string" &&
environment.hasOwnProperty(expression)
) {
return environment[expression];
}
// Return values.
return expression;
}
}
////////////////////////////////////////////////////
expect(evaluate(10, defaultEnvironment)).toEqual(10)
expect(evaluate(["+", 5, 3, 4], defaultEnvironment)).toEqual(12)
expect(evaluate(["-", 9, 1], defaultEnvironment)).toEqual(8)
expect(evaluate(["/", 6, 2], defaultEnvironment)).toEqual(3)
expect(evaluate(["+", ["*", 2, 4], ["-", 4, 6]], defaultEnvironment)).toEqual(6)
expect(evaluate(["uppercase", "Hello world!"], defaultEnvironment)).toEqual("HELLO WORLD!")
expect(evaluate(["if", true, ["+", 1, 1]], defaultEnvironment)).toEqual(2)
expect(evaluate(["if", false, 1, ["+", 1, 2]], defaultEnvironment)).toEqual(3)
expect(evaluate(["if", false, 1], defaultEnvironment)).toEqual(null)