import { refPosition } from '../reader'; import * as M from '../meta'; import { block, brackets, formatItems, Item, parens, seq } from './block'; import { FunctionContext } from "./context"; import { typeForAlternative } from './gentype'; import { FieldType, renderType, SimpleType } from './type'; export function unconverterForDefinition( ctx: FunctionContext, name: string, def: M.Definition, src: string): Item[] { if (def._variant === 'or') { return [seq(`switch (${src}._variant) `, block( ... [def.pattern, ... def.patterns].map(p => seq(`case `, JSON.stringify(p.variantLabel), `: `, ctx.block(() => { const hasValueField = p.alternative._variant === 'Pattern' && p.alternative.value._variant === 'SimplePattern'; return [seq(`return `, unconverterForAlternative( ctx, name, p.alternative, hasValueField ? `${src}.value` : src))]; })))))]; } else { return [seq(`return `, unconverterForAlternative(ctx, name, def.value, `${src}`))]; } } export class InsufficientInformationError extends Error {} function unconverterForAlternative( ctx: FunctionContext, name: string, a: M.Alternative, src: string): Item { const t = typeForAlternative(ctx.mod, a); if (a._variant === 'and') { const errs: [string, InsufficientInformationError][] = []; const cs = [a.pattern, ... a.patterns].flatMap(p => { if (p._variant === 'anonymous' && p.value._variant === 'SimplePattern') { return []; } else { try { return [unconverterForNamed(ctx, p, src, t)]; } catch (e) { if (e instanceof InsufficientInformationError) { errs.push([name + '/' + (M.nameFor(p) ?? ''), e]); return []; } throw e; } } }); if (cs.length === 0 || errs.length > 0) { throw new Error(`Cannot produce unconverter for ${name}: ` + errs.map(e => `${e[0]}: ${e[1].message}`).join(', ')); } return (cs.length === 1) ? cs[0] : seq(`_.merge`, parens(`(a, b) => (a === b) ? a : void 0`, ... cs)); } else { try { return unconverterFor(ctx, a.value, src, t); } catch (e) { if (e instanceof InsufficientInformationError) { throw new Error(`Cannot produce unconverter for ${name}: ${e.message}`); } throw e; } } } function stepSource( src: string, t: SimpleType, key: string): { steppedSrc: string, steppedType: FieldType } { if (t.kind !== 'record' || !t.fields.has(key)) { throw new Error( `Internal error: attempt to step type ` + `${formatItems([renderType(t)])} with key ${key}`); } return { steppedSrc: `${src}[${JSON.stringify(key)}]`, steppedType: t.fields.get(key)! }; } function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string, t: SimpleType): Item { switch (p._variant) { case 'SimplePattern': return ((p: M.SimplePattern) => { if (p._variant !== 'lit' && (t.kind === 'record' || t.kind === 'unit')) { throw new InsufficientInformationError(`A Cannot produce unconverter TODO ${src} ${formatItems([renderType(t)])}`); } switch (p._variant) { case 'any': return `${src}`; case 'atom': switch (p.atomKind._variant) { case 'Float': return `_.Single(${src})`; case 'Double': return `_.Double(${src})`; default: return `${src}`; } case 'lit': return ctx.mod.literal(p.value); case 'embedded': return `_.embedded(${src})`; case 'Ref': return M.lookup( refPosition(p.value), p.value, ctx.mod.env, (_p) => `from${p.value.name.description!}(${src})`, (modId, modPath, _p) => { ctx.mod.imports.add([modId, modPath]); return `${modId}.from${p.value.name.description!}(${src})`; }); } })(p.value); case 'CompoundPattern': return ((p: M.CompoundPattern) => { switch (p._variant) { case 'rec': return seq(`_.Record`, parens( unconverterForNamed(ctx, p.label, src, t), unconverterForNamed(ctx, p.fields, src, t))); case 'tuple': return brackets(... p.patterns.map(pp => unconverterForNamed(ctx, pp, src, t))); case 'tuple*': { let varexp: Item; if (p.variable._variant === 'named') { const { steppedSrc, steppedType } = stepSource(src, t, p.variable.value.name.description!); if (steppedType.kind !== 'array') { throw new Error( `Internal error: attempt to visit element type of ` + `${formatItems([renderType(steppedType)])} after ` + `stepping by key ${p.variable.value.name.description!}`); } varexp = seq(steppedSrc, `.map`, parens( seq(`v => `, unconverterFor( ctx, M.Pattern.SimplePattern(p.variable.value.pattern), `v`, steppedType.type)))); } else { if (t.kind !== 'array') { throw new InsufficientInformationError( `B Cannot produce unconverter TODO ${src} ${formatItems([renderType(t)])}`); } varexp = seq(src, `.map`, parens( seq(`v => `, unconverterFor( ctx, M.Pattern.SimplePattern(p.variable.value), `v`, t.type)))); } if (p.fixed.length === 0) { return varexp; } else { return brackets( ... p.fixed.map(pp => unconverterForNamed(ctx, pp, src, t)), seq(`... `, varexp)); } } case 'setof': if (t.kind !== 'set') { throw new InsufficientInformationError( `C Cannot produce unconverter TODO ${src} ${formatItems([renderType(t)])}`); } return seq(`new _.Set<_embedded>`, parens( `_.Array.from(${src}.values()).map(v => `, unconverterFor(ctx, M.Pattern.SimplePattern(p.pattern), 'v', t), `)`)); case 'dictof': if (t.kind !== 'dictionary') { throw new InsufficientInformationError( `D Cannot produce unconverter TODO ${src} ${formatItems([renderType(t)])}`); } return seq(`new _.Dictionary<_embedded>`, parens(seq( `_.Array.from(${src}.entries()).map(([k, v]) => `, brackets( unconverterFor(ctx, M.Pattern.SimplePattern(p.key), 'k', t), unconverterFor(ctx, M.Pattern.SimplePattern(p.value), 'v', t)), `)`))); case 'dict': return seq(`new _.Dictionary<_embedded>`, parens( brackets(... Array.from(p.entries.entries()).map(([k, n]) => brackets( ctx.mod.literal(k), unconverterForNamedSimple(ctx, M.addNameIfAbsent(n, k), src, t)))))); } })(p.value); } } function unconverterForNamed(ctx: FunctionContext, p: M.NamedPattern, src: string, t: SimpleType): Item { if (p._variant === 'named') { const { steppedSrc, steppedType } = stepSource(src, t, p.value.name.description!); return unconverterFor(ctx, M.Pattern.SimplePattern(p.value.pattern), steppedSrc, steppedType); } else { return unconverterFor(ctx, p.value, src, t); } } function unconverterForNamedSimple(ctx: FunctionContext, p: M.NamedSimplePattern, src: string, t: SimpleType): Item { if (p._variant === 'named') { const { steppedSrc, steppedType } = stepSource(src, t, p.value.name.description!); return unconverterFor(ctx, M.Pattern.SimplePattern(p.value.pattern), steppedSrc, steppedType); } else { return unconverterFor(ctx, M.Pattern.SimplePattern(p.value), src, t); } }