fez-lisp
Version:
Lisp interpreted & compiled to JavaScript
834 lines (828 loc) • 29 kB
JavaScript
import {
TYPE,
VALUE,
WORD,
KEYWORDS,
FALSE,
TRUE,
TYPES,
RUNTIME_TYPES,
STATIC_TYPES
} from './keywords.js'
import { evaluate } from './evaluator.js'
import { isForbiddenVariableName, stringifyArgs } from './utils.js'
import { LISP } from './parser.js'
export const keywords = {
[KEYWORDS.LOOP]: (args, env) => {
if (args.length != 2)
throw new RangeError(`Wrong number of args to ${KEYWORDS.LOOP}`)
while (evaluate(args[0], env) === TRUE) evaluate(args[1], env)
return -1
},
[KEYWORDS.ADDITION]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.ADDITION
}), expected (= 2) but got ${args.length}\n\n(${
KEYWORDS.ADDITION
} ${stringifyArgs(args)})`
)
const a = evaluate(args[0], env)
if (typeof a !== 'number')
throw new TypeError(
`First arguments of (${KEYWORDS.ADDITION}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.ADDITION
} ${stringifyArgs(args)})`
)
const b = evaluate(args[1], env)
if (typeof b !== 'number')
throw new TypeError(
`Second arguments of (${KEYWORDS.ADDITION}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.ADDITION
} ${stringifyArgs(args)})`
)
return a + b
},
[KEYWORDS.MULTIPLICATION]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.MULTIPLICATION
}), expected (= 2) but got ${args.length}\n\n(${
KEYWORDS.MULTIPLICATION
} ${stringifyArgs(args)})`
)
const a = evaluate(args[0], env)
if (typeof a !== 'number')
throw new TypeError(
`First arguments of (${KEYWORDS.MULTIPLICATION}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.MULTIPLICATION
} ${stringifyArgs(args)})`
)
const b = evaluate(args[1], env)
if (typeof b !== 'number')
throw new TypeError(
`Second arguments of (${KEYWORDS.MULTIPLICATION}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.MULTIPLICATION
} ${stringifyArgs(args)})`
)
return a * b
},
[KEYWORDS.SUBTRACTION]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.SUBTRACTION
}), expected (= 2) but got ${args.length}\n\n(${
KEYWORDS.SUBTRACTION
} ${stringifyArgs(args)})`
)
const a = evaluate(args[0], env)
if (typeof a !== 'number')
throw new TypeError(
`First argument of (${KEYWORDS.SUBTRACTION}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.SUBTRACTION
} ${stringifyArgs(args)})`
)
const b = evaluate(args[1], env)
if (typeof b !== 'number')
throw new TypeError(
`Second argument of (${KEYWORDS.SUBTRACTION}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.SUBTRACTION
} ${stringifyArgs(args)})`
)
return a - b
},
[KEYWORDS.DIVISION]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.DIVISION
}), expected (= 2) but got ${args.length}\n\n(${
KEYWORDS.DIVISION
} ${stringifyArgs(args)})`
)
const a = evaluate(args[0], env)
if (typeof a !== 'number')
throw new TypeError(
`First argument of (${KEYWORDS.DIVISION}) is not (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.DIVISION
} ${stringifyArgs(args)})`
)
const b = evaluate(args[1], env)
if (typeof b !== 'number')
throw new TypeError(
`Second argument of (${KEYWORDS.DIVISION}) is not (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.DIVISION
} ${stringifyArgs(args)})`
)
if (b === 0)
throw new TypeError(
`Second Argument of (${
KEYWORDS.DIVISION
}) can't be a 0 (division by 0 is not allowed) but ${LISP.source(
args[1]
)} is 0\n\n(${KEYWORDS.DIVISION} ${stringifyArgs(args)})`
)
return a / b
},
[KEYWORDS.REMAINDER_OF_DIVISION]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.REMAINDER_OF_DIVISION
}), expected (= 2) but got ${args.length}\n\n(${
KEYWORDS.REMAINDER_OF_DIVISION
} ${stringifyArgs(args)})`
)
const a = evaluate(args[0], env)
if (typeof a !== 'number')
throw new TypeError(
`First argument of (${KEYWORDS.REMAINDER_OF_DIVISION}) is not (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.REMAINDER_OF_DIVISION
} ${stringifyArgs(args)})`
)
const b = evaluate(args[1], env)
if (typeof b !== 'number')
throw new TypeError(
`Second argument of (${KEYWORDS.REMAINDER_OF_DIVISION}) is not (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.REMAINDER_OF_DIVISION
} ${stringifyArgs(args)})`
)
if (b === 0)
throw new TypeError(
`Second argument of (${
KEYWORDS.REMAINDER_OF_DIVISION
}) can't be a 0 (division by 0 is not allowed) but ${LISP.source(
args[1]
)} is 0\n\n(${KEYWORDS.REMAINDER_OF_DIVISION} ${stringifyArgs(args)})`
)
return a % b
},
[KEYWORDS.BITWISE_AND]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments to (${
KEYWORDS.BITWISE_AND
}) (= 2 required)\n\n(${KEYWORDS.BITWISE_AND} ${stringifyArgs(args)})`
)
const a = evaluate(args[0], env)
if (typeof a !== 'number')
throw new TypeError(
`First arguments of (${KEYWORDS.BITWISE_AND}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.BITWISE_AND
} ${stringifyArgs(args)})`
)
const b = evaluate(args[1], env)
if (typeof b !== 'number')
throw new TypeError(
`Second arguments of (${KEYWORDS.BITWISE_AND}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.BITWISE_AND
} ${stringifyArgs(args)})`
)
return a & b
},
[KEYWORDS.BITWISE_NOT]: (args, env) => {
if (args.length !== 1)
throw new RangeError(
`Invalid number of arguments to (${
KEYWORDS.BITWISE_NOT
}) (= 1 required)\n\n(${KEYWORDS.BITWISE_NOT} ${stringifyArgs(args)})`
)
const operand = evaluate(args[0], env)
if (typeof operand !== 'number')
throw new TypeError(
`Argument of (${KEYWORDS.BITWISE_NOT}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.BITWISE_NOT
} ${stringifyArgs(args)})`
)
return ~operand
},
[KEYWORDS.BITWISE_OR]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments to (${
KEYWORDS.BITWISE_OR
}) (= 2 required)\n\n(${KEYWORDS.BITWISE_OR} ${stringifyArgs(args)})`
)
const a = evaluate(args[0], env)
if (typeof a !== 'number')
throw new TypeError(
`First arguments of (${KEYWORDS.BITWISE_OR}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.BITWISE_OR
} ${stringifyArgs(args)})`
)
const b = evaluate(args[1], env)
if (typeof b !== 'number')
throw new TypeError(
`Second arguments of (${KEYWORDS.BITWISE_OR}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.BITWISE_OR
} ${stringifyArgs(args)})`
)
return a | b
},
[KEYWORDS.BITWISE_XOR]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments to (${
KEYWORDS.BITWISE_XOR
}) (= 2 required)\n\n(${KEYWORDS.BITWISE_XOR} ${stringifyArgs(args)})`
)
const a = evaluate(args[0], env)
if (typeof a !== 'number')
throw new TypeError(
`First arguments of (${KEYWORDS.BITWISE_XOR}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.BITWISE_XOR
} ${stringifyArgs(args)})`
)
const b = evaluate(args[1], env)
if (typeof b !== 'number')
throw new TypeError(
`Second arguments of (${KEYWORDS.BITWISE_XOR}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.BITWISE_XOR
} ${stringifyArgs(args)})`
)
return a ^ b
},
[KEYWORDS.BITWISE_LEFT_SHIFT]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments to (${
KEYWORDS.BITWISE_LEFT_SHIFT
}) (= 2 required)\n\n(${KEYWORDS.BITWISE_LEFT_SHIFT} ${stringifyArgs(
args
)})`
)
const a = evaluate(args[0], env)
if (typeof a !== 'number')
throw new TypeError(
`First arguments of (${KEYWORDS.BITWISE_LEFT_SHIFT}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.BITWISE_LEFT_SHIFT
} ${stringifyArgs(args)})`
)
const b = evaluate(args[1], env)
if (typeof b !== 'number')
throw new TypeError(
`Second arguments of (${KEYWORDS.BITWISE_LEFT_SHIFT}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.BITWISE_LEFT_SHIFT
} ${stringifyArgs(args)})`
)
return a << b
},
[KEYWORDS.BITWISE_RIGHT_SHIFT]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments to (${
KEYWORDS.BITWISE_RIGHT_SHIFT
}) (= 2 required)\n\n(${KEYWORDS.BITWISE_RIGHT_SHIFT} ${stringifyArgs(
args
)})`
)
const a = evaluate(args[0], env)
if (typeof a !== 'number')
throw new TypeError(
`First arguments of (${KEYWORDS.BITWISE_RIGHT_SHIFT}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.BITWISE_RIGHT_SHIFT
} ${stringifyArgs(args)})`
)
const b = evaluate(args[1], env)
if (typeof b !== 'number')
throw new TypeError(
`Second arguments of (${KEYWORDS.BITWISE_RIGHT_SHIFT}) must be a (${
RUNTIME_TYPES.NUMBER
}) but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.BITWISE_RIGHT_SHIFT
} ${stringifyArgs(args)})`
)
return a >> b
},
[KEYWORDS.CREATE_ARRAY]: (args, env) => {
return args.length ? args.map((x) => evaluate(x, env)) : []
},
[KEYWORDS.GET_ARRAY]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.GET_ARRAY
}) (= 2 required)\n\n(${KEYWORDS.GET_ARRAY} ${stringifyArgs(args)})`
)
const array = evaluate(args[0], env)
if (!Array.isArray(array))
throw new TypeError(
`First argument of (${KEYWORDS.GET_ARRAY}) must be an (${
RUNTIME_TYPES.ARRAY
})\n\n(${KEYWORDS.GET_ARRAY} ${stringifyArgs(args)})`
)
if (array.length === 0)
throw new RangeError(
`First argument of (${KEYWORDS.GET_ARRAY}) is an empty (${
RUNTIME_TYPES.ARRAY
})\n\n(${KEYWORDS.GET_ARRAY} ${stringifyArgs(args)})`
)
const index = evaluate(args[1], env)
if (!Number.isInteger(index) || index < 0)
throw new TypeError(
`Second argument of (${
KEYWORDS.GET_ARRAY
}) must be a positive (interger ${
RUNTIME_TYPES.NUMBER
}) (= i ${index})\n\n(${KEYWORDS.GET_ARRAY} ${stringifyArgs(args)})`
)
if (index > array.length - 1)
throw new RangeError(
`Second argument of (${KEYWORDS.GET_ARRAY}) is outside of (${
RUNTIME_TYPES.ARRAY
}) bounds (= i ${index}) expected (and (>= i 0) (< i ${
array.length
}))\n\n(${KEYWORDS.GET_ARRAY} ${stringifyArgs(args)})`
)
const value = array.at(index)
if (value == undefined)
throw new RangeError(
`Trying to get a null value in (${RUNTIME_TYPES.ARRAY}) at (${
KEYWORDS.GET_ARRAY
})\n\n(${KEYWORDS.GET_ARRAY} ${stringifyArgs(args)})`
)
return value
},
[KEYWORDS.SET_ARRAY]: (args, env) => {
if (args.length !== 3)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.SET_ARRAY
}) (= 3) required\n\n(${KEYWORDS.SET_ARRAY} ${stringifyArgs(args)})`
)
const array = evaluate(args[0], env)
if (!Array.isArray(array))
throw new TypeError(
`First argument of (${KEYWORDS.SET_ARRAY}) must be an (${
RUNTIME_TYPES.ARRAY
}) but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.SET_ARRAY
} ${stringifyArgs(args)})`
)
const index = evaluate(args[1], env)
if (!Number.isInteger(index) || index < 0)
throw new TypeError(
`Second argument of (${KEYWORDS.SET_ARRAY}) must be a positive (${
RUNTIME_TYPES.NUMBER
} integer) (= i ${index}) but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.SET_ARRAY
} ${stringifyArgs(args)})`
)
if (index > array.length)
throw new RangeError(
`Second argument of (${KEYWORDS.SET_ARRAY}) is outside of the (${
RUNTIME_TYPES.ARRAY
}) bounds (${index}) expected (and (>= i 0) (< i ${
array.length
})) but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.SET_ARRAY
} ${stringifyArgs(args)})`
)
const value = evaluate(args[2], env)
if (value == undefined)
throw new RangeError(
`Trying to set a null value in (${RUNTIME_TYPES.ARRAY}) at (${
KEYWORDS.SET_ARRAY
})\n\n(${KEYWORDS.SET_ARRAY} ${stringifyArgs(args)})`
)
array[index] = value
return array
},
[KEYWORDS.POP_ARRAY]: (args, env) => {
if (args.length !== 1)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.POP_ARRAY
}) (= 1) required\n\n(${KEYWORDS.POP_ARRAY} ${stringifyArgs(args)})`
)
const array = evaluate(args[0], env)
if (!Array.isArray(array))
throw new TypeError(
`First argument of (${KEYWORDS.POP_ARRAY}) must be an (${
RUNTIME_TYPES.ARRAY
}) but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.POP_ARRAY
} ${stringifyArgs(args)})`
)
array.pop()
return array
},
[KEYWORDS.ARRAY_LENGTH]: (args, env) => {
if (args.length !== 1)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.ARRAY_LENGTH
}) (= 1 required)\n\n(${KEYWORDS.ARRAY_LENGTH} ${stringifyArgs(args)})`
)
const array = evaluate(args[0], env)
if (!Array.isArray(array))
throw new TypeError(
`First argument of (${KEYWORDS.ARRAY_LENGTH}) must be an ${
RUNTIME_TYPES.ARRAY
} but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.ARRAY_LENGTH
} ${stringifyArgs(args)})`
)
return array.length
},
[KEYWORDS.IF]: (args, env) => {
if (args.length !== 3)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.IF
}), expected (= 3) but got ${args.length}\n\n(${
KEYWORDS.IF
} ${stringifyArgs(args)})`
)
const condition = evaluate(args[0], env)
if (condition !== FALSE && condition !== TRUE)
throw new TypeError(
`Condition of (${
KEYWORDS.IF
}) must be ${TRUE} or ${FALSE} but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.IF
} ${stringifyArgs(args)})`
)
return condition ? evaluate(args[1], env) : evaluate(args[2], env)
},
[KEYWORDS.NOT]: (args, env) => {
if (args.length !== 1)
throw new RangeError(
`Invalid number of arguments for (${KEYWORDS.NOT}) (= 1 required)\n\n(${
KEYWORDS.NOT
} ${stringifyArgs(args)})`
)
const operand = evaluate(args[0], env)
if (operand !== FALSE && operand !== TRUE)
throw new TypeError(
`Condition of (${
KEYWORDS.NOT
}) must be ${TRUE} or ${FALSE} but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.NOT
} ${stringifyArgs(args)})`
)
return +!operand
},
[KEYWORDS.EQUAL]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.EQUAL
}) (= 2 required)\n\n(${KEYWORDS.EQUAL} ${stringifyArgs(args)})`
)
const a = evaluate(args[0], env)
const b = evaluate(args[1], env)
if (typeof a !== 'number')
throw new TypeError(
`First argument of (${KEYWORDS.EQUAL}) must be a ${
RUNTIME_TYPES.NUMBER
} but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.EQUAL
} ${stringifyArgs(args)})`
)
if (typeof b !== 'number')
throw new TypeError(
`Second argument of (${KEYWORDS.EQUAL}) must be a ${
RUNTIME_TYPES.NUMBER
} but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.EQUAL
} ${stringifyArgs(args)})`
)
return +(a === b)
},
[KEYWORDS.LESS_THAN]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.LESS_THAN
}) (= 2 required)\n\n(${KEYWORDS.LESS_THAN} ${stringifyArgs(args)})`
)
const a = evaluate(args[0], env)
const b = evaluate(args[1], env)
if (typeof a !== 'number')
throw new TypeError(
`First argument of (${KEYWORDS.LESS_THAN}) must be a ${
RUNTIME_TYPES.NUMBER
} but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.LESS_THAN
} ${stringifyArgs(args)})`
)
if (typeof b !== 'number')
throw new TypeError(
`Second argument of (${KEYWORDS.LESS_THAN}) must be a ${
RUNTIME_TYPES.NUMBER
} but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.LESS_THAN
} ${stringifyArgs(args)})`
)
return +(a < b)
},
[KEYWORDS.GREATHER_THAN]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.GREATHER_THAN
}) (= 2 required)\n\n(${KEYWORDS.GREATHER_THAN} ${stringifyArgs(args)})`
)
const a = evaluate(args[0], env)
const b = evaluate(args[1], env)
if (typeof a !== 'number')
throw new TypeError(
`First argument of (${KEYWORDS.GREATHER_THAN}) must be a ${
RUNTIME_TYPES.NUMBER
} but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.GREATHER_THAN
} ${stringifyArgs(args)})`
)
if (typeof b !== 'number')
throw new TypeError(
`Second argument of (${KEYWORDS.GREATHER_THAN}) must be a ${
RUNTIME_TYPES.NUMBER
} but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.GREATHER_THAN
} ${stringifyArgs(args)})`
)
return +(a > b)
},
[KEYWORDS.GREATHER_THAN_OR_EQUAL]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.GREATHER_THAN_OR_EQUAL
}) (= 2 required)\n\n(${
KEYWORDS.GREATHER_THAN_OR_EQUAL
} ${stringifyArgs(args)})`
)
const a = evaluate(args[0], env)
const b = evaluate(args[1], env)
if (typeof a !== 'number')
throw new TypeError(
`First argument of (${KEYWORDS.GREATHER_THAN_OR_EQUAL}) must be a ${
RUNTIME_TYPES.NUMBER
} but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.GREATHER_THAN_OR_EQUAL
} ${stringifyArgs(args)})`
)
if (typeof b !== 'number')
throw new TypeError(
`Second argument of (${KEYWORDS.GREATHER_THAN_OR_EQUAL}) must be a ${
RUNTIME_TYPES.NUMBER
} but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.GREATHER_THAN_OR_EQUAL
} ${stringifyArgs(args)})`
)
return +(a >= b)
},
[KEYWORDS.LESS_THAN_OR_EQUAL]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.LESS_THAN_OR_EQUAL
}) (= 2 required)\n\n(${KEYWORDS.LESS_THAN_OR_EQUAL} ${stringifyArgs(
args
)})`
)
const a = evaluate(args[0], env)
const b = evaluate(args[1], env)
if (typeof a !== 'number')
throw new TypeError(
`First argument of (${KEYWORDS.LESS_THAN_OR_EQUAL}) must be a ${
RUNTIME_TYPES.NUMBER
} but ${LISP.source(args[0])} is not\n\n(${
KEYWORDS.LESS_THAN_OR_EQUAL
} ${stringifyArgs(args)})`
)
if (typeof b !== 'number')
throw new TypeError(
`Second argument of (${KEYWORDS.LESS_THAN_OR_EQUAL}) must be a ${
RUNTIME_TYPES.NUMBER
} but ${LISP.source(args[1])} is not\n\n(${
KEYWORDS.LESS_THAN_OR_EQUAL
} ${stringifyArgs(args)})`
)
return +(a <= b)
},
[KEYWORDS.AND]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments for (${KEYWORDS.AND}) (= 2 required)\n\n(${
KEYWORDS.AND
} ${stringifyArgs(args)})`
)
const a = evaluate(args[0], env)
if (a !== FALSE && a !== TRUE)
throw new TypeError(
`Condition of (${
KEYWORDS.AND
}) must be ${TRUE} or ${FALSE} but got\n\n(${
KEYWORDS.AND
} ${stringifyArgs(args)})`
)
if (!a) return FALSE
const b = evaluate(args[1], env)
if (b !== FALSE && b !== TRUE)
throw new TypeError(
`Condition of (${
KEYWORDS.AND
}) must be ${TRUE} or ${FALSE} but got\n\n(${
KEYWORDS.AND
} ${stringifyArgs(args)})`
)
return b
},
[KEYWORDS.OR]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments for (${KEYWORDS.OR}) (= 2 required)\n\n(${
KEYWORDS.OR
} ${stringifyArgs(args)})`
)
const a = evaluate(args[0], env)
if (a !== FALSE && a !== TRUE)
throw new TypeError(
`Condition of (${
KEYWORDS.OR
}) must be ${TRUE} or ${FALSE} but got\n\n(${
KEYWORDS.OR
} ${stringifyArgs(args)})`
)
if (a) return TRUE
const b = evaluate(args[1], env)
if (b !== FALSE && b !== TRUE)
throw new TypeError(
`Condition of (${
KEYWORDS.OR
}) must be ${TRUE} or ${FALSE} but got\n\n(${
KEYWORDS.OR
} ${stringifyArgs(args)})`
)
return b
},
[KEYWORDS.DEFINE_VARIABLE]: (args, env) => {
if (args.length !== 2)
throw new RangeError(
`Invalid number of arguments to (${
KEYWORDS.DEFINE_VARIABLE
}) (= 2 required)\n\n(${KEYWORDS.DEFINE_VARIABLE} ${stringifyArgs(
args
)})`
)
const word = args[0]
const type = word[TYPE]
const name = word[VALUE]
if (type !== WORD)
throw new SyntaxError(
`First argument of (${KEYWORDS.DEFINE_VARIABLE}) must be word but got ${
TYPES[type]
}\n\n(${KEYWORDS.DEFINE_VARIABLE} ${stringifyArgs(args)})`
)
else if (isForbiddenVariableName(name))
throw new ReferenceError(
`Variable name ${name} is forbidden \n\n(${
KEYWORDS.DEFINE_VARIABLE
} ${stringifyArgs(args)})`
)
env[name] = evaluate(args[1], env)
return env[name]
},
[KEYWORDS.ANONYMOUS_FUNCTION]: (args, env) => {
if (!args.length)
throw new RangeError(
`At lest one argument (the body) is required for (${KEYWORDS.ANONYMOUS_FUNCTION})`
)
const params = args.slice(0, -1)
const body = args.at(-1)
return (props = [], scope, name) => {
if (props.length !== params.length)
throw new RangeError(
`Incorrect number of arguments for (${KEYWORDS.ANONYMOUS_FUNCTION}${
name ? ` ${name}` : ''
}) are provided (expects ${params.length} but got ${
props.length
})\n\n${
name
? `(${name} ${stringifyArgs(params)})`
: `(${KEYWORDS.ANONYMOUS_FUNCTION} ${stringifyArgs(params)})`
}`
)
const localEnv = Object.create(env)
for (let i = 0; i < props.length; ++i) {
const value = evaluate(props[i], scope)
localEnv[params[i][VALUE]] = value
}
return evaluate(body, localEnv)
}
},
[KEYWORDS.CALL_FUNCTION]: (args, env) => {
if (!args.length)
throw new RangeError(
`Invalid number of arguments to (${
KEYWORDS.CALL_FUNCTION
}) (>= 1 required)\n\n(${KEYWORDS.CALL_FUNCTION} ${stringifyArgs(
args
)})`
)
const first = args.at(-1)
if (first[TYPE] === WORD && first[VALUE] in keywords)
throw new TypeError(
`Preceeding arguments of (${
KEYWORDS.CALL_FUNCTION
}) must not be an reserved word\n\n(${
KEYWORDS.CALL_FUNCTION
} ${stringifyArgs(args)})`
)
const apply = evaluate(first, env)
if (typeof apply !== 'function')
throw new TypeError(
`Last argument of (${KEYWORDS.CALL_FUNCTION}) must be a (${
KEYWORDS.ANONYMOUS_FUNCTION
}) but got ${LISP.stringify(apply)}\n\n(${
KEYWORDS.CALL_FUNCTION
} ${stringifyArgs(args)})`
)
return apply(args.slice(0, -1), env)
},
[KEYWORDS.BLOCK]: (args, env) => {
if (!args.length)
throw new RangeError(
`Invalid number of arguments to (${
KEYWORDS.BLOCK
}) (>= 1 required)\n\n(${KEYWORDS.BLOCK} ${stringifyArgs(args)})`
)
let out = FALSE
for (const exp of args) out = evaluate(exp, env)
return out
},
[KEYWORDS.IS_ATOM]: (args, env) => {
if (args.length !== 1)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.IS_ATOM
}) (= 1 required)\n\n(${KEYWORDS.IS_ATOM} ${stringifyArgs(args)})`
)
return +(typeof evaluate(args[0], env) === 'number')
},
[KEYWORDS.IS_LAMBDA]: (args, env) => {
if (args.length !== 1)
throw new RangeError(
`Invalid number of arguments for (${
KEYWORDS.IS_LAMBDA
}) (= 1 required)\n\n(${KEYWORDS.IS_LAMBDA} ${stringifyArgs(args)})`
)
return +(typeof evaluate(args[0], env) === 'function')
},
[STATIC_TYPES.UNKNOWN]: (args, env) => evaluate(args[0], env),
[STATIC_TYPES.ANY]: (args, env) => evaluate(args[0], env),
[STATIC_TYPES.ATOM]: (args, env) => evaluate(args[0], env),
[STATIC_TYPES.ABSTRACTION]: (args, env) => evaluate(args[0], env),
[STATIC_TYPES.COLLECTION]: (args, env) => evaluate(args[0], env),
[STATIC_TYPES.BOOLEAN]: (args, env) => evaluate(args[0], env),
[STATIC_TYPES.NUMBER]: (args, env) => evaluate(args[0], env),
[STATIC_TYPES.NUMBERS]: (args, env) => evaluate(args[0], env),
[STATIC_TYPES.COLLECTIONS]: (args, env) => evaluate(args[0], env),
[STATIC_TYPES.ABSTRACTIONS]: (args, env) => evaluate(args[0], env),
[STATIC_TYPES.BOOLEANS]: (args, env) => evaluate(args[0], env)
}