UNPKG

fez-lisp

Version:

Lisp interpreted & compiled to JavaScript

1,401 lines (1,397 loc) 70.7 kB
import { APPLY, ATOM, FALSE, GETTERS_SET, KEYWORDS, MUTATION_SUFFIX, MUTATORS_SET, PLACEHOLDER, PREDICATE_SUFFIX, PREDICATES_OUTPUT_SET, SPECIAL_FORMS_SET, STATIC_TYPES, STATIC_TYPES_SET, TRUE, TYPE, VALUE, WORD } from './keywords.js' import { isLeaf } from './parser.js' import { SPECIAL_FORM_TYPES, toTypeNames, ARG_COUNT, VARIADIC, STATS, ARGUMENTS, UNKNOWN, RETURNS, SCOPE_NAME, TYPE_PROP, SIGNATURE, MAX_RETRY_DEFINITION, MAX_ARGUMENT_RETRY, COLLECTION, ANY, formatType, ANONYMOUS_FUNCTION_TYPE_PREFIX, validateLambda, NIL, TRUE_WORD, FALSE_WORD, BOOLEAN_SUBTYPE, formatSubType, BOOLEAN, IS_ARGUMENT, NUMBER, NUMBER_SUBTYPE, UNKNOWN_SUBTYPE } from './types.js' import { Brr, getSuffix, hasApplyLambdaBlock, hasBlock, log, logExp, stringifyArgs } from './utils.js' Set.prototype.union = function (B) { const A = this const out = new Set() A.forEach((element) => out.add(element)) B.forEach((element) => out.add(element)) return out } Set.prototype.xor = function (B) { const A = this const out = new Set() B.forEach((element) => !A.has(element) && out.add(element)) A.forEach((element) => !B.has(element) && out.add(element)) return out } Set.prototype.intersection = function (B) { const A = this const out = new Set() B.forEach((element) => A.has(element) && out.add(element)) return out } Set.prototype.difference = function (B) { const A = this const out = new Set() A.forEach((element) => !B.has(element) && out.add(element)) return out } export const identity = (name) => [ [0, 'let'], [1, name], [ [0, 'lambda'], [1, 'x'], [1, 'x'] ] ] const returnType = (rest) => { const body = rest.at(-1) const rem = hasBlock(body) ? body.at(-1) : body return isLeaf(rem) ? rem : rem[0] } const drillReturnType = (rest, condition) => { const body = rest.at(-1) const rem = hasBlock(body) ? body.at(-1) : body const returns = isLeaf(rem) ? rem : rem[0] return condition(returns) ? drillReturnType(rem.at(-1), condition) : [returns, rem] } const deepLambdaReturn = (rest, condition) => { const body = rest.at(-1) const rem = hasBlock(body) ? body.at(-1) : body return condition(rem) ? rem : deepLambdaReturn(rem, condition) } export const isUnknownNotAnyType = (stats) => stats && !isAnyType(stats) && isUnknownType(stats) export const isUnknownNotAnyReturn = (stats) => stats && !isAnyReturn(stats) && isUnknownReturn(stats) export const castType = (stats, type) => { return ( (stats[TYPE_PROP][0] = type[RETURNS][0]), type[RETURNS][1] && (stats[TYPE_PROP][1] = type[RETURNS][1]) ) } export const castReturn = (stats, type) => { return ( (stats[RETURNS][0] = type[RETURNS][0]), type[RETURNS][1] && (stats[RETURNS][1] = type[RETURNS][1]) ) } export const isGenericReturn = (stats) => stats[RETURNS].length === 3 export const isTypeAbstraction = (stats) => stats[TYPE_PROP] === APPLY export const setPropToAtom = (stats, prop) => { return ( (stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) && ((stats[prop][0] = ATOM), (stats[prop][1] = NUMBER_SUBTYPE())) ) } const setReturnToGeneric = (stats, index) => { stats[RETURNS] = [UNKNOWN, undefined, index] } export const setPropToPredicate = (stats, prop) => { return (stats[prop][1] = BOOLEAN_SUBTYPE()) } export const setReturnToPredicate = (stats) => { return (stats[RETURNS][1] = BOOLEAN_SUBTYPE()) } export const setTypeToPredicate = (stats) => { return (stats[RETURNS][1] = BOOLEAN_SUBTYPE()) } export const setPropToAbstraction = (stats, prop) => { return ( (stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) && (stats[prop][0] = APPLY) ) } export const setPropToCollection = (stats, prop) => { return ( (stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) && (stats[prop][0] = COLLECTION) ) } export const setProp = (stats, prop, value) => { if ( (stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) && value[prop][0] !== UNKNOWN && value[prop][0] !== ANY ) { stats[prop][0] = value[prop][0] if (value[prop][1]) stats[prop][1] = value[prop][1] } } export const setPropToReturn = (stats, prop, value) => { if ( (stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) && value[RETURNS][0] !== UNKNOWN && value[RETURNS][0] !== ANY ) { stats[prop][0] = value[RETURNS][0] if (value[RETURNS][1]) stats[prop][1] = value[RETURNS][1] } } export const setPropToReturnRef = (stats, prop, value) => { if ( stats[prop] && (stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) && value[RETURNS][0] !== UNKNOWN && value[RETURNS][0] !== ANY ) stats[prop] = value[RETURNS] } export const setPropToType = (stats, prop, value) => { if ( stats[prop] && (stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) && value[TYPE_PROP][0] !== ANY ) { stats[prop][0] = value[TYPE_PROP][0] if (value[TYPE_PROP][1]) stats[prop][1] = value[TYPE_PROP][1] } } export const setPropToTypeRef = (stats, prop, value) => { if (stats[prop] && (stats[prop][0] === UNKNOWN || stats[prop][0] === ANY)) stats[prop] = value[TYPE_PROP] } export const setReturnToAtom = (stats) => { if (isUnknownReturn(stats) || isAnyReturn(stats)) { stats[RETURNS][0] = ATOM stats[RETURNS][1] = NUMBER_SUBTYPE() } } export const setTypeToAtom = (stats) => (isUnknownType(stats) || isAnyType(stats)) && ((stats[TYPE_PROP][0] = ATOM), (stats[TYPE_PROP][1] = NUMBER_SUBTYPE())) export const setTypeToCollection = (stats) => (isUnknownType(stats) || isAnyType(stats)) && (stats[TYPE_PROP][0] = COLLECTION) export const setTypeToAbstraction = (stats) => (isUnknownType(stats) || isAnyType(stats)) && (stats[TYPE_PROP][0] = APPLY) export const setReturnToAbbstraction = (stats) => isUnknownReturn(stats) && (stats[RETURNS][0] = APPLY) export const setTypeRef = (stats, value) => (isUnknownType(stats) || isAnyType(stats)) && (stats[TYPE_PROP] = value[TYPE_PROP]) export const setReturnRef = (stats, value) => { if (SPECIAL_FORMS_SET.has(value[SIGNATURE])) { return setReturn(stats, value) } return isUnknownReturn(stats) && (stats[RETURNS] = value[RETURNS]) } export const setReturnToTypeRef = (stats, value) => { return ( (isUnknownReturn(stats) || isAnyReturn(stats)) && (stats[RETURNS] = value[TYPE_PROP]) ) } export const setStatsRef = (a, b) => (a[STATS] = b[STATS]) export const setTypeToReturnRef = (stats, value) => { // To prevent getters overwritting the array subtype // Change main type if unknown if (isUnknownType(stats) || isAnyType(stats)) if (!isUnknownReturn(value)) stats[TYPE_PROP][0] = value[RETURNS][0] // TODO: Figure out what we do if generic thigns get infered // cange sub type if it doesn't have if (!hasSubType(stats) || getSubType(stats).has(UNKNOWN)) if (hasSubReturn(value)) stats[TYPE_PROP][1] = value[RETURNS][1] } export const setPropRef = (stats, prop, value) => { return ( (stats[prop][0] === UNKNOWN || stats[prop][0] === ANY) && (stats[prop] = value[prop]) ) } export const setReturn = (stats, value) => { return ( isUnknownReturn(stats) && !isUnknownReturn(value) && ((stats[RETURNS][0] = value[RETURNS][0]), value[RETURNS][1] && (stats[RETURNS][1] = value[RETURNS][1])) ) } export const setType = (stats, value) => (isUnknownType(stats) || isAnyType(stats)) && !isUnknownType(value) && ((stats[TYPE_PROP][0] = value[TYPE_PROP][0]), value[TYPE_PROP][1] && (stats[TYPE_PROP][1] = value[TYPE_PROP][1])) export const setSubType = (stats, value) => // makes no senseto protect this for now hasSubType(value) && (stats[TYPE_PROP][1] = value[TYPE_PROP][1]) export const setPropToSubType = (stats, prop, value) => // makes no senseto protect this for now hasSubType(value) && (stats[prop][1] = value[TYPE_PROP][1]) export const setPropToSubReturn = (stats, prop, value) => // makes no senseto protect this for now hasSubReturn(value) && (stats[prop][1] = value[RETURNS][1]) export const setTypeToReturn = (stats, value) => (isUnknownType(stats) || isAnyType(stats)) && !isUnknownReturn(value) && ((stats[TYPE_PROP][0] = value[RETURNS][0]), value[RETURNS][1] && (stats[TYPE_PROP][1] = value[RETURNS][1])) export const setReturnToType = (stats, value) => (isUnknownReturn(stats) || isAnyReturn(stats)) && !isUnknownType(value) && ((stats[RETURNS][0] = value[TYPE_PROP][0]), value[TYPE_PROP][1] && (stats[RETURNS][1] = value[TYPE_PROP][1])) export const isAnyReturn = (stats) => stats && stats[RETURNS][0] === ANY export const isAnyType = (stats) => stats && stats[TYPE_PROP][0] === ANY export const isUnknownType = (stats) => stats && stats[TYPE_PROP][0] === UNKNOWN export const isUnknownProp = (stats, prop) => { return stats && stats[prop][0] === UNKNOWN } export const isUnknownReturn = (stats) => stats[RETURNS][0] === UNKNOWN export const getType = (stats) => stats && stats[TYPE_PROP][0] export const getTypes = (stats) => stats && stats[TYPE_PROP] export const getReturn = (stats) => stats && stats[RETURNS][0] export const getReturns = (stats) => stats && stats[RETURNS] export const getSubType = (stats) => stats && stats[TYPE_PROP][1] export const hasSubType = (stats) => stats && stats[TYPE_PROP][1] instanceof Set export const getSubReturn = (stats) => stats && stats[RETURNS][1] export const hasSubReturn = (stats) => stats && stats[RETURNS][1] instanceof Set export const isUknownSubReturn = (stats) => !hasSubReturn(stats) || (stats[RETURNS][1].size === 1 && stats[RETURNS][1].has(UNKNOWN)) export const isUknownSubType = (stats) => hasSubType(stats) && stats[TYPE_PROP][1].size === 1 && stats[TYPE_PROP][1].has(UNKNOWN) export const isAtomType = (stats) => isAnyType(stats) || stats[TYPE_PROP][0] === ATOM export const isAtomReturn = (stats) => isAnyType(stats) || stats[RETURNS][0] === ATOM export const equalTypes = (a, b) => { const isAnyAny = isAnyType(a) || isAnyType(b) if (isAnyAny) return true const isSameType = a[TYPE_PROP][0] === b[TYPE_PROP][0] if (!isSameType) return false return true } const isRedifinedInLambda = (env, name, exp) => { if (exp.slice(1, -1).some((x) => x[VALUE] === name)) return true else if ( exp .at(-1) .some( (x) => !isLeaf(x) && x[0][TYPE] === APPLY && x[0][VALUE] === KEYWORDS.DEFINE_VARIABLE && x[1][VALUE] === name ) ) return true else return false } export const equalReturns = (a, b) => isAnyReturn(a) || isAnyReturn(b) || a[RETURNS][0] === b[RETURNS][0] export const equalsTypeWithReturn = (a, b) => isAnyType(a) || isAnyReturn(b) || a[TYPE_PROP][0] === b[RETURNS][0] const IsPredicate = (leaf) => (leaf[TYPE] === ATOM && (leaf[VALUE] === TRUE || leaf[VALUE] === FALSE)) || (leaf[TYPE] === WORD && (leaf[VALUE] === TRUE_WORD || leaf[VALUE] === FALSE_WORD || leaf[VALUE] === NIL || getSuffix(leaf[VALUE]) === PREDICATE_SUFFIX)) || (leaf[TYPE] === APPLY && (PREDICATES_OUTPUT_SET.has(leaf[VALUE]) || getSuffix(leaf[VALUE]) === PREDICATE_SUFFIX)) const equalSubTypes = (a, b) => { return ( !hasSubType(a) || !hasSubType(b) || getSubType(a).has(UNKNOWN) || getSubType(b).has(UNKNOWN) || getSubType(a).has(ANY) || getSubType(b).has(ANY) || getSubType(a).difference(getSubType(b)).size === 0 ) } const equalSubReturns = (a, b) => { return ( !hasSubReturn(a) || !hasSubReturn(b) || getSubReturn(a).has(UNKNOWN) || getSubReturn(b).has(UNKNOWN) || getSubReturn(a).has(ANY) || getSubReturn(b).has(ANY) || getSubReturn(a).difference(getSubReturn(b)).size === 0 ) } const equalSubTypesWithSubReturn = (a, b) => { return ( !hasSubType(a) || !hasSubReturn(b) || getSubType(a).has(UNKNOWN) || getSubReturn(b).has(UNKNOWN) || getSubType(a).has(ANY) || getSubReturn(b).has(ANY) || getSubType(a).difference(getSubReturn(b)).size === 0 ) } const isAtomABoolean = (atom) => atom === TRUE || atom === FALSE const checkPredicateName = (exp, rest) => { if (getSuffix(rest[0][VALUE]) === PREDICATE_SUFFIX) { const last = rest.at(-1) if (last[TYPE] !== APPLY && isLeaf(last) && !IsPredicate(last)) { if (!IsPredicate(last)) throw new TypeError( `Assigning predicate (ending in ?) variable (${ rest[0][VALUE] }) to an (${ STATIC_TYPES.ATOM }) that is not (or ${TRUE} ${FALSE}) or to another variable which is not a predicate (also ending in ?) or to a variable that is not (or true false nil) (${stringifyArgs( exp )}) (check #100)` ) } else if (last[0][0] === APPLY) { const application = last[0] if (application[VALUE] === KEYWORDS.CALL_FUNCTION) return checkPredicateName(exp, [rest[0], last.at(-1)]) else if (application[VALUE] !== KEYWORDS.IF && !IsPredicate(application)) throw new TypeError( `Assigning predicate (ending in ?) variable (${ application[VALUE] }) to another variable which is not a predicate (also ending in ?) or to a variable that is not (or true false nil) (${stringifyArgs( exp )}) (check #101)` ) else if (application[VALUE] === KEYWORDS.IF) { return ( checkPredicateName(exp, [rest[0], last[2]]) && checkPredicateName(exp, [rest[0], last[3]]) ) } } return true } return false } const checkPredicateNameDeep = (name, exp, rest, returns) => { if (returns[VALUE] === KEYWORDS.CALL_FUNCTION) { const fn = rest.at(-1).at(-1).at(-1) return checkPredicateName(exp, [ [WORD, name], isLeaf(fn) ? fn // when apply is a word (let x? (lambda (apply [] array:empty!))) : drillReturnType(fn, (r) => r[VALUE] === KEYWORDS.CALL_FUNCTION) // when apply is an annonymous lambda // (let fn? (lambda x (apply x (lambda x (array:empty! []))))) ]) } return checkPredicateName(exp, [[WORD, name], returns]) } const fillUknownArgs = (n) => Array.from({ length: n }) .fill(null) .map(() => ({ [STATS]: { retried: 0, [SIGNATURE]: PLACEHOLDER, [TYPE_PROP]: [UNKNOWN], [RETURNS]: [UNKNOWN], [ARGUMENTS]: [], [ARG_COUNT]: 0 } })) const getScopeNames = (scope) => { const scopeNames = [] let current = scope while (current) { if (current[SCOPE_NAME]) { scopeNames.push(current[SCOPE_NAME]) } current = Object.getPrototypeOf(current) } return scopeNames.reverse() } const withScope = (name, scope) => { const chain = getScopeNames(scope) return `${chain.length === 1 ? '; ' : ''}${chain.join(' ')} ${name}` } const retry = (stats, ctx, stack, cb, method = 'prepend') => { if ( (isUnknownNotAnyType(stats) || isUnknownNotAnyReturn(stats)) && stats.retried < MAX_RETRY_DEFINITION ) { stats.retried += 1 stagger(stack, method, ctx, cb) } } const once = (stats, ctx, stack, cb, method = 'prepend') => { if ( (isUnknownNotAnyType(stats) || isUnknownNotAnyReturn(stats)) && !stats.tried ) { stats.tried = true stagger(stack, method, ctx, cb) } } const retryArgs = (stats, ctx, stack, cb) => { if (stats.counter < MAX_ARGUMENT_RETRY) { stats.counter++ stagger(stack, 'prepend', ctx, cb) } } const IfApplyBranch = ({ leaf, branch, re, prop, ref, env, stack, exp, check }) => { switch (leaf[VALUE]) { case KEYWORDS.IF: return ifExpression({ re: re.slice(2), env, ref, prop, stack, exp, check }) case KEYWORDS.CREATE_ARRAY: setPropToReturnRef(ref[STATS], prop, initArrayType({ rem: re, env })) break case KEYWORDS.ANONYMOUS_FUNCTION: setPropToAbstraction(ref[STATS], prop) ref[STATS][RETURNS] = [UNKNOWN] ref[STATS][ARG_COUNT] = re.length - 2 ref[STATS][ARGUMENTS] = fillUknownArgs(re.length - 2) break case KEYWORDS.CALL_FUNCTION: if (re.at(-1)[TYPE] === WORD) { if (env[re.at(-1)[VALUE]] && re.at(-1)[VALUE] !== ref[STATS][SIGNATURE]) setPropToReturnRef(ref[STATS], prop, env[re.at(-1)[VALUE]][STATS]) } else { const returns = returnType(re.at(-1)) if (env[returns[VALUE]] && returns[VALUE] !== ref[STATS][SIGNATURE]) IfApplyBranch({ branch: env[returns[VALUE]], ref, env, prop, leaf: re.at(-1), re: re.at(-1).slice(2), stack, exp }) } break default: return setPropToReturnRef(ref[STATS], prop, branch[STATS]) } } const ifExpression = ({ re, env, ref, prop, stack, exp, check }) => { if (re[0][TYPE] === ATOM || re[1][TYPE] === ATOM) return setPropToAtom(ref[STATS], prop) // TODO check that both brancehs are predicates if one is else { const conc = isLeaf(re[0]) ? re[0] : re[0][0] const alt = isLeaf(re[1]) ? re[1] : re[1][0] const concequent = env[conc[VALUE]] const alternative = env[alt[VALUE]] // TODO make this more simple - it's so many different things just because types are functions or not // WHY not consiter making return types for everything if (concequent) if (conc[TYPE] === WORD) { return setPropToTypeRef(ref[STATS], prop, concequent[STATS]) } else if ( conc[TYPE] === APPLY && getType(concequent[STATS]) === APPLY && // Making sure the recursive function don't look for their own return type concequent[STATS][SIGNATURE] !== ref[STATS][SIGNATURE] ) { IfApplyBranch({ leaf: conc, branch: concequent, re: re[0], prop, env, ref, stack, exp, check }) } if (alternative) { if (alt[TYPE] === WORD) { return setPropToTypeRef(ref[STATS], prop, alternative[STATS]) } else if ( alt[TYPE] === APPLY && getType(alternative[STATS]) === APPLY && // Making sure the recursive function don't look for their own return type alternative[STATS][SIGNATURE] !== ref[STATS][SIGNATURE] ) { IfApplyBranch({ leaf: alt, branch: alternative, re: re[1], prop, env, ref, stack, exp, check }) } } } } const resolveCondition = ({ rem, name, env, exp, prop, stack, check }) => { const ret = rem[0] const re = rem.slice(2) const ref = env[name] checkPredicateName(exp, [[WORD, name], isLeaf(re[0]) ? re[0] : re[0][0]]) checkPredicateName(exp, [[WORD, name], isLeaf(re[1]) ? re[1] : re[1][0]]) switch (ret[VALUE]) { case KEYWORDS.IF: ifExpression({ re, env, ref, prop, exp, stack, check }) break default: if (env[ret[VALUE]]) setPropRef(ref[STATS], prop, env[ret[VALUE]][STATS]) else stagger(stack, 'append', exp, () => { if (env[ret[VALUE]]) setPropRef(ref[STATS], prop, env[ret[VALUE]][STATS]) }) break } } const resolveSetter = (first, rest, env, stack) => { if ( getSuffix(first[VALUE]) === MUTATION_SUFFIX && MUTATORS_SET.has(first[VALUE]) && rest[0] && isLeaf(rest[0]) && rest[0][TYPE] !== ATOM && env[rest[0][VALUE]] ) { const name = rest[0][VALUE] const current = env[name] const right = isLeaf(rest.at(-1)) ? rest.at(-1) : rest.at(-1)[0] const currentSubType = hasSubType(current[STATS]) ? getSubType(current[STATS]) : new Set([UNKNOWN]) switch (right[TYPE]) { case ATOM: if ( !currentSubType.has(ANY) && !currentSubType.has(UNKNOWN) && !currentSubType.has(NUMBER) ) throw new TypeError( `Incorrect array type at (${ first[VALUE] }). ${name} is (${formatSubType( getTypes(current[STATS]) )}) but insertion is (${formatSubType([ ATOM, NUMBER_SUBTYPE() ])}) (${stringifyArgs([first, rest])}) (check #199)` ) current[STATS][TYPE_PROP][1] = NUMBER_SUBTYPE() break case WORD: if (env[right[VALUE]]) { if (hasSubType(env[right[VALUE]][STATS])) { if (currentSubType.has(UNKNOWN)) current[STATS][TYPE_PROP][1] = new Set([ ...getSubType(env[right[VALUE]][STATS]) ]) else if (!equalSubTypes(current[STATS], env[right[VALUE]][STATS])) throw new TypeError( `Incorrect array type at (${ first[VALUE] }). ${name} is (${formatSubType( getTypes(current[STATS]) )}) but insertion is (${formatSubType( getTypes(env[right[VALUE]][STATS]) )}) (${stringifyArgs([first, rest])}) (check #198)` ) current[STATS][TYPE_PROP][1] = new Set( getSubType(env[right[VALUE]][STATS]) ) } else retry(env[right[VALUE]][STATS], [first[VALUE], rest], stack, () => resolveSetter(first, rest, env, stack) ) } break case APPLY: if (env[right[VALUE]]) { if (right[VALUE] === KEYWORDS.CREATE_ARRAY) { current[STATS][TYPE_PROP][1] = initArrayType({ rem: rest.at(-1), env })[RETURNS][1] break } if (hasSubReturn(env[right[VALUE]][STATS])) { if (currentSubType.has(UNKNOWN)) current[STATS][TYPE_PROP][1] = new Set([ ...getSubReturn(env[right[VALUE]][STATS]) ]) else if ( !equalSubTypesWithSubReturn( current[STATS], env[right[VALUE]][STATS] ) ) throw new TypeError( `Incorrect array type at (${ first[VALUE] }). ${name} is (${formatSubType( getTypes(current[STATS]) )}) but insertion is (${formatSubType( getReturns(env[right[VALUE]][STATS]) )}) (${stringifyArgs([first, rest])}) (check #198)` ) current[STATS][TYPE_PROP][1] = new Set([ ...getSubReturn(env[right[VALUE]][STATS]) ]) } // else // retry(env[right[VALUE]][STATS], [first[VALUE], rest], stack, () => // resolveSetter(first, rest, env, stack) // ) } break } setTypeToCollection(current[STATS]) // // Retry setting the sub-type if infered it out later // if (!hasSubType(current[STATS]) || getSubType(current[STATS]).has(UNKNOWN)) // retry(current[STATS], [first[VALUE], rest], stack, () => // resolveSetter(first, rest, env, stack) // ) } } const resolveGetter = ({ rem, prop, name, env }) => { const array = isLeaf(rem[1]) ? rem[1] : rem[1][0] if (!env[array[VALUE]] || !env[name]) return true switch (array[TYPE]) { case APPLY: if (hasSubType(env[array[VALUE]][STATS])) { const rightSub = getSubReturn(env[array[VALUE]][STATS]) const isAtom = rightSub.has(NUMBER) || rightSub.has(BOOLEAN) const isCollection = rightSub.has(COLLECTION) if (isAtom && !isCollection) { setPropToAtom(env[name][STATS], prop) setPropToSubReturn(env[name][STATS], prop, env[array[VALUE]][STATS]) } else if (!isAtom && isCollection) { setPropToReturn(env[name][STATS], prop, env[array[VALUE]][STATS]) // TODO: handle this nested array overwrite better if (getSubReturn(env[array[VALUE]][STATS]).has(COLLECTION)) setPropToSubReturn(env[name][STATS], prop, { [RETURNS]: [COLLECTION, UNKNOWN_SUBTYPE()] }) } else return false } else return false break case WORD: { if (hasSubType(env[array[VALUE]][STATS])) { const rightSub = getSubType(env[array[VALUE]][STATS]) const isAtom = rightSub.has(ATOM) || rightSub.has(NUMBER) || rightSub.has(BOOLEAN) const isCollection = rightSub.has(COLLECTION) if (isAtom && !isCollection) { setPropToAtom(env[name][STATS], prop) setPropToSubType(env[name][STATS], prop, env[array[VALUE]][STATS]) } else if (!isAtom && isCollection) { setPropToType(env[name][STATS], prop, env[array[VALUE]][STATS]) // TODO: handle this nested array overwrite better if (getSubType(env[array[VALUE]][STATS]).has(COLLECTION)) setPropToSubType(env[name][STATS], prop, { [TYPE_PROP]: [COLLECTION, UNKNOWN_SUBTYPE()] }) } else return false } else return false } break } return true } const initArrayType = ({ rem, env }) => { const ret = rem .slice(1) .map((x) => isLeaf(x) ? x[TYPE] === WORD ? env[x[VALUE]] ? getTypes(env[x[VALUE]][STATS]) : [UNKNOWN] : [x[TYPE], x[TYPE] === ATOM ? NUMBER_SUBTYPE() : new Set([UNKNOWN])] : env[x[0][VALUE]] ? getReturns(env[x[0][VALUE]][STATS]) : [UNKNOWN] ) const known = ret.find((x) => x[0] !== ANY && x[0] !== UNKNOWN) if ( known && ret.length && !ret.some((x) => known[0] !== x[0] || known.length !== x.length) ) { const [main, sub] = ret[0] return { [TYPE_PROP]: [APPLY], [RETURNS]: [COLLECTION, new Set(sub ? [...sub] : [main])] } } else return { [TYPE_PROP]: [APPLY], [RETURNS]: [COLLECTION, new Set([UNKNOWN])] } } const resolveReturnType = ({ returns, rem, stack, prop, exp, name, env, check }) => { if (returns[TYPE] === ATOM) setPropToAtom(env[name][STATS], prop) else if (returns[TYPE] === WORD) { if (env[returns[VALUE]]) { if (!isUnknownType(env[returns[VALUE]][STATS])) setPropToType(env[name][STATS], prop, env[returns[VALUE]][STATS]) else once(env[name][STATS], exp, stack, () => { setPropToTypeRef(env[name][STATS], prop, env[returns[VALUE]][STATS]) if (isUnknownProp(env[name][STATS], prop)) { // TODO: DRY const index = env[name][STATS][ARGUMENTS] ? env[name][STATS][ARGUMENTS].findIndex( (x) => x[STATS][SIGNATURE] === returns[VALUE] ) : -1 if (index >= 0) { setReturnToGeneric(env[name][STATS], index) return true } else if (!env[returns[VALUE]]) return false } }) } } else { switch (returns[VALUE]) { case KEYWORDS.CREATE_ARRAY: setPropToCollection(env[name][STATS], prop) setPropToSubReturn(env[name][STATS], prop, initArrayType({ rem, env })) break case KEYWORDS.IF: resolveCondition({ rem, name, env, exp, prop, stack, check }) break default: { if ( !GETTERS_SET.has(name) && GETTERS_SET.has(returns[VALUE]) && !resolveGetter({ rem, prop, name, env }) ) return retry(env[name][STATS], [returns, env], stack, () => { resolveReturnType({ returns, rem, stack, prop, exp, name, env, check }) }) checkPredicateNameDeep(name, exp, exp.slice(1), returns) // TODO: DRY const index = env[name][STATS][ARGUMENTS] ? env[name][STATS][ARGUMENTS].findIndex( (x) => x[STATS][SIGNATURE] === returns[VALUE] ) : -1 if (index >= 0) { setReturnToGeneric(env[name][STATS], index) return true } else if (!env[returns[VALUE]]) return false else if (getType(env[returns[VALUE]][STATS]) === APPLY) { if (returns[TYPE] === WORD) setReturnToAbbstraction(env[name][STATS]) else { // ALWAYS APPLY // rest.at(-1)[0][TYPE] === APPLY // Here is upon application to store the result in the variable if (isUnknownType(env[name][STATS])) stagger(stack, 'prepend', exp, () => { if (isGenericReturn(env[returns[VALUE]][STATS])) { // env[name][STATS][TYPE_PROP] = const genericReturn = rem.slice(1)[env[returns[VALUE]][STATS][RETURNS][2]] const head = isLeaf(genericReturn) ? genericReturn : genericReturn[0] switch (head[TYPE]) { case ATOM: setTypeToAtom(env[name][STATS]) break case WORD: if (env[head[VALUE]]) setStatsRef(env[name], env[head[VALUE]]) break case APPLY: switch (head[VALUE]) { case KEYWORDS.ANONYMOUS_FUNCTION: { // TODO figure out a better way to do this // This is insitialisation of identity or any other // function that returns it's argument // Redifine the variable but since it's an error doing that // Delete it first delete env[name] check( [ [APPLY, KEYWORDS.DEFINE_VARIABLE], [WORD, name], genericReturn ], env, exp ) // const n = genericReturn.length // setTypeToAbstraction(env[name][STATS]) // env[name][STATS][ARG_COUNT] = n - 2 // env[name][STATS][ARGUMENTS] = fillUknownArgs( // n - 2 // ) // checkReturnType({ // exp: [genericReturn], // stack, // name, // env, // check // }) } break case KEYWORDS.CREATE_ARRAY: { setTypeToCollection(env[name][STATS]) setPropToSubReturn( env[name][STATS], TYPE_PROP, initArrayType({ rem: genericReturn, env }) ) } break } default: if (env[head[VALUE]]) setTypeToReturn( env[name][STATS], env[head[VALUE]][STATS] ) break } } else setTypeToReturnRef( env[name][STATS], env[returns[VALUE]][STATS] ) }) else { // if (SPECIAL_FORMS_SET.has(returns[VALUE])) // setReturn(env[name][STATS], env[returns[VALUE]][STATS]) // else setReturnRef(env[name][STATS], env[returns[VALUE]][STATS]) } } } } break } } return true } const checkReturnType = ({ exp, stack, name, env, check }) => { const last = exp.at(-1).at(-1) const body = hasApplyLambdaBlock(last) ? last.at(-1).at(-1) : last const rem = hasBlock(body) ? body.at(-1) : body const returns = isLeaf(rem) ? rem : rem[0] return resolveReturnType({ returns, rem, prop: RETURNS, exp, name, env, stack, check }) } const stagger = (stack, method, data, fn) => { stack[method]({ data, fn }) } export const typeCheck = (ast, ctx = SPECIAL_FORM_TYPES) => { const Types = new Map() const stack = new Brr() let scopeIndex = 0 // TODO also handle casting const match = ({ rest, args, i, env, scope, exp }) => { const first = exp[0] const actual = rest[i][0][VALUE] === KEYWORDS.CREATE_ARRAY ? initArrayType({ rem: rest[i], env }) : env[rest[i][0][VALUE]][STATS] const expected = args[i][STATS] retryArgs(args[i][STATS], exp, stack, () => match({ rest, args, i, env, scope, exp }) ) if (!isUnknownType(expected) && !isUnknownReturn(actual)) if (!equalsTypeWithReturn(expected, actual)) throw new TypeError( `Incorrect type of argument (${i}) for (${ first[VALUE] }). Expected (${toTypeNames( getType(expected) )}) but got (${toTypeNames(getReturn(actual))}) (${stringifyArgs( exp )}) (check #16)` ) else if (!equalSubTypesWithSubReturn(expected, actual)) { throw new TypeError( `Incorrect type of argument (${i}) for (${ first[VALUE] }). Expected (${formatSubType( getTypes(expected) )}) but got (${formatSubType(getReturns(actual))}) (${stringifyArgs( exp )}) (check #206)` ) } else { switch (getType(expected)) { // almost exclusively for anonymous lambdas case APPLY: { const argsN = rest[i].length - 2 if ( env[rest[i][0][VALUE]][STATS][SIGNATURE] === KEYWORDS.ANONYMOUS_FUNCTION ) { if (argsN !== args[i][STATS][ARG_COUNT]) throw new TypeError( `Incorrect number of arguments for (${ args[i][STATS][SIGNATURE] }) the (${KEYWORDS.ANONYMOUS_FUNCTION}) argument of (${ first[VALUE] }) at position (${i}). Expected (= ${ args[i][STATS][ARG_COUNT] }) but got ${argsN} (${stringifyArgs(exp)}) (check #777)` ) else { // ANONYMOUS LAMBDAS TYPE CHECKING const local = Object.create(env) const lambdaName = `${ANONYMOUS_FUNCTION_TYPE_PREFIX}${i}::${++scopeIndex}` check( [ [APPLY, KEYWORDS.DEFINE_VARIABLE], [WORD, lambdaName], rest[i] ], local, scope ) // TODO delete this maybe // #C2 // It will not be possilbe to know return type const match1 = () => { const actual = local[lambdaName] const expected = args[i] if ( !isUnknownReturn(expected[STATS]) && !isUnknownReturn(actual[STATS]) && !equalReturns(expected[STATS], actual[STATS]) ) throw new TypeError( `Incorrect return type for (${ expected[STATS][SIGNATURE] }) the (${KEYWORDS.ANONYMOUS_FUNCTION}) argument of (${ first[VALUE] }) at position (${i}). Expected (${formatSubType( getReturns(expected[STATS]) )}) but got (${formatSubType( getReturns(actual[STATS]) )}) (${stringifyArgs(exp)}) (check #779)` ) else if (!equalSubReturns(expected[STATS], actual[STATS])) throw new TypeError( `Incorrect return type for (${ expected[STATS][SIGNATURE] }) the (${KEYWORDS.ANONYMOUS_FUNCTION}) argument of (${ first[VALUE] }) at position (${i}). Expected (${formatSubType( getReturns(expected[STATS]) )}) but got (${formatSubType( getReturns(actual[STATS]) )}) (${stringifyArgs(exp)}) (check #783)` ) else retry( actual[STATS], [[WORD, lambdaName], local], stack, match1 ) } match1() for (let j = 0; j < args[i][STATS][ARGUMENTS].length; ++j) { const actual = local[lambdaName][STATS][ARGUMENTS][j] const expected = args[i][STATS][ARGUMENTS][j] if ( !isUnknownType(actual[STATS]) && !isUnknownType(expected[STATS]) && !equalTypes(actual[STATS], expected[STATS]) ) throw new TypeError( `Incorrect type for (${KEYWORDS.ANONYMOUS_FUNCTION}) (${ args[i][STATS][SIGNATURE] }) argument at position (${j}) named as (${ local[lambdaName][STATS][ARGUMENTS][j][STATS][ SIGNATURE ] }). Expected (${toTypeNames( getType(expected[STATS]) )}) but got (${toTypeNames( getType(actual[STATS]) )}) (${stringifyArgs(exp)}) (check #780)` ) else if (!equalSubReturns(expected[STATS], actual[STATS])) throw new TypeError( `Incorrect return type for (${ expected[STATS][SIGNATURE] }) the (${KEYWORDS.ANONYMOUS_FUNCTION}) argument of (${ first[VALUE] }) at position (${i}). Expected (${toTypeNames( getReturn(expected[STATS]) )}) but got (${toTypeNames( getReturn(actual[STATS]) )}) (${stringifyArgs(exp)}) (check #784)` ) // else // retry( // actual[STATS], // [[WORD, lambdaName], local], // stack, // match2 // ) } } } else { // TODO fix curry: lambdas enter here as undefined } } break // case ATOM: // case COLLECTION: // break } } else if (isUnknownType(expected)) retry(args[i][STATS], [first, env], stack, () => match({ rest, args, i, env, scope, exp }) ) } const check = (exp, env, scope) => { const [first, ...rest] = isLeaf(exp) ? [exp] : exp if (first === undefined) throw new TypeError( `(${KEYWORDS.ANONYMOUS_FUNCTION}) invocation with missing (Abstraction) name () Provide an (Abstraction) name as the (1) argument.` ) const isSpecial = SPECIAL_FORMS_SET.has(first[VALUE]) || STATIC_TYPES_SET.has(first[VALUE]) switch (first[TYPE]) { case WORD: if (!isSpecial) stagger(stack, 'append', [first, env], () => { // Figure out how to determine if varible is define after it's used if (env[first[VALUE]] === undefined) { throw new TypeError( `Trying to access undefined variable ${first[VALUE]} (check #11)` ) } }) break case ATOM: break case APPLY: { switch (first[VALUE]) { // Var // --------------- case KEYWORDS.DEFINE_VARIABLE: if (rest.length !== 2) throw new SyntaxError( `Incorrect number of arguments for (${ first[VALUE] }). Expected (= 2) but got ${rest.length} (${stringifyArgs( exp )})` ) // TODO check leT define types const name = rest[0][VALUE] if (env.hasOwnProperty(name)) throw new ReferenceError( `Attempting to rediclare (${name}) which was previously declared in this scope (${stringifyArgs( exp )})` ) if (name in env) { // const [head, ...tail] = isLeaf(rest.at(-1)) // ? [rest.at(-1)] // : rest.at(-1) // const ref = env[name] // if (ref) { // if (getType(ref[STATS]) === APPLY && head[TYPE] !== APPLY) // throw new TypeError( // `Miss-matching type for (${name}) predifined expected type is (${toTypeNames( // APPLY // )}) but got (${stringifyArgs(exp)})` // ) // const returns = deepLambdaReturn( // hasBlock(tail) ? tail.at(-1) : tail, // (result) => result[VALUE] !== KEYWORDS.IF // ) // const [rhead] = isLeaf(returns) ? [returns] : returns // const rightRef = env[rhead[VALUE]] // if (rightRef) // if ( // !equalReturns(ref[STATS], rightRef[STATS]) || // !equalSubReturns(ref[STATS], rightRef[STATS]) // ) // throw new TypeError( // `Miss-matching return type for (${name}) predifined expected return is (${formatSubType( // getReturns(ref[STATS]) // )}) but got (${formatSubType( // getReturns(rightRef[STATS]) // )}) (${stringifyArgs(exp)})` // ) // } Types.set(withScope(name, env), () => formatType(name, env)) break } // Predicate name consistency const rightHand = rest.at(-1) const isApply = rightHand && rightHand[0] && rightHand[0][TYPE] === APPLY if ( isApply && rightHand[0][VALUE] === KEYWORDS.ANONYMOUS_FUNCTION ) { validateLambda(rightHand, name) const n = rightHand.length env[name] = { [STATS]: { [TYPE_PROP]: [APPLY], [SIGNATURE]: name, retried: 0, counter: 0, [ARG_COUNT]: n - 2, [ARGUMENTS]: fillUknownArgs(n - 2), [RETURNS]: [UNKNOWN] } } check(rightHand, env, exp) if (isUnknownReturn(env[name][STATS])) retry(env[name][STATS], exp, stack, () => check(rightHand, env, exp) ) } else if ( isApply && rightHand[0][VALUE] === KEYWORDS.CREATE_ARRAY ) { const right = rightHand[0] env[name] = { [STATS]: { [TYPE_PROP]: [COLLECTION], [RETURNS]: [UNKNOWN], [SIGNATURE]: name, retried: 0, counter: 0 } } setTypeToReturn(env[name][STATS], env[right[VALUE]][STATS]) const resolve = () => { const body = rightHand const rem = hasBlock(body) ? body.at(-1) : body const returns = isLeaf(rem) ? rem : rem[0] resolveReturnType({ stack, returns, rem, prop: TYPE_PROP, exp, env, name, check }) } resolve() if (isUknownSubType(env[name][STATS])) stagger(stack, 'prepend', exp, () => { once(env[name][STATS], exp, stack, () => { setPropToCollection(env[name][STATS], TYPE_PROP) setPropToSubReturn( env[name][STATS], TYPE_PROP, initArrayType({ rem: rightHand, env }) ) }) }) check(rightHand, env, scope) } else { checkPredicateName(exp, rest) const isLeafNode = isLeaf(rightHand) if (isLeafNode && rightHand[TYPE] === WORD) { // TODO make sure this prevents the assigment all together if (env[rest[1][VALUE]] === undefined) throw new TypeError( `Trying to access undefined variable ${rest[1][VALUE]} (check #22)` ) // Used to be checkin if it's an assigment to a special form // but this should not cause problems // env[name] = SPECIAL_FORMS_SET.has(rest[1][VALUE]) // ? structuredClone(env[rest[1][VALUE]]) // : env[rest[1][VALUE]] // FULL REFF ASSIGMENT env[name] = env[rest[1][VALUE]] } else if (isLeafNode && rightHand[TYPE] === ATOM) { // DECLARATION of ATOM env[name] = { [STATS]: { [SIGNATURE]: name, retried: 0, counter: 0, [TYPE_PROP]: [ATOM, NUMBER_SUBTYPE()], [RETURNS]: [ATOM, NUMBER_SUBTYPE()] } } } else if (rightHand[0]) { const right = rightHand[0] // DECLARATION env[name] = { [STATS]: { retried: 0, counter: 0, [SIGNATURE]: name, [TYPE_PROP]: [UNKNOWN], [RETURNS]: [UNKNOWN] } } const type = isLeafNode ? right[TYPE] : env[right[VALUE]] == undefined ? UNKNOWN : env[right[VALUE]][STATS][RETURNS][0] if (type !== UNKNOWN) setTypeToReturn(env[name][STATS], env[right[VALUE]][STATS]) const resolve = () => { const body = rightHand const rem = hasBlock(body) ? body.at(-1) : body const returns = isLeaf(rem) ? rem : rem[0] resolveReturnType({ stack, returns, rem, prop: TYPE_PROP, exp, env, name, check }) } resolve() if (isUnknownType(env[name][STATS])) once(env[name][STATS], exp, stack, () => resolve()) } check(rightHand, env, scope) } Types.set(withScope(name, env), () => formatType(name, env)) break case KEYWORDS.ANONYMOUS_FUNCTION: { validateLambda(exp) const params = exp.slice(1, -1) const copy = Object.create(env) if (Array.isArray(scope[1]) && scope[1][TYPE] === WORD) copy[SCOPE_NAME] = scope[1][VALUE] else copy[SCOPE_NAME] = ++scopeIndex const ref = env[copy[SCOPE_NAME]] for (let i = 0; i < params.length; ++i) { const param = params[i] // TODO move this somewhere else if (!isLeaf(param)) throw new TypeError( `Invalid body for (${ first[VALUE] }) if it takes more than one expression it must be wrapped in a (${ KEYWORDS.BLOCK }) (${stringifyArgs(exp)}) (check #666)` ) copy[param[VALUE]] = { [STATS]: { [IS_ARGUMENT]: true, [SIGNATURE]: param[VALUE], [TYPE_PROP]: [UNKNOWN], [RETURNS]: [UNKNOWN], [ARGUMENTS]: [], argIndex: i, retried: 0, counter: 0