import { FunctionContext } from "./context"; import * as M from '../meta'; import { anglebrackets, block, brackets, Item, opseq, parens, seq } from "./block"; import { typeFor } from './type'; import { refPosition } from "../reader"; import { predicateFor } from "./predicate"; function decodeCompound(ctx: FunctionContext, p: M.Pattern, kFail: () => Item[], kAcc: (temp: string) => Item[]): Item { const t = ctx.gentemp(); return seq(`while (!d.closeCompound()) `, ctx.block(() => [ seq(`${t} = void 0`), ... decoderFor(ctx, p, t), seq(`if (${t} === void 0) `, block(... kFail(), seq(`break`))), ... kAcc(t)])); } function decoderForTuple(ctx: FunctionContext, tuplePattern: M.Pattern, ps: M.Pattern[], dest: string, recordFields: boolean, variablePattern: M.Pattern | undefined): Item[] { const temps = ctx.gentemps(ps.length); function loop(i: number): Item[] { if (i < ps.length) { return [... decoderFor(ctx, ps[i], temps[i]), seq(`if (${temps[i]} !== void 0) `, ctx.block(() => loop(i + 1)))]; } else { if (variablePattern === void 0) { return [seq(`if (d.closeCompound()) ${dest} = `, brackets(... temps), ` as `, typeFor(ctx.mod, tuplePattern))]; } else { return [block( seq(`let vN: `, typeFor(ctx.mod, tuplePattern), ` | undefined = `, brackets(... temps)), decodeCompound(ctx, variablePattern, () => [`vN = void 0`], (t) => [`vN.push(${t})`]), seq(`${dest} = vN`))]; } } } return recordFields ? loop(0) : [seq(`if (d.openSequence()) `, ctx.block(() => loop(0)))]; } export function decoderFor(ctx: FunctionContext, p: M.Definition, dest: string, recordFields = false): Item[] { switch (p.label) { case M.$atom: switch (p[0]) { case M.$Boolean: return [`${dest} = d.nextBoolean()`]; case M.$Float: return [`${dest} = d.nextFloat()`]; case M.$Double: return [`${dest} = d.nextDouble()`]; case M.$SignedInteger: return [`${dest} = d.nextSignedInteger()`]; case M.$String: return [`${dest} = d.nextString()`]; case M.$ByteString: return [`${dest} = d.nextByteString()`]; case M.$Symbol: return [`${dest} = d.nextSymbol()`]; } case M.$lit: { let n: string; switch (typeof p[0]) { case 'boolean': n = `d.nextBoolean()`; break; case 'string': n = `d.nextString()`; break; case 'number': n = `d.nextSignedInteger()`; break; case 'symbol': n = `d.nextSymbol()`; break; default: n = `d.next()`; break; } return [`${dest} = _.asLiteral(${n}, ${ctx.mod.literal(p[0])})`]; } case M.$ref: return M.lookup(refPosition(p), p, ctx.mod.env, (_p) => [`${dest} = decode${p[1].description!}(d)`], (p) => decoderFor(ctx, p, dest), (modId, modPath,_p) => { ctx.mod.imports.add([modId, modPath]); return [`${dest} = ${modId}.decode${p[1].description!}(d)`]; }); 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)) { // Hoist the record check up. // This is pretty hacky. If we lift the level of // discourse a little, we can do this // automatically and generically... return [seq(`if (d.openRecord()) `, ctx.block(() => { const label = ctx.gentemp(); const mark = ctx.gentemp(); function loop(i: number): Item[] { const alt = recs[i]; if (alt.label !== M.$rec) throw new Error("Internal error"); // avoid a cast return [ seq(`if (`, predicateFor(ctx, label, M.unname(alt[0])), `) `, ctx.block(() => { const fs = ctx.gentemp(); return [...decoderFor(ctx, M.unname(alt[1]), fs, true), seq(`if (${fs} !== void 0) ${dest} = _.Record`, anglebrackets(typeFor(ctx.mod, M.unname(alt[0])), typeFor(ctx.mod, M.unname(alt[1]))), parens(seq(label, ` as any`), seq(fs, ` as any`)))]; })), ... (i < recs.length - 1) ? [seq(`if (${dest} === void 0) `, ctx.block(() => [`d.restoreMark(${mark})`, ... loop(i + 1)]))] : [], ]; } return [seq(`${label} = d.next()`), seq(`${mark} = d.mark()`), ... loop(0)]; }))]; } else { switch (alts.length) { case 0: return []; // assume dest is already void 0 case 1: return decoderFor(ctx, alts[0][1], dest); default: { const mark = ctx.gentemp(); function loop(i: number): Item[] { return [ ... decoderFor(ctx, alts[i][1], dest), ... (i < alts.length - 1) ? [seq(`if (${dest} === void 0) `, ctx.block(() => [`d.restoreMark(${mark})`, ... loop(i + 1)]))] : [], ]; } return [`${mark} = d.mark()`, ... loop(0)]; } } } } case M.$and: switch (p[0].length) { case 0: return [`${dest} = d.next()`]; case 1: return decoderFor(ctx, M.unname(p[0][0]), dest); default: { const [pp0, ... ppN] = p[0]; return [...decoderFor(ctx, M.unname(pp0), dest), seq(`if (!`, opseq('true', ' && ', ... ppN.map(pp => predicateFor(ctx, dest, M.unname(pp)))), `) ${dest} = void 0`)]; } } case M.$pointer: return [`${dest} = _decodePtr(d)`]; case M.$rec: // assume dest is already void 0 return [seq(`if (d.openRecord()) `, ctx.block(() => { const label = ctx.gentemp(); return [...decoderFor(ctx, M.unname(p[0]), label), seq(`if (${label} !== void 0) `, ctx.block(() => { const fs = ctx.gentemp(); return [...decoderFor(ctx, M.unname(p[1]), fs, true), seq(`if (${fs} !== void 0) ${dest} = _.Record`, anglebrackets(typeFor(ctx.mod, M.unname(p[0])), typeFor(ctx.mod, M.unname(p[1]))), parens(seq(label, ` as any`), seq(fs, ` as any`)))]; }))]; }))]; case M.$tuple: // assume dest is already void 0 return decoderForTuple(ctx, p, p[0].map(M.unname), dest, recordFields, void 0); case M.$tuple_STAR_: // assume dest is already void 0 return decoderForTuple(ctx, p, p[0].map(M.unname), dest, recordFields, M.unname(p[1])); case M.$setof: // assume dest is already void 0 return [seq(`if (d.openSet()) `, ctx.block(() => [ seq(`let r: `, typeFor(ctx.mod, p), ` | undefined = new _.KeyedSet()`), decodeCompound(ctx, p[0], () => [`r = void 0`], (t) => [`r.add(${t})`]), `${dest} = r`]))]; case M.$dictof: // assume dest is already void 0 return [seq(`if (d.openDictionary()) `, ctx.block(() => [ seq(`let r: `, typeFor(ctx.mod, p), ` | undefined = new _.KeyedDictionary()`), seq(`while (!d.closeCompound()) `, ctx.block(() => [ seq(`let K: undefined | `, typeFor(ctx.mod, p[0]), ` = void 0`), ... decoderFor(ctx, p[0], 'K'), seq(`if (K === void 0) { r = void 0; break; }`), seq(`let V: undefined | `, typeFor(ctx.mod, p[1]), ` = void 0`), ... decoderFor(ctx, p[1], 'V'), seq(`if (V === void 0) { r = void 0; break; }`), seq(`r.set(K, V)`)])), seq(`${dest} = r`)]))]; case M.$dict: return [seq(`${dest} = d.next()`), seq(`if (${dest} !== void 0 && !(`, predicateFor(ctx, dest, p), `)) ${dest} = void 0`)]; default: ((_p: never) => {})(p); throw new Error("Unreachable"); } }