preserves/implementations/javascript/packages/schema/src/compiler/predicate.ts

99 lines
5.1 KiB
TypeScript

import { refPosition } from '../reader';
import * as M from '../meta';
import { block, fnblock, Item, opseq, parens, seq } from './block';
import { FunctionContext } from './context';
export function predicateFor(ctx: FunctionContext, v: string, p: M.Definition, recordOkAsTuple = false): Item
{
switch (p.label) {
case M.$atom:
switch (p[0]) {
case M.$Boolean: return `typeof ${v} === 'boolean'`;
case M.$Float: return `_.Float.isSingle(${v})`;
case M.$Double: return `_.Float.isDouble(${v})`;
case M.$SignedInteger: return `typeof ${v} === 'number'`;
case M.$String: return `typeof ${v} === 'string'`;
case M.$ByteString: return `_.Bytes.isBytes(${v})`;
case M.$Symbol: return `typeof ${v} === 'symbol'`;
}
case M.$lit:
return `_.is(${v}, ${ctx.mod.literal(p[0])})`;
case M.$ref:
return M.lookup(refPosition(p), p, ctx.mod.env,
(_p) => `is${M.Ref._.name(p).description!}(${v})`,
(pp) => predicateFor(ctx, v, pp),
(modId, modPath, _p) => {
ctx.mod.imports.add([modId, modPath]);
return `${modId}.is${M.Ref._.name(p).description!}(${v})`;
});
case M.$or: {
const alts = p[0];
const recs = alts.map(p => ctx.mod.derefPattern(p));
if (recs.length > 1 && recs.every(pp => pp.label === M.$rec)) {
return seq(
`_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v}) && `,
parens(opseq('false', ' || ',
... recs.map(r =>
(r.label !== M.$rec) ? '' : parens(seq(
predicateFor(ctx, `${v}.label`, M.unname(r[0])),
' && ',
predicateFor(ctx, v, M.unname(r[1]), true)))))));
} else {
return opseq('false', ' || ', ... p[0].map(pp => predicateFor(ctx, v, pp[1])));
}
}
case M.$and:
return opseq('true', ' && ', ...p[0].map(pp => predicateFor(ctx, v, M.unname(pp))));
case M.$pointer:
return `_.isPointer(${v})`;
case M.$rec:
return opseq('true', ' && ',
`_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`,
predicateFor(ctx, `${v}.label`, M.unname(p[0])),
predicateFor(ctx, v, M.unname(p[1]), true));
case M.$tuple:
return opseq('true', ' && ',
... (recordOkAsTuple ? []
: [`_.Array.isArray(${v})`, `!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`]),
`(${v}.length === ${p[0].length})`,
...p[0].map((pp, i) => predicateFor(ctx, `${v}[${i}]`, M.unname(pp))));
case M.$tuple_STAR_:
return opseq('true', ' && ',
... (recordOkAsTuple ? []
: [`_.Array.isArray(${v})`, `!_.Record.isRecord<_val, _.Tuple<_val>, _ptr>(${v})`]),
`(${v}.length >= ${p[0].length})`,
seq(p[0].length > 0 ? `${v}.slice(${p[0].length})` : v,
`.every(v => `,
parens(predicateFor(ctx, 'v', M.unname(p[1]))),
`)`),
...p[0].map((pp, i) => predicateFor(ctx, `${v}[${i}]`, M.unname(pp))));
case M.$setof:
return opseq('true', ' && ',
`_.Set.isSet<_val>(${v})`,
fnblock(
seq(`for (const vv of ${v}) `, block(
seq('if (!(', predicateFor(ctx, 'vv', p[0]), ')) return false'))),
seq('return true')));
case M.$dictof:
return opseq('true', ' && ',
`_.Dictionary.isDictionary<_ptr>(${v})`,
fnblock(
seq(`for (const e of ${v}) `, block(
seq('if (!(', predicateFor(ctx, 'e[0]', p[0]), ')) return false'),
seq('if (!(', predicateFor(ctx, 'e[1]', p[1]), ')) return false'))),
seq('return true')));
case M.$dict:
return opseq('true', ' && ',
`_.Dictionary.isDictionary<_ptr>(${v})`,
... Array.from(p[0]).map(([k, vp]) => {
const tmp = ctx.gentemp();
return parens(seq(
`(${tmp} = ${v}.get(${ctx.mod.literal(k)})) !== void 0 && `,
predicateFor(ctx, tmp, M.unname(vp))));
}));
default:
((_p: never) => {})(p);
throw new Error("Unreachable");
}
}