UNPKG

fez-lisp

Version:

Lisp interpreted & compiled to JavaScript

1,210 lines (1,201 loc) 40.4 kB
import stdT from '../lib/baked/std-T.js' import std from '../lib/baked/std.js' import { typeCheck } from './check.js' import { enhance } from './enhance.js' import { APPLY, ATOM, KEYWORDS, TYPE, VALUE, WORD, TRUE, FALSE, STATIC_TYPES, DEBUG, SPECIAL_FORMS_SET, RUNTIME_TYPES } from './keywords.js' import { isLeaf, LISP } from './parser.js' import { definedTypes, filteredDefinedTypes, formatType, SPECIAL_FORM_TYPES, withCtxTypes } from './types.js' import { isForbiddenVariableName, removeNoCode, stringifyArgs } from './utils.js' 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') }, // Only for type checking [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), [DEBUG.TYPE_SIGNATURE]: (args, env) => evaluate(args[0], env), [DEBUG.SIGNATURE]: (args, env) => evaluate(args[0], env), [DEBUG.STRING]: (args, env) => evaluate(args[0], env), [DEBUG.UNQUOTED_STRING]: (args, env) => evaluate(args[0], env), [DEBUG.LOG]: (args, env) => evaluate(args[0], env) } const debugStack = [] let isDebugging = false const evaluate = (exp, env = keywords) => { const [head, ...tail] = isLeaf(exp) ? [exp] : exp if (head == undefined) return [] const type = head[TYPE] const value = head[VALUE] let res switch (type) { case WORD: res = env[value] return res case APPLY: const apply = env[value] if (apply == undefined) throw new ReferenceError( `Undefined (${ KEYWORDS.ANONYMOUS_FUNCTION }) (${value}) (${stringifyArgs(exp)})` ) if (typeof apply !== 'function') throw new TypeError( `${value} is not a (${KEYWORDS.ANONYMOUS_FUNCTION}) (${stringifyArgs( exp )})` ) res = apply(tail, env) if ( isDebugging && value !== KEYWORDS.BLOCK && value !== KEYWORDS.CALL_FUNCTION && value !== KEYWORDS.ANONYMOUS_FUNCTION && value !== KEYWORDS.DEFINE_VARIABLE ) { const out = typeof res === 'function' ? '(lambda)' : LISP.serialise(res) if (debugStack.at(-1)?.result !== out) debugStack.push({ function: value, source: LISP.source(exp), result: out }) // else debugStack[debugStack.length - 1].src += LISP.source(exp) } return res case ATOM: res = value return res } } export const debugStackToString = (stack) => stack.map(({ source, result }) => `${source}\n${result}`).join('\n') const sliceStack = (debugStack, start, end) => { start = start ? debugStack.findIndex(start) : 0 end = end ? debugStack.findIndex(end) + 1 : debugStack.length return debugStack.slice(start, end) } export const startDebug = (ast, speed = 250, start, end) => { debugStack.length = 0 isDebugging = true try { evaluate(enhance(ast)) const stack = sliceStack(debugStack, start, end) if (speed !== 0) { stack.reverse() const rec = () => { setTimeout(() => { if (stack.length) { const { source, result } = stack.pop() console.log(`\x1b[31m${source}\x1b[32m\n${result}\x1b[0m`) rec() } }, speed) } rec() } isDebugging = false return [stack, null] } catch (error) { isDebugging = false return [sliceStack(debugStack, start, end), error] } } // const types = typeCheck(std[0], withCtxTypes(definedTypes(stdT)))[1] const __debugStack__ = [] export const debug = (ast, checkTypes = true, userDefinedTypes) => { let types = new Map() const debugEnv = { ...keywords, [DEBUG.TYPE_SIGNATURE]: (args, env) => { if (args.length !== 2) throw new RangeError( `Invalid number of arguments to (${DEBUG.TYPE_SIGNATURE}) (= 1) (${ DEBUG.TYPE_SIGNATURE } ${stringifyArgs(args)})` ) const name = Array.isArray(args[0]) && args[0][0][VALUE] === DEBUG.STRING ? args[0][1] .slice(1) .map((x) => String.fromCharCode(x[VALUE])) .join('') : args[0][VALUE] const option = args[1][VALUE] const wildcard = name === '*' if (option === 'Scope') { if (wildcard) { return [...types.entries()] .filter((x) => x[0].split(' ').length === 3 && !x[0].includes('.')) .sort((a, b) => a[0].localeCompare(b[0])) .map(([k, v]) => `${k}\n${v()}`) .join('\n\n') } const t = types.get(`; 1 ${name}`) return t ? t() : '' } else if (option === 'Search') { return [...types.entries()] .filter((x) => !x[0].includes('.')) .filter((x) => x[0].includes(name)) .map(([k, v]) => `${k}\n${v()}`) .join('\n\n') } else if (option === 'Special') { return formatType(name, SPECIAL_FORM_TYPES) } else if (option === 'Type') { types = typeCheck(std[0], withCtxTypes(definedTypes(stdT)))[1] const [from, to] = name.split(KEYWORDS.BLOCK).map((x) => x.trim()) return [...types.entries()] .filter((x) => x[0].split(' ').length === 3 && !x[0].includes('.')) .filter(([k, v]) => { const T = v() if (T) { const last = LISP.parse(T).at(-1).at(-1) if (last[0][VALUE] === KEYWORDS.ANONYMOUS_FUNCTION) { const [left, right] = last .slice(1) .flat(Infinity) .filter((x) => x.length) .join(' ') .split(KEYWORDS.BLOCK) .map((x) => x.trim()) return left.includes(from) && right.includes(to) } } }) .sort((a, b) => a[0].length - b[0].length) .map(([k, v]) => `${k}\n${v()}`) .join('\n\n') } else if (option === 'Library') { types = typeCheck(std[0], withCtxTypes(definedTypes(stdT)))[1] const matches = wildcard ? [...types.entries()].filter( (x) => x[0].split(' ').length === 3 && !x[0].includes('.') ) : [...types.entries()] .filter( (x) => x[0].split(' ').length === 3 && !x[0].includes('.') ) .filter(([k, v]) => v().includes(name)) return matches .sort((a, b) => a[0].length - b[0].length) .map(([k, v]) => `${k}\n${v()}`) .join('\n\n') } else { return '' } }, [DEBUG.SIGNATURE]: (args, env) => { const name = Array.isArray(args[0]) && args[0][0][VALUE] === DEBUG.STRING ? args[0][1] .slice(1) .map((x) => String.fromCharCode(x[VALUE])) .join('') : args[0][VALUE] const signatures = args.length === 0 ? std[0][1][1].slice(1) : std[0][1][1].filter( (x) => x[0][TYPE] === APPLY && x[0][VALUE] === KEYWORDS.DEFINE_VARIABLE && x[1][TYPE] === WORD && x[1][VALUE].toString().includes(name) ) return signatures.length === 0 ? 'Not defined in library' : signatures.map(LISP.source).join('\n\n') }, [DEBUG.STRING]: (args, env) => { if (args.length !== 1) throw new RangeError( `Invalid number of arguments to (${DEBUG.STRING}) (= 1) (${ DEBUG.STRING } but got (${args.length}) ${stringifyArgs(args)})` ) const expression = evaluate(args[0], env) if (!Array.isArray(expression)) throw new TypeError( `Argument of (${DEBUG.STRING}) must be an (${ RUNTIME_TYPES.ARRAY }) but got (${expression}) (${DEBUG.STRING} ${stringifyArgs(args)})` ) return `"${expression.map((x) => String.fromCharCode(x)).join('')}"` }, [DEBUG.UNQUOTED_STRING]: (args, env) => { if (args.length !== 1) throw new RangeError( `Invalid number of arguments to (${DEBUG.UNQUOTED_STRING}) (= 1) (${ DEBUG.UNQUOTED_STRING } but got (${args.length}) ${stringifyArgs(args)})` ) const expression = evaluate(args[0], env) if (!Array.isArray(expression)) throw new TypeError( `Argument of (${DEBUG.UNQUOTED_STRING}) must be an (${ RUNTIME_TYPES.ARRAY }) but got (${expression}) (${DEBUG.UNQUOTED_STRING} ${stringifyArgs( args )})` ) return `${expression.map((x) => String.fromCharCode(x)).join('')}` }, [DEBUG.LOG]: (args, env) => { if (args.length !== 1 && args.length !== 2) throw new RangeError( `Invalid number of arguments to (${DEBUG.LOG}) (or (= 1) (= 2)) (${ DEBUG.LOG } ${stringifyArgs(args)})` ) const expression = evaluate(args[0], env) if (args.length === 2) { const option = evaluate(args[1], env) if (!Array.isArray(option)) { throw new TypeError( `Second argument of (${DEBUG.LOG}) must be an (${ RUNTIME_TYPES.ARRAY }) but got (${expression}) (${DEBUG.LOG} ${stringifyArgs(args)})` ) } const type = option.map((x) => String.fromCharCode(x)).join('') switch (type) { case 'string': case 'str': { if (!Array.isArray(expression)) throw new TypeError( `Argument of (${DEBUG.LOG}) must be an (${ RUNTIME_TYPES.ARRAY }) in the case ${type} but got (${expression}) (${ DEBUG.LOG } ${stringifyArgs(args)})` ) console.log( expression.map((x) => String.fromCharCode(x)).join('') ) } break case 'char': case 'ch': { if (typeof expression !== 'number') throw new TypeError( `Argument argument of (${DEBUG.LOG}) must be a (${ RUNTIME_TYPES.NUMBER }) in the case ${type} but got (${expression}) (${ DEBUG.LOG } ${stringifyArgs(args)})` ) console.log(String.fromCharCode(expression)) } break case '*': console.log(expression) break case '!': __debugStack__.push(structuredClone(expression)) break default: throw new TypeError( `Invalid option to (${DEBUG.LOG}) got ${option} ${stringifyArgs( args )})` ) } } else console.log(expression) return expression } } try { types = checkTypes ? typeCheck( ast, withCtxTypes( userDefinedTypes ? { ...definedTypes(filteredDefinedTypes(ast, std, stdT)), ...definedTypes(LISP.parse(removeNoCode(userDefinedTypes))) } : definedTypes(filteredDefinedTypes(ast, std, stdT)) ) )[1] : new Map() const evaluated = evaluate(enhance(ast), debugEnv) const exp = ast.at(-1).at(-1).at(-1) const [head, ...rest] = isLeaf(exp) ? [exp] : exp let type = '' switch (head[TYPE]) { case ATOM: type = STATIC_TYPES.ATOM break case WORD: case APPLY: switch (head[VALUE]) { case DEBUG.TYPE_SIGNATURE: break case KEYWORDS.DEFINE_VARIABLE: type = debugEnv[DEBUG.TYPE_SIGNATURE]([rest[0], [WORD, 'Scope']]) break default: if (SPECIAL_FORMS_SET.has(head[VALUE])) { // type = debugEnv[DEBUG.TYPE_SIGNATURE]([head, [WORD, 'Special']]) type = `${debugEnv[DEBUG.TYPE_SIGNATURE]([ head, [WORD, 'Special'] ])}` } else type = `${debugEnv[DEBUG.TYPE_SIGNATURE]([ head, [WORD, 'Scope'] ])}` break } break } type = type.split('\n').pop() return { type, evaluated, error: null } } catch (error) { // console.log(error) return { type: null, evaluated: null, error: { message: error.message } } } }