Move insufficient-information checking to read-time

This commit is contained in:
Tony Garnock-Jones 2021-05-21 15:49:06 +02:00
parent ecdb314366
commit 20b676df27
11 changed files with 235 additions and 137 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "@preserves/schema", "name": "@preserves/schema",
"version": "0.9.0", "version": "0.11.0",
"description": "Schema support for Preserves data serialization format", "description": "Schema support for Preserves data serialization format",
"homepage": "https://gitlab.com/preserves/preserves", "homepage": "https://gitlab.com/preserves/preserves",
"license": "Apache-2.0", "license": "Apache-2.0",

View File

@ -0,0 +1,128 @@
import { typeFor } from './gentype';
import { ANY_TYPE } from './type';
import * as M from './meta';
export function checkSchema(schema: M.Schema): M.Schema {
const checker = new Checker();
schema.definitions.forEach(checker.checkDefinition.bind(checker));
if (checker.problems.length > 0) {
throw new Error(`Cannot produce unconverter: insufficient information in:\n` +
checker.problems.map(c => ' - ' + c).join('\n'));
}
return schema;
}
class Checker {
problems: Array<string> = [];
recordProblem(context: string, detail: string): void {
this.problems.push(`${detail} in ${context}`);
}
checkBinding(scope: Set<string>, sym: symbol, context: string): void {
const name = sym.description!;
if (scope.has(name)) {
this.recordProblem(context, `duplicate binding named ${JSON.stringify(name)}`);
} else {
scope.add(name);
}
}
checkDefinition(def: M.Definition, name: symbol): void {
switch (def._variant) {
case 'or':
[def.pattern0, def.pattern1, ... def.patternN].forEach(({ variantLabel, pattern }) =>
this.checkPattern(new Set(), pattern, `variant ${variantLabel} of ${name.description!}`));
break;
case 'and': {
const scope = new Set<string>();
[def.pattern0, def.pattern1, ... def.patternN].forEach((p) =>
this.checkNamedPattern(scope, p, name.description!));
break;
}
case 'Pattern':
this.checkPattern(new Set(), def.value, name.description!);
break;
}
}
checkNamedPattern(scope: Set<string>, p: M.NamedPattern, context: string): void {
switch (p._variant) {
case 'named':
this.checkPattern(scope,
M.Pattern.SimplePattern(p.value.pattern),
`${p.value.name.description!} of ${context}`);
break;
case 'anonymous':
this.checkPattern(scope, p.value, context);
break;
}
}
checkPattern(scope: Set<string>, p: M.Pattern, context: string): void {
const t = typeFor((_ref) => ANY_TYPE, p);
switch (p._variant) {
case 'SimplePattern':
if (p.value._variant !== 'lit' && (t.kind === 'record' || t.kind === 'unit')) {
this.recordProblem(context, 'cannot recover serialization of non-literal pattern');
}
break;
case 'CompoundPattern':
((p: M.CompoundPattern): void => {
switch (p._variant) {
case 'rec':
this.checkNamedPattern(scope, p.label, `label of ${context}`);
this.checkNamedPattern(scope, p.fields, `fields of ${context}`);
break;
case 'tuple':
p.patterns.forEach((pp, i) =>
this.checkNamedPattern(scope, pp, `item ${i} of ${context}`));
break;
case 'tuple*':
if (p.variable._variant === 'named') {
this.checkBinding(scope, p.variable.value.name, context);
this.checkPattern(scope,
M.Pattern.SimplePattern(p.variable.value.pattern),
`${JSON.stringify(p.variable.value.name.description!)} of ${context}`);
} else {
if (t.kind !== 'array') {
this.recordProblem(context, 'unable to reconstruct tail of tuple* pattern');
}
this.checkPattern(scope,
M.Pattern.SimplePattern(p.variable.value),
`variable-length portion of ${context}`);
}
p.fixed.forEach((pp, i) =>
this.checkNamedPattern(scope, pp, `item ${i} of ${context}`));
break;
case 'setof':
if (t.kind !== 'set') {
this.recordProblem(context, 'unable to reconstruct set');
}
this.checkPattern(scope,
M.Pattern.SimplePattern(p.pattern),
`set in ${context}`);
break;
case 'dictof':
if (t.kind !== 'dictionary') {
this.recordProblem(context, 'unable to reconstruct dictionary');
}
this.checkPattern(scope,
M.Pattern.SimplePattern(p.key),
`key in dictionary in ${context}`);
this.checkPattern(scope,
M.Pattern.SimplePattern(p.value),
`value in dictionary in ${context}`);
break;
case 'dict':
p.entries.forEach((np, key) =>
this.checkNamedPattern(
scope,
M.promoteNamedSimplePattern(M.addNameIfAbsent(np, key)),
`entry ${key.asPreservesText()} in dictionary in ${context}`));
break;
}
})(p.value);
}
}
}

