293 lines
12 KiB
TypeScript
293 lines
12 KiB
TypeScript
|
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 [];
|
||
|
}
|