function getType(stmt, ctx, returnCtx=false) {
if (typeof ctx === 'undefined') {
return getType(stmt, getDefaultCtx());
} else if (!Array.isArray(stmt)) {
throw new Error('Expecting statement to be an array');
} else if (stmt[0] === 'call') {
const head = getType(stmt[1], ctx);
const args = stmt.slice(2).map(arg => getType(arg, ctx));
const argsTypes = args.map(arg => arg.type.name);
const argsSignature = '(' + argsTypes.join(', ') + ')';
if (!head instanceof Typed) throw new Error();
if (!args.every(x => x instanceof Typed)) throw new Error();
const allArgsHasValue = args.every(arg => arg.hasValue());
if (head.type === types.polymorphic) {
for (const entry of head.value) {
if (checkSignature(entry.args, args)) {
if (allArgsHasValue) {
return new Typed(entry.resType, entry.js(...args.map(arg => arg.value)));
} else {
return new Typed(entry.resType);
}
}
}
const funName = head.value[0].name;
throw new Error(`No signature matches ${argsSignature} for function ${funName}`);
} else if (head.type === types.function) {
if (allArgsHasValue) {
return head.value(...args);
} else {
return new Typed(head.value.obj.meta.resType);
}
} else {
throw new Error();
}
} else if (stmt[0] === 'fun') {
const resFun = (...args) => {
const ctx = getDefaultCtx();
Object.entries(stmt[1].meta.imports).forEach(([importAs, fun]) => {
ctx[importAs] = getType(['fun', fun]);
});
if (!checkSignature(stmt[1].meta.args, args)) {
const argsTypes = args.map(arg => arg.type.name);
const argsSignature = '(' + argsTypes.join(', ') + ')';
throw new Error(`Function ${stmt[1].meta.name} signature mismatch: got ${argsSignature}`);
}
Object.entries(stmt[1].meta.args).forEach(([argName, argType], argNum) => {
ctx[argName] = args[argNum];
});
const res = getType(stmt[1].body, ctx);
if (res.type === types.assignments) {
if (returnCtx) {
return res.value(ctx);
} else {
return Object.values(res.value(ctx)).at(-1);
}
} else {
return res;
}
};
resFun.obj = stmt[1];
return new Typed(types.function, resFun);
} else if (stmt[0] === 'name') {
return ctx[stmt[1]];
} else if (stmt[0] === 'const') {
const s = stmt[1];
if (/^[0-9]*\.[0-9]*$/g.test(s)) {
return new Typed(types.float, Number(s));
} else if (/^[0-9]+$/g.test(s)) {
return new Typed(types.int, Number(s));
} else if (['true', 'false'].includes(s)) {
return new Typed(types.bool, s === 'true' ? true : false);
} else {
throw new Error(`Unexpected constant: "${s}"`);
}
} else if (stmt[0] === 'assignments') {
return new Typed(types.assignments, ctx => {
const res = {...ctx};
stmt.slice(1).forEach(([name, expr]) => {
const curRes = getType(expr, res);
if (curRes.type === types.assignments) {
res = curRes(res);
} else {
res[name] = curRes;
}
});
return res;
});
} else {
throw new Error(`Unknown statement: "${stmt[0]}"`);
}
}