Move insufficient-information checking to read-time
This commit is contained in:
parent
ecdb314366
commit
20b676df27
|
@ -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",
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 [
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)];
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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>,
|
||||||
|
|
|
@ -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');
|
Loading…
Reference in New Issue