View File

@ -2,9 +2,10 @@ import { 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 } from "./compiler/block"; import { Formatter, block, seq } from "./compiler/block";
import { typeForDefinition } from "./compiler/gentype"; import { typeForDefinition } from "./gentype";
import { converterForDefinition } from "./compiler/genconverter"; import { converterForDefinition } from "./compiler/genconverter";
import { renderType, Type } from "./compiler/type"; import { Type } from "./type";
import { renderType } 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";
@ -16,12 +17,12 @@ export function compile(env: M.Environment, schema: M.Schema, options: CompilerO
mod.defineType(seq(`export type _embedded = `, mod.defineType(seq(`export type _embedded = `,
renderType(embeddedName._variant === 'false' renderType(embeddedName._variant === 'false'
? Type.ref('any') ? Type.ref('any')
: typeForDefinition(mod, M.Definition.Pattern(M.Pattern.SimplePattern(M.SimplePattern.Ref(embeddedName.value))))), : typeForDefinition(mod.resolver(), M.Definition.Pattern(M.Pattern.SimplePattern(M.SimplePattern.Ref(embeddedName.value))))),
`;`)); `;`));
mod.defineType(`export type _val = _.Value<_embedded>;`); mod.defineType(`export type _val = _.Value<_embedded>;`);
for (const [name, def] of schema.definitions) { for (const [name, def] of schema.definitions) {
const t = typeForDefinition(mod, def); const t = typeForDefinition(mod.resolver(), def);
const nameStr = stringify(name); const nameStr = stringify(name);
mod.defineType(seq(`export type ${nameStr} = `, renderType(t), `;`)); mod.defineType(seq(`export type ${nameStr} = `, renderType(t), `;`));
@ -60,7 +61,7 @@ export function compile(env: M.Environment, schema: M.Schema, options: CompilerO
mod.defineFunction(ctx => mod.defineFunction(ctx =>
seq(`export function from${name.description!}`, seq(`export function from${name.description!}`,
'(_v: ', name.description!, '): _val ', '(_v: ', name.description!, '): _val ',
ctx.block(() => unconverterForDefinition(ctx, name.description!, def, '_v')))); ctx.block(() => unconverterForDefinition(ctx, def, '_v'))));
} }
const f = new Formatter(); const f = new Formatter();

View File

@ -2,7 +2,8 @@ import { Dictionary, KeyedSet, Position } from "@preserves/core";
import { refPosition } from "../reader"; import { refPosition } from "../reader";
import * as M from "../meta"; import * as M from "../meta";
import { block, braces, commas, formatItems, Item, keyvalue, seq } from "./block"; import { block, braces, commas, formatItems, Item, keyvalue, seq } from "./block";
import { ANY_TYPE, renderType, Type, variantInitFor } from "./type"; import { ANY_TYPE, AtomicType, Type } from "../type";
import { renderType, variantInitFor } from "./rendertype";
export interface CompilerOptions { export interface CompilerOptions {
preservesModule?: string; preservesModule?: string;
@ -67,6 +68,15 @@ export class ModuleContext {
defineFunction(f: (ctx: FunctionContext) => Item): void { defineFunction(f: (ctx: FunctionContext) => Item): void {
this.functiondefs.push(f(new FunctionContext(this))); this.functiondefs.push(f(new FunctionContext(this)));
} }
resolver(): (ref: M.Ref) => AtomicType {
return (ref) => M.lookup(refPosition(ref), ref, this.env,
(_p) => Type.ref(ref.name.description!),
(modId, modPath,_p) => {
this.imports.add([modId, modPath]);
return Type.ref(`${modId}.${ref.name.description!}`);
});
}
} }
export class FunctionContext { export class FunctionContext {

View File

@ -1,9 +1,9 @@
import { FunctionContext } from "./context"; import { FunctionContext } from "./context";
import * as M from '../meta'; import * as M from '../meta';
import { block, Item, seq } from "./block"; import { block, Item, seq } from "./block";
import { simpleType, dictionaryType, setType, typeFor } from "./gentype"; import { simpleType, dictionaryType, setType, typeFor } from "../gentype";
import { refPosition } from "../reader"; import { refPosition } from "../reader";
import { ANY_TYPE, Type } from "./type"; import { ANY_TYPE, Type } from "../type";
export function converterForDefinition( export function converterForDefinition(
ctx: FunctionContext, ctx: FunctionContext,
@ -48,7 +48,7 @@ function converterForPattern(
if (simpleValue === void 0) { if (simpleValue === void 0) {
return [ctx.buildCapturedCompound(dest)]; return [ctx.buildCapturedCompound(dest)];
} else if (ctx.variantName !== void 0) { } else if (ctx.variantName !== void 0) {
if (typeFor(ctx.mod, p).kind === 'unit') { if (typeFor(ctx.mod.resolver(), p).kind === 'unit') {
return [ctx.buildCapturedCompound(dest)]; return [ctx.buildCapturedCompound(dest)];
} else { } else {
return [ctx.withCapture('value', return [ctx.withCapture('value',
@ -98,7 +98,7 @@ function converterForArray(ctx: FunctionContext,
k: (dest: string) => Item[]): Item k: (dest: string) => Item[]): Item
{ {
const postCheck = () => { const postCheck = () => {
const r = ctx.gentemp(Type.array(simpleType(ctx.mod, M.unnameSimplePattern(arrayType)))); const r = ctx.gentemp(Type.array(simpleType(ctx.mod.resolver(), M.unnameSimplePattern(arrayType))));
const v = ctx.gentempname(); const v = ctx.gentempname();
return [ return [
seq(`${r} = []`), seq(`${r} = []`),
@ -125,14 +125,14 @@ function converterFor(
let maybeName = M.nameFor(np); let maybeName = M.nameFor(np);
if (p._variant === 'SimplePattern') { if (p._variant === 'SimplePattern') {
const dest = ctx.gentemp(simpleType(ctx.mod, p.value)); const dest = ctx.gentemp(simpleType(ctx.mod.resolver(), p.value));
return [... converterForSimple(ctx, p.value, src, dest), return [... converterForSimple(ctx, p.value, src, dest),
ctx.convertCapture(maybeName, dest, ks)]; ctx.convertCapture(maybeName, dest, ks)];
} else { } else {
switch (p.value._variant) { switch (p.value._variant) {
case 'setof': { case 'setof': {
const setPattern = p.value.pattern; const setPattern = p.value.pattern;
const r = ctx.gentemp(setType(ctx.mod, setPattern)); const r = ctx.gentemp(setType(ctx.mod.resolver(), setPattern));
const v = ctx.gentempname(); const v = ctx.gentempname();
return [ return [
seq(`if (_.Set.isSet<_embedded>(${src})) `, ctx.block(() => [ seq(`if (_.Set.isSet<_embedded>(${src})) `, ctx.block(() => [
@ -147,7 +147,7 @@ function converterFor(
case 'dictof': { case 'dictof': {
const keyPattern = p.value.key; const keyPattern = p.value.key;
const valPattern = p.value.value; const valPattern = p.value.value;
const r = ctx.gentemp(dictionaryType(ctx.mod, keyPattern, valPattern)); const r = ctx.gentemp(dictionaryType(ctx.mod.resolver(), keyPattern, valPattern));
const v = ctx.gentempname(); const v = ctx.gentempname();
const k = ctx.gentempname(); const k = ctx.gentempname();
return [ return [

View File

@ -1,6 +1,7 @@
import * as M from '../meta'; import * as M from '../meta';
import { block, braces, Item, keyvalue, parens, seq } from "./block"; import { block, braces, Item, keyvalue, parens, seq } from "./block";
import { FieldType, renderType, SimpleType } from "./type"; import { FieldType, SimpleType } from "../type";
import { renderType } from "./rendertype";
export function genConstructor( export function genConstructor(
name: string, name: string,

View File

@ -2,12 +2,12 @@ import { refPosition } from '../reader';
import * as M from '../meta'; import * as M from '../meta';
import { block, brackets, formatItems, Item, parens, seq } from './block'; import { block, brackets, formatItems, Item, parens, seq } from './block';
import { FunctionContext } from "./context"; import { FunctionContext } from "./context";
import { FieldType, renderType, SimpleType } from './type'; import { FieldType, SimpleType } from '../type';
import { typeFor, typeForIntersection } from './gentype'; import { typeFor, typeForIntersection } from '../gentype';
import { renderType } from "./rendertype";
export function unconverterForDefinition( export function unconverterForDefinition(
ctx: FunctionContext, ctx: FunctionContext,
name: string,
def: M.Definition, def: M.Definition,
src: string): Item[] src: string): Item[]
{ {
@ -18,53 +18,30 @@ export function unconverterForDefinition(
seq(`case `, JSON.stringify(p.variantLabel), `: `, ctx.block(() => { seq(`case `, JSON.stringify(p.variantLabel), `: `, ctx.block(() => {
const hasValueField = p.pattern._variant === 'SimplePattern'; const hasValueField = p.pattern._variant === 'SimplePattern';
return [seq(`return `, unconverterForPattern( return [seq(`return `, unconverterForPattern(
ctx, name, p.pattern, hasValueField ? `${src}.value` : src))]; ctx, p.pattern, hasValueField ? `${src}.value` : src))];
})))))]; })))))];
case 'and': { case 'and': {
const ps = [def.pattern0, def.pattern1, ... def.patternN]; const ps = [def.pattern0, def.pattern1, ... def.patternN];
const t = typeForIntersection(ctx.mod, ps); const t = typeForIntersection(ctx.mod.resolver(), ps);
const errs: [string, InsufficientInformationError][] = [];
const cs = ps.flatMap(p => { const cs = ps.flatMap(p => {
if (p._variant === 'anonymous' && p.value._variant === 'SimplePattern') { if (p._variant === 'anonymous' && p.value._variant === 'SimplePattern') {
return []; return [];
} else { } else {
try { return [unconverterForNamed(ctx, p, src, t)];
return [unconverterForNamed(ctx, p, src, t)];
} catch (e) {
if (e instanceof InsufficientInformationError) {
errs.push([name + '/' + (M.nameFor(p) ?? '<anonymous>'), 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 [seq(`return `, (cs.length === 1) return [seq(`return `, (cs.length === 1)
? cs[0] ? cs[0]
: seq(`_.merge`, parens(`(a, b) => (a === b) ? a : void 0`, ... cs)))]; : seq(`_.merge`, parens(`(a, b) => (a === b) ? a : void 0`, ... cs)))];
} }
case 'Pattern': case 'Pattern':
return [seq(`return `, unconverterForPattern(ctx, name, def.value, `${src}`))]; return [seq(`return `, unconverterForPattern(ctx, def.value, `${src}`))];
} }
} }
export class InsufficientInformationError extends Error {} function unconverterForPattern(ctx: FunctionContext, a: M.Pattern, src: string): Item
function unconverterForPattern(ctx: FunctionContext, name: string, a: M.Pattern, src: string): Item
{ {
const t = typeFor(ctx.mod, a); return unconverterFor(ctx, a, src, typeFor(ctx.mod.resolver(), a));
try {
return unconverterFor(ctx, a, src, t);
} catch (e) {
if (e instanceof InsufficientInformationError) {
throw new Error(`Cannot produce unconverter for ${name}: ${e.message}`);
}
throw e;
}
} }
function stepSource( function stepSource(
@ -74,8 +51,7 @@ function stepSource(
{ {
if (t.kind !== 'record' || !t.fields.has(key)) { if (t.kind !== 'record' || !t.fields.has(key)) {
throw new Error( throw new Error(
`Internal error: attempt to step type ` + `Internal error: attempt to step type ${JSON.stringify(t)} with key ${key}`);
`${formatItems([renderType(t)])} with key ${key}`);
} }
return { return {
steppedSrc: `${src}[${JSON.stringify(key)}]`, steppedSrc: `${src}[${JSON.stringify(key)}]`,
@ -87,9 +63,6 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string, t: Simp
switch (p._variant) { switch (p._variant) {
case 'SimplePattern': case 'SimplePattern':
return ((p: M.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) { switch (p._variant) {
case 'any': case 'any':
return `${src}`; return `${src}`;
@ -141,10 +114,7 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string, t: Simp
`v`, `v`,
steppedType.type)))); steppedType.type))));
} else { } else {
if (t.kind !== 'array') { if (t.kind !== 'array') throw new Error("Internal error");
throw new InsufficientInformationError(
`B Cannot produce unconverter TODO ${src} ${formatItems([renderType(t)])}`);
}
varexp = seq(src, `.map`, parens( varexp = seq(src, `.map`, parens(
seq(`v => `, unconverterFor( seq(`v => `, unconverterFor(
ctx, ctx,
@ -161,19 +131,11 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string, t: Simp
} }
} }
case 'setof': case 'setof':
if (t.kind !== 'set') {
throw new InsufficientInformationError(
`C Cannot produce unconverter TODO ${src} ${formatItems([renderType(t)])}`);
}
return seq(`new _.Set<_embedded>`, parens( return seq(`new _.Set<_embedded>`, parens(
`_.Array.from(${src}.values()).map(v => `, `_.Array.from(${src}.values()).map(v => `,
unconverterFor(ctx, M.Pattern.SimplePattern(p.pattern), 'v', t), unconverterFor(ctx, M.Pattern.SimplePattern(p.pattern), 'v', t),
`)`)); `)`));
case 'dictof': 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( return seq(`new _.Dictionary<_embedded>`, parens(seq(
`_.Array.from(${src}.entries()).map(([k, v]) => `, `_.Array.from(${src}.entries()).map(([k, v]) => `,
brackets( brackets(

View File

@ -1,41 +1,6 @@
import { SimpleType, Type } from "../type";
import { anglebrackets, braces, Item, keyvalue, opseq, seq } from "./block"; import { anglebrackets, braces, Item, keyvalue, opseq, seq } from "./block";
export type Type =
| { kind: 'union', variants: VariantMap } // zero: never
| SimpleType
export type SimpleType = AtomicType | CompoundType
export type FieldType = AtomicType | CollectionType;
export type AtomicType =
| { kind: 'unit' }
| { kind: 'ref', typeName: string } // also for base types
export type CompoundType =
| CollectionType
| { kind: 'record', fields: FieldMap }
export type CollectionType =
| { kind: 'array', type: AtomicType }
| { kind: 'set', type: AtomicType }
| { kind: 'dictionary', key: AtomicType, value: AtomicType }
export type VariantMap = Map<string, SimpleType>;
export type FieldMap = Map<string, FieldType>;
export namespace Type {
export const union = (variants: VariantMap): Type => ({ kind: 'union', variants });
export const unit = (): AtomicType => ({ kind: 'unit' });
export const ref = (typeName: string): AtomicType => ({ kind: 'ref', typeName });
export const record = (fields: FieldMap): CompoundType => ({ kind: 'record', fields });
export const array = (type: AtomicType): CollectionType => ({ kind: 'array', type });
export const set = (type: AtomicType): CollectionType => ({ kind: 'set', type });
export const dictionary = (key: AtomicType, value: AtomicType): CollectionType => (
{ kind: 'dictionary', key, value });
}
export const ANY_TYPE: AtomicType = Type.ref('_val');
export function variantInitFor(variantName: string | undefined) : Item[] { export function variantInitFor(variantName: string | undefined) : Item[] {
return variantName === void 0 ? [] : [variantFor(variantName)]; return variantName === void 0 ? [] : [variantFor(variantName)];
} }

View File

@ -1,62 +1,62 @@
import { refPosition } from "../reader"; import * as M from "./meta";
import * as M from "../meta";
import { ModuleContext } from "./context";
import { ANY_TYPE, AtomicType, CollectionType, FieldMap, SimpleType, Type } from "./type"; import { ANY_TYPE, AtomicType, CollectionType, FieldMap, SimpleType, Type } from "./type";
export function typeForDefinition(mod: ModuleContext, d: M.Definition): Type { export type RefResolver = (ref: M.Ref) => AtomicType;
export function typeForDefinition(resolver: RefResolver, d: M.Definition): Type {
switch (d._variant) { switch (d._variant) {
case 'or': case 'or':
return Type.union( return Type.union(
new Map([d.pattern0, d.pattern1, ... d.patternN].map(a => new Map([d.pattern0, d.pattern1, ... d.patternN].map(a =>
[a.variantLabel, typeFor(mod, a.pattern)]))); [a.variantLabel, typeFor(resolver, a.pattern)])));
case 'and': case 'and':
return typeForIntersection(mod, [d.pattern0, d.pattern1, ... d.patternN]); return typeForIntersection(resolver, [d.pattern0, d.pattern1, ... d.patternN]);
case 'Pattern': case 'Pattern':
return typeFor(mod, d.value); return typeFor(resolver, d.value);
} }
} }
export function typeForIntersection(mod: ModuleContext, ps: M.NamedPattern[]): SimpleType { export function typeForIntersection(resolver: RefResolver, ps: M.NamedPattern[]): SimpleType {
const fs = new Map(); const fs = new Map();
ps.forEach(p => gatherFields(fs, mod, p)); ps.forEach(p => gatherFields(fs, resolver, p));
return fs.size > 0 ? Type.record(fs) : Type.unit(); return fs.size > 0 ? Type.record(fs) : Type.unit();
} }
export function setType(mod: ModuleContext, p: M.SimplePattern): CollectionType { export function setType(resolver: RefResolver, p: M.SimplePattern): CollectionType {
return Type.set(simpleType(mod, p)); return Type.set(simpleType(resolver, p));
} }
export function dictionaryType(mod: ModuleContext, export function dictionaryType(resolver: RefResolver,
kp: M.SimplePattern, kp: M.SimplePattern,
vp: M.SimplePattern): CollectionType vp: M.SimplePattern): CollectionType
{ {
return Type.dictionary(simpleType(mod, kp), simpleType(mod, vp)); return Type.dictionary(simpleType(resolver, kp), simpleType(resolver, vp));
} }
export function typeFor(mod: ModuleContext, p: M.Pattern): SimpleType { export function typeFor(resolver: RefResolver, p: M.Pattern): SimpleType {
if (p._variant === 'SimplePattern') { if (p._variant === 'SimplePattern') {
return simpleType(mod, p.value); return simpleType(resolver, p.value);
} else { } else {
switch (p.value._variant) { switch (p.value._variant) {
case 'setof': case 'setof':
return setType(mod, p.value.pattern); return setType(resolver, p.value.pattern);
case 'dictof': case 'dictof':
return dictionaryType(mod, p.value.key, p.value.value); return dictionaryType(resolver, p.value.key, p.value.value);
default: { default: {
const arrayType = M.simpleArray(p.value); const arrayType = M.simpleArray(p.value);
if (arrayType === void 0) { if (arrayType === void 0) {
const fs = new Map(); const fs = new Map();
compoundFields(fs, mod, p.value); compoundFields(fs, resolver, p.value);
return fs.size > 0 ? Type.record(fs) : Type.unit(); return fs.size > 0 ? Type.record(fs) : Type.unit();
} else { } else {
return Type.array(simpleType(mod, arrayType)); return Type.array(simpleType(resolver, arrayType));
} }
} }
} }
} }
} }
export function simpleType(mod: ModuleContext, p: M.SimplePattern): AtomicType { export function simpleType(resolver: RefResolver, p: M.SimplePattern): AtomicType {
switch (p._variant) { switch (p._variant) {
case 'any': case 'any':
return ANY_TYPE; return ANY_TYPE;
@ -75,32 +75,27 @@ export function simpleType(mod: ModuleContext, p: M.SimplePattern): AtomicType {
case 'lit': case 'lit':
return Type.unit(); return Type.unit();
case 'Ref': case 'Ref':
return M.lookup(refPosition(p.value), p.value, mod.env, return resolver(p.value);
(_p) => Type.ref(p.value.name.description!),
(modId, modPath,_p) => {
mod.imports.add([modId, modPath]);
return Type.ref(`${modId}.${p.value.name.description!}`);
});
default: default:
((_p: never) => {})(p); ((_p: never) => {})(p);
throw new Error("Unreachable"); throw new Error("Unreachable");
} }
} }
function compoundFields(fs: FieldMap, mod: ModuleContext, p: M.CompoundPattern): void { function compoundFields(fs: FieldMap, resolver: RefResolver, p: M.CompoundPattern): void {
switch (p._variant) { switch (p._variant) {
case 'rec': case 'rec':
gatherFields(fs, mod, p.label); gatherFields(fs, resolver, p.label);
gatherFields(fs, mod, p.fields); gatherFields(fs, resolver, p.fields);
break; break;
case 'tuple': case 'tuple':
p.patterns.forEach(pp => gatherFields(fs, mod, pp)); p.patterns.forEach(pp => gatherFields(fs, resolver, pp));
break; break;
case 'tuple*': { case 'tuple*': {
p.fixed.forEach(pp => gatherFields(fs, mod, pp)); p.fixed.forEach(pp => gatherFields(fs, resolver, pp));
const n = p.variable; const n = p.variable;
if (n._variant === 'named') { if (n._variant === 'named') {
fs.set(n.value.name.description!, Type.array(simpleType(mod, n.value.pattern))); fs.set(n.value.name.description!, Type.array(simpleType(resolver, n.value.pattern)));
} }
break; break;
} }
@ -109,7 +104,7 @@ function compoundFields(fs: FieldMap, mod: ModuleContext, p: M.CompoundPattern):
break; break;
case 'dict': case 'dict':
p.entries.forEach((n, k) => p.entries.forEach((n, k) =>
gatherFields(fs, mod, M.promoteNamedSimplePattern(M.addNameIfAbsent(n, k)))); gatherFields(fs, resolver, M.promoteNamedSimplePattern(M.addNameIfAbsent(n, k))));
break; break;
default: default:
((_p: never) => {})(p); ((_p: never) => {})(p);
@ -117,10 +112,10 @@ function compoundFields(fs: FieldMap, mod: ModuleContext, p: M.CompoundPattern):
} }
} }
function gatherFields(fs: FieldMap, mod: ModuleContext, n: M.NamedPattern): void { function gatherFields(fs: FieldMap, resolver: RefResolver, n: M.NamedPattern): void {
if (n._variant === 'named') { if (n._variant === 'named') {
fs.set(n.value.name.description!, simpleType(mod, n.value.pattern)); fs.set(n.value.name.description!, simpleType(resolver, n.value.pattern));
} else if (n.value._variant === 'CompoundPattern') { } else if (n.value._variant === 'CompoundPattern') {
compoundFields(fs, mod, n.value.value); compoundFields(fs, resolver, n.value.value);
} }
} }

View File

@ -2,6 +2,7 @@ import { Reader, Annotated, Dictionary, is, peel, preserves, Record, strip, Tupl
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';
import { checkSchema } from './checker';
const positionTable = new WeakMap<object, Position>(); const positionTable = new WeakMap<object, Position>();
@ -58,7 +59,7 @@ function _readSchema(source: string, options?: ReaderOptions<never>): Array<Inpu
export function readSchema(source: string, export function readSchema(source: string,
options?: ReaderOptions<never> & SchemaReaderOptions): Schema options?: ReaderOptions<never> & SchemaReaderOptions): Schema
{ {
return parseSchema(_readSchema(source, options), options ?? {}); return checkSchema(parseSchema(_readSchema(source, options), options ?? {}));
} }
export function parseSchema(toplevelTokens: Array<Input>, export function parseSchema(toplevelTokens: Array<Input>,

View File

@ -0,0 +1,35 @@
export type Type =
| { kind: 'union', variants: VariantMap } // zero: never
| SimpleType
export type SimpleType = AtomicType | CompoundType
export type FieldType = AtomicType | CollectionType;
export type AtomicType =
| { kind: 'unit' }
| { kind: 'ref', typeName: string } // also for base types
export type CompoundType =
| CollectionType
| { kind: 'record', fields: FieldMap }
export type CollectionType =
| { kind: 'array', type: AtomicType }
| { kind: 'set', type: AtomicType }
| { kind: 'dictionary', key: AtomicType, value: AtomicType }
export type VariantMap = Map<string, SimpleType>;
export type FieldMap = Map<string, FieldType>;
export namespace Type {
export const union = (variants: VariantMap): Type => ({ kind: 'union', variants });
export const unit = (): AtomicType => ({ kind: 'unit' });
export const ref = (typeName: string): AtomicType => ({ kind: 'ref', typeName });
export const record = (fields: FieldMap): CompoundType => ({ kind: 'record', fields });
export const array = (type: AtomicType): CollectionType => ({ kind: 'array', type });
export const set = (type: AtomicType): CollectionType => ({ kind: 'set', type });
export const dictionary = (key: AtomicType, value: AtomicType): CollectionType => (
{ kind: 'dictionary', key, value });
}
export const ANY_TYPE: AtomicType = Type.ref('_val');