import { FunctionContext } from "./context"; import * as M from '../meta'; import { block, braces, Item, keyvalue, seq } from "./block"; import { typeFor, variantFor, variantInitFor } from "./type"; import { refPosition } from "../reader"; import { stringify } from "@preserves/core"; function converterForTuple(ctx: FunctionContext, ps: M.NamedPattern[], src: string, dest: string, variantName: string | undefined, recordFields: boolean, variablePattern: M.NamedSimplePattern | undefined): Item[] { const temps = ctx.gentemps(ps.length); function loop(i: number): Item[] { if (i < ps.length) { return [...converterFor(ctx, M.unname(ps[i]), `${src}[${i}]`, temps[i]), seq(`if (${temps[i]} !== void 0) `, ctx.block(() => loop(i + 1)))]; } else { if (variablePattern === void 0) { return [seq(`${dest} = `, braces( ... variantInitFor(variantName), ... ps.flatMap((pp, i) => converterField(pp, temps[i]))))]; } else { return [ps.length > 0 ? `let vN = ${src}.slice(${ps.length})` : `let vN = ${src}`, converterForArray(ctx, M.unname(variablePattern), 'vN', dest, false)]; } } } const lengthCheck = variablePattern === void 0 ? seq(`${src}.length === ${ps.length}`) : seq(`${src}.length >= ${ps.length}`); return recordFields ? loop(0) : [seq(`if (_.Array.isArray(${src}) && `, lengthCheck, `) `, ctx.block(() => loop(0)))]; } export function converterForDefinition( ctx: FunctionContext, p: M.Definition, src: string, dest: string): Item[] { if (p.label === M.$or) { const alts = p[0]; switch (alts.length) { case 0: return []; // assume dest is already void 0 case 1: return converterForAlternative(ctx, alts[0][1], src, dest, alts[0][0]); default: { function loop(i: number): Item[] { return [ ... converterForAlternative(ctx, alts[i][1], src, dest, alts[i][0]), ... (i < alts.length - 1) ? [seq(`if (${dest} === void 0) `, ctx.block(() => loop(i + 1)))] : []]; } return loop(0); } } } else { return converterForAlternative(ctx, p, src, dest, void 0); } } function converterForAlternative(ctx: FunctionContext, p: M.Alternative, src: string, dest: string, variantName: string | undefined): Item[] { if (p.label === M.$and) { switch (p[0].length) { case 0: return [`${dest} = ${src}`]; case 1: { return converterFor(ctx, M.unname(p[0][0]), src, dest, variantName); } default: { const alts = p[0]; const temps = ctx.gentemps(alts.length); function loop(i: number): Item[] { return (i < temps.length) ? [...converterFor(ctx, M.unname(alts[i]), src, temps[i]), seq(`if (${temps[i]} !== void 0) `, ctx.block(() => loop(i + 1)))] : [seq(`${dest} = `, braces( ... variantInitFor(variantName), ... alts.flatMap((pp, i) => converterField(pp, temps[i]))))]; } return loop(0); } } } else { return converterFor(ctx, p, src, dest, variantName); } } function converterForArray(ctx: FunctionContext, arrayType: M.SimplePattern, src: string, dest: string, checkArray: boolean): Item { const postCheck = () => [ seq(`let r: Array<`, typeFor(ctx.mod, arrayType), `> | undefined = []`), seq(`for (const v of ${src}) `, ctx.block(() => [ seq(`let vv`), ... converterFor(ctx, arrayType, 'v', 'vv'), seq(`if (vv === void 0) { r = void 0; break; }`), seq(`r.push(vv)`)])), seq(`${dest} = r`)]; return (checkArray ? seq(`if (_.Array.isArray(${src})) `, ctx.block(postCheck)) : block(... postCheck())); } function converterFor( ctx: FunctionContext, p: M.Pattern, src: string, dest: string, variantName?: string, recordFields = false): Item[] { let converterItem: Item[]; const unlabeled = variantName === void 0 ? dest : ctx.gentemp(); if (M.isSimplePattern(p)) { converterItem = converterForSimple(ctx, p, src, unlabeled); } else { switch (p.label) { case M.$setof: // assume dest is already void 0 converterItem = [ seq(`if (_.Set.isSet(${src})) `, ctx.block(() => [ seq(`let r: `, typeFor(ctx.mod, p), ` | undefined = new _.KeyedSet()`), seq(`for (const v of ${src}) `, ctx.block(() => [ seq(`let vv`), ... converterFor(ctx, p[0], 'v', 'vv'), seq(`if (vv === void 0) { r = void 0; break; }`), seq(`r.add(vv)`)])), seq(`${unlabeled} = r`)]))]; break; case M.$dictof: // assume dest is already void 0 converterItem = [ seq(`if (_.Dictionary.isDictionary(${src})) `, ctx.block(() => [ seq(`let r: `, typeFor(ctx.mod, p), ` | undefined = new _.KeyedDictionary()`), seq(`for (const [k, v] of ${src}) `, ctx.block(() => [ seq(`let kk`), ... converterFor(ctx, p[0], 'k', 'kk'), seq(`if (kk === void 0) { r = void 0; break; }`), seq(`let vv`), ... converterFor(ctx, p[1], 'v', 'vv'), seq(`if (vv === void 0) { r = void 0; break; }`), seq(`r.set(kk, vv)`)])), seq(`${unlabeled} = r`)]))]; break; default: { const arrayType = M.simpleArray(p); if (arrayType === void 0) { return converterForCompound(ctx, p, src, dest, variantName, recordFields); } else { converterItem = [ converterForArray(ctx, arrayType, src, unlabeled, !recordFields)]; break; } } } } if (variantName === void 0) { return converterItem; } else { return [... converterItem, seq(`if (${unlabeled} !== void 0) ${dest} = `, braces( variantFor(variantName), keyvalue('value', unlabeled)))]; } } function converterForSimple( ctx: FunctionContext, p: M.SimplePattern, src: string, dest: string): Item[] { switch (p.label) { case M.$atom: { let test: Item; switch (p[0]) { case M.$Boolean: test = `typeof ${src} === 'boolean'`; break; case M.$Float: test = `_.Float.isSingle(${src})`; break; case M.$Double: test =`_.Float.isDouble(${src})`; break; case M.$SignedInteger: test = `typeof ${src} === 'number'`; break; case M.$String: test = `typeof ${src} === 'string'`; break; case M.$ByteString: test = `_.Bytes.isBytes(${src})`; break; case M.$Symbol: test = `typeof ${src} === 'symbol'`; break; } return [seq(`${dest} = `, test, ` ? ${src} : void 0`)]; } case M.$lit: return [`${dest} = _.is(${src}, ${ctx.mod.literal(p[0])}) ? ${src} : void 0`]; case M.$ref: return M.lookup(refPosition(p), p, ctx.mod.env, (_p) => [`${dest} = to${p[1].description!}(${src})`], (p) => converterForAlternative(ctx, p, src, dest, void 0), (modId, modPath,_p) => { ctx.mod.imports.add([modId, modPath]); return [`${dest} = ${modId}.decode${p[1].description!}(${src})`]; }); case M.$pointer: return [`${dest} = _toPtr(${src})`]; default: ((_p: never) => {})(p); throw new Error("Unreachable"); } } function converterForCompound( ctx: FunctionContext, p: M.CompoundPattern, src: string, dest: string, variantName: string | undefined, recordFields: boolean): Item[] { switch (p.label) { case M.$rec: // assume dest is already void 0 return [seq(`if (_.Record.isRecord(${src})) `, ctx.block(() => { const label = ctx.gentemp(); return [...converterFor(ctx, M.unname(p[0]), `${src}.label`, label), seq(`if (${label} !== void 0) `, ctx.block(() => { const fs = ctx.gentemp(); return [...converterFor(ctx, M.unname(p[1]), src, fs, void 0, true), seq(`if (${fs} !== void 0) ${dest} = `, braces(... variantInitFor(variantName), ... converterField(p[0], label), `... ${fs}`))]; }))]; }))]; case M.$tuple: // assume dest is already void 0 return converterForTuple(ctx, p[0], src, dest, variantName, recordFields, void 0); case M.$tuple_STAR_: // assume dest is already void 0 return converterForTuple(ctx, p[0], src, dest, variantName, recordFields, p[1]); case M.$setof: case M.$dictof: throw new Error('Internal error: setof and dictof are handled in converterFor()'); case M.$dict: { const entries = Array.from(p[0]); const temps = ctx.gentemps(entries.length); function loop(i: number): Item[] { if (i < entries.length) { const [k, n] = entries[i]; const tmpSrc = ctx.gentemp(); return [ seq(`if ((${tmpSrc} = ${src}.get(${ctx.mod.literal(k)})) !== void 0) `, ctx.block(() => [ ...converterFor(ctx, M.unname(n), tmpSrc, temps[i]), seq(`if (${temps[i]} !== void 0) `, ctx.block(() => loop(i + 1)))]))]; } else { return [ seq(`${dest} = `, braces( ... variantInitFor(variantName), ... entries.flatMap(([k, n], i) => converterField(n, temps[i], k))))]; } } return [seq(`if (_.Dictionary.isDictionary(${src})) `, ctx.block(() => loop(0)))]; } default: ((_p: never) => {})(p); throw new Error("Unreachable"); } } function converterField(n: M.NamedPattern, src: string, k?: M.Input): Item[] { if (n.label === M.$named) { return [keyvalue(stringify(n[0]), src)]; } if (k !== void 0) { const s = M.namelike(k); if (s !== void 0) { return [keyvalue(JSON.stringify(s), src)]; } } if (M.isCompoundPattern(n)) { return [`... ${src}`]; } return []; }