diff --git a/implementations/javascript/packages/schema/package.json b/implementations/javascript/packages/schema/package.json index 932a9bb..83a51bb 100644 --- a/implementations/javascript/packages/schema/package.json +++ b/implementations/javascript/packages/schema/package.json @@ -1,6 +1,6 @@ { "name": "@preserves/schema", - "version": "0.9.0", + "version": "0.11.0", "description": "Schema support for Preserves data serialization format", "homepage": "https://gitlab.com/preserves/preserves", "license": "Apache-2.0", diff --git a/implementations/javascript/packages/schema/src/checker.ts b/implementations/javascript/packages/schema/src/checker.ts new file mode 100644 index 0000000..77fcf49 --- /dev/null +++ b/implementations/javascript/packages/schema/src/checker.ts @@ -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 = []; + + recordProblem(context: string, detail: string): void { + this.problems.push(`${detail} in ${context}`); + } + + checkBinding(scope: Set, 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(); + [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, 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, 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); + } + } +} diff --git a/implementations/javascript/packages/schema/src/compiler.ts b/implementations/javascript/packages/schema/src/compiler.ts index adc7fbb..ee8f4bc 100644 --- a/implementations/javascript/packages/schema/src/compiler.ts +++ b/implementations/javascript/packages/schema/src/compiler.ts @@ -2,9 +2,10 @@ import { stringify } from "@preserves/core"; import * as M from "./meta"; import { CompilerOptions, ModuleContext } from "./compiler/context"; import { Formatter, block, seq } from "./compiler/block"; -import { typeForDefinition } from "./compiler/gentype"; +import { typeForDefinition } from "./gentype"; 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 { unconverterForDefinition } from "./compiler/genunconverter"; 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 = `, renderType(embeddedName._variant === 'false' ? 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>;`); for (const [name, def] of schema.definitions) { - const t = typeForDefinition(mod, def); + const t = typeForDefinition(mod.resolver(), def); const nameStr = stringify(name); 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 => seq(`export function from${name.description!}`, '(_v: ', name.description!, '): _val ', - ctx.block(() => unconverterForDefinition(ctx, name.description!, def, '_v')))); + ctx.block(() => unconverterForDefinition(ctx, def, '_v')))); } const f = new Formatter(); diff --git a/implementations/javascript/packages/schema/src/compiler/context.ts b/implementations/javascript/packages/schema/src/compiler/context.ts index 692b07b..13b36a8 100644 --- a/implementations/javascript/packages/schema/src/compiler/context.ts +++ b/implementations/javascript/packages/schema/src/compiler/context.ts @@ -2,7 +2,8 @@ import { Dictionary, KeyedSet, Position } from "@preserves/core"; import { refPosition } from "../reader"; import * as M from "../meta"; 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 { preservesModule?: string; @@ -67,6 +68,15 @@ export class ModuleContext { defineFunction(f: (ctx: FunctionContext) => Item): void { 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 { diff --git a/implementations/javascript/packages/schema/src/compiler/genconverter.ts b/implementations/javascript/packages/schema/src/compiler/genconverter.ts index 320ccb5..0e6a014 100644 --- a/implementations/javascript/packages/schema/src/compiler/genconverter.ts +++ b/implementations/javascript/packages/schema/src/compiler/genconverter.ts @@ -1,9 +1,9 @@ import { FunctionContext } from "./context"; import * as M from '../meta'; 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 { ANY_TYPE, Type } from "./type"; +import { ANY_TYPE, Type } from "../type"; export function converterForDefinition( ctx: FunctionContext, @@ -48,7 +48,7 @@ function converterForPattern( if (simpleValue === void 0) { return [ctx.buildCapturedCompound(dest)]; } 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)]; } else { return [ctx.withCapture('value', @@ -98,7 +98,7 @@ function converterForArray(ctx: FunctionContext, k: (dest: string) => Item[]): Item { 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(); return [ seq(`${r} = []`), @@ -125,14 +125,14 @@ function converterFor( let maybeName = M.nameFor(np); 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), ctx.convertCapture(maybeName, dest, ks)]; } else { switch (p.value._variant) { case 'setof': { 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(); return [ seq(`if (_.Set.isSet<_embedded>(${src})) `, ctx.block(() => [ @@ -147,7 +147,7 @@ function converterFor( case 'dictof': { const keyPattern = p.value.key; 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 k = ctx.gentempname(); return [ diff --git a/implementations/javascript/packages/schema/src/compiler/genctor.ts b/implementations/javascript/packages/schema/src/compiler/genctor.ts index 29d8c05..ebdb859 100644 --- a/implementations/javascript/packages/schema/src/compiler/genctor.ts +++ b/implementations/javascript/packages/schema/src/compiler/genctor.ts @@ -1,6 +1,7 @@ import * as M from '../meta'; 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( name: string, diff --git a/implementations/javascript/packages/schema/src/compiler/genunconverter.ts b/implementations/javascript/packages/schema/src/compiler/genunconverter.ts index f6106f7..a8259e0 100644 --- a/implementations/javascript/packages/schema/src/compiler/genunconverter.ts +++ b/implementations/javascript/packages/schema/src/compiler/genunconverter.ts @@ -2,12 +2,12 @@ import { refPosition } from '../reader'; import * as M from '../meta'; import { block, brackets, formatItems, Item, parens, seq } from './block'; import { FunctionContext } from "./context"; -import { FieldType, renderType, SimpleType } from './type'; -import { typeFor, typeForIntersection } from './gentype'; +import { FieldType, SimpleType } from '../type'; +import { typeFor, typeForIntersection } from '../gentype'; +import { renderType } from "./rendertype"; export function unconverterForDefinition( ctx: FunctionContext, - name: string, def: M.Definition, src: string): Item[] { @@ -18,53 +18,30 @@ export function unconverterForDefinition( seq(`case `, JSON.stringify(p.variantLabel), `: `, ctx.block(() => { const hasValueField = p.pattern._variant === 'SimplePattern'; return [seq(`return `, unconverterForPattern( - ctx, name, p.pattern, hasValueField ? `${src}.value` : src))]; + ctx, p.pattern, hasValueField ? `${src}.value` : src))]; })))))]; case 'and': { const ps = [def.pattern0, def.pattern1, ... def.patternN]; - const t = typeForIntersection(ctx.mod, ps); - const errs: [string, InsufficientInformationError][] = []; + const t = typeForIntersection(ctx.mod.resolver(), ps); const cs = ps.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; - } + return [unconverterForNamed(ctx, p, src, t)]; } }); - 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) ? cs[0] : seq(`_.merge`, parens(`(a, b) => (a === b) ? a : void 0`, ... cs)))]; } 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, name: string, a: M.Pattern, src: string): Item +function unconverterForPattern(ctx: FunctionContext, a: M.Pattern, src: string): Item { - const t = typeFor(ctx.mod, 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; - } + return unconverterFor(ctx, a, src, typeFor(ctx.mod.resolver(), a)); } function stepSource( @@ -74,8 +51,7 @@ function stepSource( { if (t.kind !== 'record' || !t.fields.has(key)) { throw new Error( - `Internal error: attempt to step type ` + - `${formatItems([renderType(t)])} with key ${key}`); + `Internal error: attempt to step type ${JSON.stringify(t)} with key ${key}`); } return { steppedSrc: `${src}[${JSON.stringify(key)}]`, @@ -87,9 +63,6 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string, t: Simp 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}`; @@ -141,10 +114,7 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string, t: Simp `v`, steppedType.type)))); } else { - if (t.kind !== 'array') { - throw new InsufficientInformationError( - `B Cannot produce unconverter TODO ${src} ${formatItems([renderType(t)])}`); - } + if (t.kind !== 'array') throw new Error("Internal error"); varexp = seq(src, `.map`, parens( seq(`v => `, unconverterFor( ctx, @@ -161,19 +131,11 @@ function unconverterFor(ctx: FunctionContext, p: M.Pattern, src: string, t: Simp } } 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( diff --git a/implementations/javascript/packages/schema/src/compiler/type.ts b/implementations/javascript/packages/schema/src/compiler/rendertype.ts similarity index 56% rename from implementations/javascript/packages/schema/src/compiler/type.ts rename to implementations/javascript/packages/schema/src/compiler/rendertype.ts index 1a38ef8..b2fc868 100644 --- a/implementations/javascript/packages/schema/src/compiler/type.ts +++ b/implementations/javascript/packages/schema/src/compiler/rendertype.ts @@ -1,41 +1,6 @@ +import { SimpleType, Type } from "../type"; 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; -export type FieldMap = Map; - -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[] { return variantName === void 0 ? [] : [variantFor(variantName)]; } diff --git a/implementations/javascript/packages/schema/src/compiler/gentype.ts b/implementations/javascript/packages/schema/src/gentype.ts similarity index 52% rename from implementations/javascript/packages/schema/src/compiler/gentype.ts rename to implementations/javascript/packages/schema/src/gentype.ts index 69ad8db..22ccea5 100644 --- a/implementations/javascript/packages/schema/src/compiler/gentype.ts +++ b/implementations/javascript/packages/schema/src/gentype.ts @@ -1,62 +1,62 @@ -import { refPosition } from "../reader"; -import * as M from "../meta"; -import { ModuleContext } from "./context"; +import * as M from "./meta"; 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) { case 'or': return Type.union( new Map([d.pattern0, d.pattern1, ... d.patternN].map(a => - [a.variantLabel, typeFor(mod, a.pattern)]))); + [a.variantLabel, typeFor(resolver, a.pattern)]))); case 'and': - return typeForIntersection(mod, [d.pattern0, d.pattern1, ... d.patternN]); + return typeForIntersection(resolver, [d.pattern0, d.pattern1, ... d.patternN]); 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(); - ps.forEach(p => gatherFields(fs, mod, p)); + ps.forEach(p => gatherFields(fs, resolver, p)); return fs.size > 0 ? Type.record(fs) : Type.unit(); } -export function setType(mod: ModuleContext, p: M.SimplePattern): CollectionType { - return Type.set(simpleType(mod, p)); +export function setType(resolver: RefResolver, p: M.SimplePattern): CollectionType { + return Type.set(simpleType(resolver, p)); } -export function dictionaryType(mod: ModuleContext, +export function dictionaryType(resolver: RefResolver, kp: M.SimplePattern, 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') { - return simpleType(mod, p.value); + return simpleType(resolver, p.value); } else { switch (p.value._variant) { case 'setof': - return setType(mod, p.value.pattern); + return setType(resolver, p.value.pattern); case 'dictof': - return dictionaryType(mod, p.value.key, p.value.value); + return dictionaryType(resolver, p.value.key, p.value.value); default: { const arrayType = M.simpleArray(p.value); if (arrayType === void 0) { const fs = new Map(); - compoundFields(fs, mod, p.value); + compoundFields(fs, resolver, p.value); return fs.size > 0 ? Type.record(fs) : Type.unit(); } 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) { case 'any': return ANY_TYPE; @@ -75,32 +75,27 @@ export function simpleType(mod: ModuleContext, p: M.SimplePattern): AtomicType { case 'lit': return Type.unit(); case 'Ref': - return M.lookup(refPosition(p.value), p.value, mod.env, - (_p) => Type.ref(p.value.name.description!), - (modId, modPath,_p) => { - mod.imports.add([modId, modPath]); - return Type.ref(`${modId}.${p.value.name.description!}`); - }); + return resolver(p.value); default: ((_p: never) => {})(p); 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) { case 'rec': - gatherFields(fs, mod, p.label); - gatherFields(fs, mod, p.fields); + gatherFields(fs, resolver, p.label); + gatherFields(fs, resolver, p.fields); break; case 'tuple': - p.patterns.forEach(pp => gatherFields(fs, mod, pp)); + p.patterns.forEach(pp => gatherFields(fs, resolver, pp)); break; case 'tuple*': { - p.fixed.forEach(pp => gatherFields(fs, mod, pp)); + p.fixed.forEach(pp => gatherFields(fs, resolver, pp)); const n = p.variable; 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; } @@ -109,7 +104,7 @@ function compoundFields(fs: FieldMap, mod: ModuleContext, p: M.CompoundPattern): break; case 'dict': p.entries.forEach((n, k) => - gatherFields(fs, mod, M.promoteNamedSimplePattern(M.addNameIfAbsent(n, k)))); + gatherFields(fs, resolver, M.promoteNamedSimplePattern(M.addNameIfAbsent(n, k)))); break; default: ((_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') { - 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') { - compoundFields(fs, mod, n.value.value); + compoundFields(fs, resolver, n.value.value); } } diff --git a/implementations/javascript/packages/schema/src/reader.ts b/implementations/javascript/packages/schema/src/reader.ts index a040769..ce47e54 100644 --- a/implementations/javascript/packages/schema/src/reader.ts +++ b/implementations/javascript/packages/schema/src/reader.ts @@ -2,6 +2,7 @@ import { Reader, Annotated, Dictionary, is, peel, preserves, Record, strip, Tupl import { Input, Pattern, Schema, Definition, CompoundPattern, SimplePattern } from './meta'; import * as M from './meta'; import { SchemaSyntaxError } from './error'; +import { checkSchema } from './checker'; const positionTable = new WeakMap(); @@ -58,7 +59,7 @@ function _readSchema(source: string, options?: ReaderOptions): Array & SchemaReaderOptions): Schema { - return parseSchema(_readSchema(source, options), options ?? {}); + return checkSchema(parseSchema(_readSchema(source, options), options ?? {})); } export function parseSchema(toplevelTokens: Array, diff --git a/implementations/javascript/packages/schema/src/type.ts b/implementations/javascript/packages/schema/src/type.ts new file mode 100644 index 0000000..e03d1de --- /dev/null +++ b/implementations/javascript/packages/schema/src/type.ts @@ -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; +export type FieldMap = Map; + +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');