fez-lisp
Version:
Lisp interpreted & compiled to JavaScript
1,401 lines (1,397 loc) • 70.7 kB
JavaScript
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