Repair codegen for setof and dictof

This commit is contained in:
Tony Garnock-Jones 2023-12-17 11:05:07 +13:00
parent cd29602761
commit 9c7770a54f
8 changed files with 627 additions and 334 deletions

View File

@ -1,10 +1,10 @@
import { encode, stringify } from "@preserves/core"; import { encode, stringify } from "@preserves/core";
import * as M from "./meta"; import * as M from "./meta";
import { CompilerOptions, ModuleContext } from "./compiler/context"; import { CompilerOptions, ModuleContext } from "./compiler/context";
import { Formatter, block, seq, braces, opseq } from "./compiler/block"; import { Formatter, block, seq, braces } from "./compiler/block";
import { typeForDefinition } from "./compiler/gentype"; import { typeForDefinition } from "./compiler/gentype";
import { converterForDefinition } from "./compiler/genconverter"; import { converterForDefinition } from "./compiler/genconverter";
import { renderType } from "./compiler/rendertype"; import { renderTypeWithConversionMixins } from "./compiler/rendertype";
import { genConstructor } from "./compiler/genctor"; import { genConstructor } from "./compiler/genctor";
import { unconverterForDefinition } from "./compiler/genunconverter"; import { unconverterForDefinition } from "./compiler/genunconverter";
import { sourceCodeFor } from "./compiler/value"; import { sourceCodeFor } from "./compiler/value";
@ -32,10 +32,10 @@ export function compile(
for (const [name, def] of schema.definitions) { for (const [name, def] of schema.definitions) {
const t = typeForDefinition(mod.resolver(), def); const t = typeForDefinition(mod.resolver(), def);
const nameStr = stringify(name); const nameStr = stringify(name);
const resultTypeItem = mod.withAsPreserveMixinType(nameStr, t); const resultTypeItem = seq(nameStr, mod.genericArgsFor(t));
mod.defineType(seq(`export type ${nameStr}`, mod.genericParametersFor(t), mod.defineType(seq(`export type ${nameStr}`, mod.genericParametersFor(t),
` = `, renderType(mod, t), `;`)); ` = `, renderTypeWithConversionMixins(mod, t), `;`));
if (t.kind === 'union') { if (t.kind === 'union') {
mod.defineFunctions(nameStr, _ctx => mod.defineFunctions(nameStr, _ctx =>
@ -53,7 +53,7 @@ export function compile(
const t = typeForDefinition(mod.resolver(), def); const t = typeForDefinition(mod.resolver(), def);
const name = name0 as symbol; const name = name0 as symbol;
const nameStr = name0.description!; const nameStr = name0.description!;
const resultTypeItem = mod.withAsPreserveMixinType(nameStr, t); const resultTypeItem = seq(nameStr, mod.genericArgsFor(t));
mod.defineFunctions(nameStr, ctx => mod.defineFunctions(nameStr, ctx =>
[seq(`export function as${name.description!}`, mod.genericParameters(), [seq(`export function as${name.description!}`, mod.genericParameters(),

View File

@ -1,7 +1,7 @@
import { Dictionary, KeyedSet, FlexSet, Position, stringify } from "@preserves/core"; import { Dictionary, KeyedSet, FlexSet, Position, stringify } from "@preserves/core";
import { refPosition } from "../reader"; import { refPosition } from "../reader";
import * as M from "../meta"; import * as M from "../meta";
import { anglebrackets, block, braces, commas, formatItems, Item, keyvalue, seq, opseq } from "./block"; import { anglebrackets, block, braces, commas, formatItems, Item, keyvalue, seq } from "./block";
import { ANY_TYPE, RefType, Type } from "./type"; import { ANY_TYPE, RefType, Type } from "./type";
import { renderType, variantInitFor } from "./rendertype"; import { renderType, variantInitFor } from "./rendertype";
import { typeForDefinition } from "./gentype"; import { typeForDefinition } from "./gentype";
@ -97,7 +97,7 @@ export class ModuleContext {
return (ref) => this.lookup( return (ref) => this.lookup(
ref, ref,
(_p, _t) => Type.ref(ref.name.description!, ref), (_p, _t) => Type.ref(ref.name.description!, ref),
(modPath, modId, modFile, modExpr, _p, t) => { (modPath, modId, modFile, modExpr, _p, _t) => {
this.imports.add([modPath, modId, modFile, modExpr]); this.imports.add([modPath, modId, modFile, modExpr]);
return Type.ref(`${modId}${modExpr}.${ref.name.description!}`, ref); return Type.ref(`${modId}${modExpr}.${ref.name.description!}`, ref);
}, },
@ -209,19 +209,6 @@ export class ModuleContext {
return walk(t); return walk(t);
} }
withAsPreserveMixinType(name: string, t: Type): Item {
if (t.kind === 'unit' || t.kind === 'record' || t.kind === 'union') {
return opseq('any', ' & ',
seq(name, this.genericArgsFor(t)),
braces(seq('__as_preserve__',
this.hasEmbedded(t) ? '' : this.genericParameters(),
'()',
': _.Value', this.genericArgs())));
} else {
return seq(name, this.genericArgsFor(t));
}
}
} }
export class FunctionContext { export class FunctionContext {
@ -285,16 +272,25 @@ export class FunctionContext {
} }
buildCapturedCompound(dest: string): Item { buildCapturedCompound(dest: string): Item {
const fields = [ return seq(`${dest} = `, buildProduct(
... variantInitFor(this.variantName), this.definitionName, this.variantName, this.captures));
... this.captures.map(({ fieldName, sourceExpr }) =>
keyvalue(fieldName, sourceExpr)),
seq(`__as_preserve__() `, block(`return from${this.definitionName}(this)`))
];
return seq(`${dest} = `, braces(... fields));
} }
} }
export function buildProduct(
definitionName: string,
variant: string | undefined,
initializers: Capture[],
): Item {
return braces(
... variantInitFor(variant),
... initializers.map(({ fieldName, sourceExpr }) => keyvalue(fieldName, sourceExpr)),
seq(`__as_preserve__() `, block(`return from${M.jsId(definitionName)}(this)`)),
seq(`__preserve_on__(e) { e.push(from${M.jsId(definitionName)}(this)); }`),
seq(`__preserve_text_on__(w) { w.push(from${M.jsId(definitionName)}(this)); }`),
);
}
export class WalkState { export class WalkState {
modulePath: M.ModulePath; modulePath: M.ModulePath;
readonly seen: FlexSet<M.Ref>; readonly seen: FlexSet<M.Ref>;

View File

@ -1,8 +1,9 @@
import { FunctionContext } from "./context"; import { FunctionContext } from "./context";
import * as M from '../meta'; import * as M from '../meta';
import { Item, seq } from "./block"; import { Item, seq, parens, anglebrackets } from "./block";
import { simpleType, typeFor } from "./gentype"; import { simpleType, typeFor } from "./gentype";
import { ANY_TYPE, Type } from "./type"; import { ANY_TYPE, Type } from "./type";
import { renderType } from "./rendertype";
export function converterForDefinition( export function converterForDefinition(
ctx: FunctionContext, ctx: FunctionContext,
@ -94,6 +95,31 @@ function converterForTuple(ctx: FunctionContext,
: [seq(`if (_.isSequence(${src})`, lengthCheck, `) `, ctx.block(() => loop(0)))]; : [seq(`if (_.isSequence(${src})`, lengthCheck, `) `, ctx.block(() => loop(0)))];
} }
function encoderForSimplePattern(
ctx: FunctionContext,
p: M.SimplePattern,
): Item | null {
switch (p._variant) {
case 'Ref':
return ctx.mod.lookup(
p.value,
(_p, t) => `from${M.jsId(p.value.name.description!)}${ctx.mod.genericArgsFor(t())}`,
(modPath, modId, modFile, modExpr, _p, t) => {
ctx.mod.imports.add([modPath, modId, modFile, modExpr]);
return `${modId}${modExpr}.from${M.jsId(p.value.name.description!)}${t ? ctx.mod.genericArgsFor(t()) : ''}`;
});
case 'embedded':
return `_.embed`;
case 'seqof': {
const e = encoderForSimplePattern(ctx, p.pattern);
if (e === null) return null;
return seq(`vs => vs.map`, parens(e));
}
default:
return null;
}
}
function converterFor( function converterFor(
ctx: FunctionContext, ctx: FunctionContext,
np: M.NamedPattern, np: M.NamedPattern,
@ -163,9 +189,12 @@ export function converterForSimple(
case 'setof': case 'setof':
return [`${dest} = void 0`, return [`${dest} = void 0`,
seq(`if (_.Set.isSet<_embedded>(${src})) `, ctx.block(() => { seq(`if (_.Set.isSet<_embedded>(${src})) `, ctx.block(() => {
const vt = simpleType(ctx.mod.resolver(), p.pattern);
const v = ctx.gentempname(); const v = ctx.gentempname();
return [ return [
seq(`${dest} = new _.KeyedSet()`), seq(`${dest} = new _.EncodableSet`,
anglebrackets(renderType(ctx.mod, vt), '_embedded'),
parens(encoderForSimplePattern(ctx, p.pattern) ?? `v => v`)),
seq(`for (const ${v} of ${src}) `, ctx.block(() => [ seq(`for (const ${v} of ${src}) `, ctx.block(() => [
... converterFor(ctx, M.anonymousSimplePattern(p.pattern), v, vv => ... converterFor(ctx, M.anonymousSimplePattern(p.pattern), v, vv =>
[`${dest}.add(${vv})`, `continue`]), [`${dest}.add(${vv})`, `continue`]),
@ -175,10 +204,16 @@ export function converterForSimple(
case 'dictof': case 'dictof':
return [`${dest} = void 0`, return [`${dest} = void 0`,
seq(`if (_.Dictionary.isDictionary<_embedded>(${src})) `, ctx.block(() => { seq(`if (_.Dictionary.isDictionary<_embedded>(${src})) `, ctx.block(() => {
const v = ctx.gentempname(); const resolver = ctx.mod.resolver();
const kt = simpleType(resolver, p.key);
const vt = simpleType(resolver, p.value);
const k = ctx.gentempname(); const k = ctx.gentempname();
const v = ctx.gentempname();
return [ return [
seq(`${dest} = new _.KeyedDictionary()`), seq(`${dest} = new _.EncodableDictionary`,
anglebrackets(renderType(ctx.mod, kt), renderType(ctx.mod, vt), '_embedded'),
parens(encoderForSimplePattern(ctx, p.key) ?? `k => k`,
encoderForSimplePattern(ctx, p.value) ?? `v => v`)),
seq(`for (const [${k}, ${v}] of ${src}) `, ctx.block(() => [ seq(`for (const [${k}, ${v}] of ${src}) `, ctx.block(() => [
... converterFor(ctx, M.anonymousSimplePattern(p.key), k, kk => ... converterFor(ctx, M.anonymousSimplePattern(p.key), k, kk =>
converterFor(ctx, M.anonymousSimplePattern(p.value), v, vv => converterFor(ctx, M.anonymousSimplePattern(p.value), v, vv =>

View File

@ -1,8 +1,8 @@
import * as M from '../meta'; import * as M from '../meta';
import { block, braces, Item, keyvalue, parens, seq } from "./block"; import { block, braces, Item, parens, seq } from "./block";
import { FieldType, SimpleType, Type } from "./type"; import { FieldType, SimpleType, Type } from "./type";
import { renderType } from "./rendertype"; import { renderType } from "./rendertype";
import { ModuleContext } from './context'; import { ModuleContext, buildProduct } from './context';
export function genConstructor( export function genConstructor(
mod: ModuleContext, mod: ModuleContext,
@ -29,12 +29,7 @@ export function genConstructor(
simpleValue = (variant === void 0) && (arg.kind !== 'unit'); simpleValue = (variant === void 0) && (arg.kind !== 'unit');
} }
const initializers: Item[] = (variant !== void 0) const initializers = formals.map(([n, _t]) => ({ fieldName: n, sourceExpr: M.jsId(n) }));
? [keyvalue('_variant', JSON.stringify(variant))]
: [];
formals.forEach(([n, _t]) => initializers.push(seq(JSON.stringify(n), ': ', M.jsId(n))));
initializers.push(seq(`__as_preserve__() `, block(`return from${M.jsId(definitionName)}(this)`)));
const declArgs: Array<Item> = (formals.length > 1) const declArgs: Array<Item> = (formals.length > 1)
? [seq(braces(...formals.map(f => M.jsId(f[0]))), ': ', ? [seq(braces(...formals.map(f => M.jsId(f[0]))), ': ',
@ -48,7 +43,7 @@ export function genConstructor(
seq(`return `, seq(`return `,
(simpleValue (simpleValue
? 'value' ? 'value'
: braces(...initializers))))), : buildProduct(definitionName, variant, initializers))))),
seq(`${M.jsId(name)}.schema = function () `, block( seq(`${M.jsId(name)}.schema = function () `, block(
seq(`return `, braces( seq(`return `, braces(
`schema: _schema()`, `schema: _schema()`,

View File

@ -10,7 +10,7 @@ export function variantFor(variantName: string): Item {
return keyvalue('_variant', JSON.stringify(variantName)); return keyvalue('_variant', JSON.stringify(variantName));
} }
function simpleTypeFields(ctxt: ModuleContext, baseType: Type, t: SimpleType): Item[] { function simpleTypeFields(ctxt: ModuleContext, t: SimpleType): Item[] {
switch (t.kind) { switch (t.kind) {
case 'unit': case 'unit':
return []; return [];
@ -29,35 +29,49 @@ function simpleTypeFields(ctxt: ModuleContext, baseType: Type, t: SimpleType): I
export function renderVariant( export function renderVariant(
ctxt: ModuleContext, ctxt: ModuleContext,
baseType: Type,
[variantName, t]: [string, SimpleType], [variantName, t]: [string, SimpleType],
): Item { ): Item {
let fields = simpleTypeFields(ctxt, baseType, t); let fields = simpleTypeFields(ctxt, t);
return braces(variantFor(variantName), ... fields); return braces(variantFor(variantName), ... fields);
} }
export function renderType(ctxt: ModuleContext, t: Type): Item { export function renderType(ctxt: ModuleContext, t: Type): Item {
switch (t.kind) { switch (t.kind) {
case 'union': return opseq('never', ' | ', ... case 'union': return opseq('never', ' | ', ...
Array.from(t.variants).flatMap(entry => renderVariant(ctxt, t, entry))); Array.from(t.variants).flatMap(entry => renderVariant(ctxt, entry)));
case 'unit': return braces(... simpleTypeFields(ctxt, t, t)); case 'unit': return braces(... simpleTypeFields(ctxt, t));
case 'ref': case 'ref':
if (t.ref === null && t.typeName === '_embedded') { if (t.ref === null && t.typeName === '_embedded') {
return t.typeName; return t.typeName;
} else { } else {
return seq(t.typeName, ctxt.genericArgsFor(t)); return seq(t.typeName, ctxt.genericArgsFor(t));
} }
case 'set': return seq('_.KeyedSet', anglebrackets( case 'set': return seq('_.EncodableSet', anglebrackets(
renderType(ctxt, t.type), renderType(ctxt, t.type),
'_embedded')); '_embedded'));
case 'dictionary': return seq('_.KeyedDictionary', anglebrackets( case 'dictionary': return seq('_.EncodableDictionary', anglebrackets(
renderType(ctxt, t.key), renderType(ctxt, t.key),
renderType(ctxt, t.value), renderType(ctxt, t.value),
'_embedded')); '_embedded'));
case 'array': return seq('Array', anglebrackets(renderType(ctxt, t.type))); case 'array': return seq('Array', anglebrackets(renderType(ctxt, t.type)));
case 'record': return braces(... simpleTypeFields(ctxt, t, t)); case 'record': return braces(... simpleTypeFields(ctxt, t));
default: default:
((_: never) => {})(t); ((_: never) => {})(t);
throw new Error("Unreachable"); throw new Error("Unreachable");
} }
} }
export function renderTypeWithConversionMixins(ctxt: ModuleContext, t: Type): Item {
if (t.kind === 'unit' || t.kind === 'record' || t.kind === 'union') {
return opseq('any', ' & ',
renderType(ctxt, t),
seq('_.Preservable', ctxt.hasEmbedded(t) ? ctxt.genericArgs() : '<any>'),
seq('_.PreserveWritable', ctxt.hasEmbedded(t) ? ctxt.genericArgs() : '<any>'),
braces(seq('__as_preserve__',
ctxt.hasEmbedded(t) ? '' : ctxt.genericParameters(),
'()',
': _.Value', ctxt.genericArgs())));
} else {
return renderType(ctxt, t);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
import { Reader, Annotated, Dictionary, is, peel, preserves, Record, strip, Tuple, Position, position, stringify, isCompound, KeyedDictionary, annotate, annotations, isEmbedded, GenericEmbedded, genericEmbeddedTypeDecode } from '@preserves/core'; import { Reader, Annotated, Dictionary, is, peel, preserves, Record, strip, Tuple, Position, position, stringify, isCompound, EncodableDictionary, annotate, annotations, isEmbedded, GenericEmbedded, genericEmbeddedTypeDecode } from '@preserves/core';
import { Input, Pattern, Schema, Definition, CompoundPattern, SimplePattern } from './meta'; import { Input, Pattern, Schema, Definition, CompoundPattern, SimplePattern } from './meta';
import * as M from './meta'; import * as M from './meta';
import { SchemaSyntaxError } from './error'; import { SchemaSyntaxError } from './error';
@ -70,7 +70,7 @@ export function parseSchema(toplevelTokens: Array<Input>, options: SchemaReaderO
{ {
let version: M.Version | undefined = void 0; let version: M.Version | undefined = void 0;
let embeddedType: M.EmbeddedTypeName = M.EmbeddedTypeName.$false(); let embeddedType: M.EmbeddedTypeName = M.EmbeddedTypeName.$false();
let definitions = new KeyedDictionary<symbol, Definition, M.InputEmbedded>(); let definitions: M.Definitions = new EncodableDictionary(k => k, M.fromDefinition);
function process(toplevelTokens: Array<Input>): void { function process(toplevelTokens: Array<Input>): void {
const toplevelClauses = splitBy(peel(toplevelTokens) as Array<Input>, M.DOT); const toplevelClauses = splitBy(peel(toplevelTokens) as Array<Input>, M.DOT);

View File

@ -9,7 +9,11 @@ describe('reader schema', () => {
}); });
it('is OK with an empty schema correctly versioned', () => { it('is OK with an empty schema correctly versioned', () => {
const s = readSchema('version 1 .'); const s = readSchema('version 1 .');
expect(Object.getOwnPropertyNames(s.version)).toEqual(['__as_preserve__']); expect(Object.getOwnPropertyNames(s.version)).toEqual([
'__as_preserve__',
'__preserve_on__',
'__preserve_text_on__',
]);
expect(s.definitions.size).toBe(0); expect(s.definitions.size).toBe(0);
expect(s.embeddedType._variant).toBe('false'); expect(s.embeddedType._variant).toBe('false');
}); });