From 889d38bbb804d5a344592cef7d6a9e6e5ac60731 Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Fri, 19 Mar 2021 23:42:43 +0100 Subject: [PATCH] Fix types. --- .../javascript/packages/schema/src/base.ts | 18 -- .../packages/schema/src/compiler.ts | 12 +- .../packages/schema/src/compiler/context.ts | 21 +- .../{converter.ts => genconverter.ts} | 29 +-- .../compiler/{decoder.ts => gendecoder.ts} | 0 .../packages/schema/src/compiler/gentype.ts | 125 ++++++++++++ .../packages/schema/src/compiler/type.ts | 186 ++++++++---------- .../javascript/packages/schema/src/index.ts | 1 - .../javascript/packages/schema/src/meta.ts | 17 +- .../javascript/packages/schema/src/reader.ts | 53 +++-- 10 files changed, 279 insertions(+), 183 deletions(-) delete mode 100644 implementations/javascript/packages/schema/src/base.ts rename implementations/javascript/packages/schema/src/compiler/{converter.ts => genconverter.ts} (90%) rename implementations/javascript/packages/schema/src/compiler/{decoder.ts => gendecoder.ts} (100%) create mode 100644 implementations/javascript/packages/schema/src/compiler/gentype.ts diff --git a/implementations/javascript/packages/schema/src/base.ts b/implementations/javascript/packages/schema/src/base.ts deleted file mode 100644 index 762d0a6..0000000 --- a/implementations/javascript/packages/schema/src/base.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Record, Dictionary } from '@preserves/core'; -import * as M from './gen/schema'; - -export const BASE: M.Schema = M.asSchema(Record(M.$schema, [new Dictionary([ - [M.$version, 1], - [M.$pointer, false], - [M.$definitions, new Dictionary([ - [Symbol.for('any'), Record(M.$and, [[] as M.Pattern[]])], - [Symbol.for('bool'), Record(M.$atom, [M.$Boolean])], - [Symbol.for('float'), Record(M.$atom, [M.$Float])], - [Symbol.for('double'), Record(M.$atom, [M.$Double])], - [Symbol.for('int'), Record(M.$atom, [M.$SignedInteger])], - [Symbol.for('string'), Record(M.$atom, [M.$String])], - [Symbol.for('bytes'), Record(M.$atom, [M.$ByteString])], - [Symbol.for('symbol'), Record(M.$atom, [M.$Symbol])], - [Symbol.for('ref'), Record(M.$pointer, [])], - ])], -])])); diff --git a/implementations/javascript/packages/schema/src/compiler.ts b/implementations/javascript/packages/schema/src/compiler.ts index 45d0ae3..1fd0ad9 100644 --- a/implementations/javascript/packages/schema/src/compiler.ts +++ b/implementations/javascript/packages/schema/src/compiler.ts @@ -2,16 +2,19 @@ import { Annotated, Bytes, Dictionary, Fold, fold, Record, stringify, Tuple, Val import * as M from "./meta"; import { CompilerOptions, ModuleContext } from "./compiler/context"; import { brackets, Formatter, Item, parens, seq } from "./compiler/block"; -import { typeForDefinition } from "./compiler/type"; +import { typeForDefinition } from "./compiler/gentype"; // import { decoderFor } from "./compiler/decoder"; -import { converterForDefinition } from "./compiler/converter"; +import { converterForDefinition } from "./compiler/genconverter"; +import { EMPTY_TYPE, renderType } from "./compiler/type"; export function compile(env: M.Environment, schema: M.Schema, options: CompilerOptions = {}): string { const mod = new ModuleContext(env, schema, options); const pointerName = M.Schema._._field0(schema).get(M.$pointer); mod.defineType(seq(`export type _ptr = `, - pointerName === false ? 'never' : typeForDefinition(mod, pointerName), + renderType(pointerName === false + ? EMPTY_TYPE + : typeForDefinition(mod, pointerName)), `;`)); mod.defineType(`export type _val = _.Value<_ptr>;`); @@ -28,7 +31,8 @@ export function compile(env: M.Environment, schema: M.Schema, options: CompilerO for (const [name, def] of M.Schema._._field0(schema).get(M.$definitions)) { mod.defineType( - seq(`export type ${stringify(name)} = `, typeForDefinition(mod, def), `;`)); + seq(`export type ${stringify(name)} = `, + renderType(typeForDefinition(mod, def)), `;`)); } for (const [name0, def] of M.Schema._._field0(schema).get(M.$definitions)) { diff --git a/implementations/javascript/packages/schema/src/compiler/context.ts b/implementations/javascript/packages/schema/src/compiler/context.ts index d2fbf19..f7d2c8e 100644 --- a/implementations/javascript/packages/schema/src/compiler/context.ts +++ b/implementations/javascript/packages/schema/src/compiler/context.ts @@ -2,6 +2,7 @@ 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"; export interface CompilerOptions { preservesModule?: string; @@ -45,7 +46,6 @@ export class ModuleContext { derefPattern([_name, p]: [string, M.Alternative]): M.Definition { if (p.label === M.$ref) { return M.lookup(refPosition(p), p, this.env, - (p) => p, (p) => p, (_modId, _modPath, pp) => pp ?? p); } else { @@ -67,6 +67,7 @@ export class FunctionContext { tempCounter = 0; temps: Map = new Map(); + captures: Capture[] = []; variantName: string | undefined = void 0; @@ -78,13 +79,13 @@ export class FunctionContext { return '_tmp' + this.tempCounter++; } - gentemp(... vartypePieces: Item[]): string { - const vartype = vartypePieces.length === 0 ? '_val | undefined' : seq(... vartypePieces); - const typestr = formatItems([vartype], Infinity); + gentemp(vartype: Type = ANY_TYPE): string { + const typeitem = renderType(vartype); + const typestr = formatItems([typeitem], Infinity); const varname = this.gentempname(); let e = this.temps.get(typestr); if (e === void 0) { - e = { type: vartype, names: [] }; + e = { type: typeitem, names: [] }; this.temps.set(typestr, e); } e.names.push(varname); @@ -99,7 +100,7 @@ export class FunctionContext { this.temps = oldTemps; return block( ... Array.from(ts).map(([_typestr, { type, names }]) => - seq(`let `, commas(... names), `: `, type)), + seq(`let `, commas(... names), `: (`, type, `) | undefined`)), ... items); } @@ -126,11 +127,3 @@ export class FunctionContext { keyvalue(fieldName, sourceExpr)))); } } - -export function variantInitFor(variantName: string | undefined) : Item[] { - return variantName === void 0 ? [] : [variantFor(variantName)]; -} - -export function variantFor(variantName: string): Item { - return keyvalue('_variant', JSON.stringify(variantName)); -} diff --git a/implementations/javascript/packages/schema/src/compiler/converter.ts b/implementations/javascript/packages/schema/src/compiler/genconverter.ts similarity index 90% rename from implementations/javascript/packages/schema/src/compiler/converter.ts rename to implementations/javascript/packages/schema/src/compiler/genconverter.ts index 3a8fd4d..175e5d3 100644 --- a/implementations/javascript/packages/schema/src/compiler/converter.ts +++ b/implementations/javascript/packages/schema/src/compiler/genconverter.ts @@ -1,8 +1,9 @@ import { FunctionContext } from "./context"; import * as M from '../meta'; import { block, Item, seq } from "./block"; -import { typeFor } from "./type"; +import { simpleType, dictionaryType, setType, typeFor } from "./gentype"; import { refPosition } from "../reader"; +import { ANY_TYPE, Type } from "./type"; export function converterForDefinition( ctx: FunctionContext, @@ -45,9 +46,13 @@ function converterForAlternative( if (simpleValue === void 0) { return [ctx.buildCapturedCompound(dest)]; } else if (ctx.variantName !== void 0) { - return [ctx.withCapture('value', - simpleValue, - () => ctx.buildCapturedCompound(dest))]; + if (typeFor(ctx.mod, p).kind === 'unit') { + return [ctx.buildCapturedCompound(dest)]; + } else { + return [ctx.withCapture('value', + simpleValue, + () => ctx.buildCapturedCompound(dest))]; + } } else { return [`${dest} = ${simpleValue}`]; } @@ -69,7 +74,7 @@ function converterForTuple(ctx: FunctionContext, if (variablePattern === void 0) { return k(); } else { - const vN = ctx.gentemp(`Array<_val>`); + const vN = ctx.gentemp(Type.array(ANY_TYPE)); return [ps.length > 0 ? `${vN} = ${src}.slice(${ps.length})` : `${vN} = ${src}`, converterForArray(ctx, variablePattern, vN, false, k)]; } @@ -92,8 +97,7 @@ function converterForArray(ctx: FunctionContext, k: (dest: string) => Item[]): Item { const postCheck = () => { - const r = ctx.gentemp( - `Array<`, typeFor(ctx.mod, M.unname(arrayType)), `> | undefined`); + const r = ctx.gentemp(Type.array(simpleType(ctx.mod, M.unname(arrayType)))); const v = ctx.gentempname(); return [ seq(`${r} = []`), @@ -119,14 +123,14 @@ function converterFor( let maybeName = M.nameFor(np); if (M.isSimplePattern(p)) { - const dest = ctx.gentemp(typeFor(ctx.mod, p), ` | undefined`); + const dest = ctx.gentemp(simpleType(ctx.mod, p)); return [... converterForSimple(ctx, p, src, dest), ctx.convertCapture(maybeName, dest, ks)]; } else { switch (p.label) { case M.$setof: { const setPattern = p[0]; - const r = ctx.gentemp(typeFor(ctx.mod, p), ` | undefined`); + const r = ctx.gentemp(setType(ctx.mod, setPattern)); const v = ctx.gentempname(); return [ seq(`if (_.Set.isSet<_ptr>(${src})) `, ctx.block(() => [ @@ -141,7 +145,7 @@ function converterFor( case M.$dictof: { const keyPattern = p[0]; const valPattern = p[1]; - const r = ctx.gentemp(typeFor(ctx.mod, p), ` | undefined`); + const r = ctx.gentemp(dictionaryType(ctx.mod, keyPattern, valPattern)); const v = ctx.gentempname(); const k = ctx.gentempname(); return [ @@ -174,6 +178,8 @@ function converterForSimple( dest: string): Item[] { switch (p.label) { + case M.$any: + return [`${dest} = ${src}`]; case M.$atom: { let test: Item; switch (p[0]) { @@ -188,11 +194,10 @@ function converterForSimple( return [seq(`${dest} = `, test, ` ? ${src} : void 0`)]; } case M.$lit: - return [`${dest} = _.is(${src}, ${ctx.mod.literal(p[0])}) ? ${ctx.mod.literal(p[0])} : void 0`]; + return [`${dest} = _.is(${src}, ${ctx.mod.literal(p[0])}) ? null : void 0`]; case M.$ref: return M.lookup(refPosition(p), p, ctx.mod.env, (_p) => [`${dest} = to${p[1].description!}(${src})`], - (p) => converterForAlternative(ctx, p, src, dest), (modId, modPath,_p) => { ctx.mod.imports.add([modId, modPath]); return [`${dest} = ${modId}.decode${p[1].description!}(${src})`]; diff --git a/implementations/javascript/packages/schema/src/compiler/decoder.ts b/implementations/javascript/packages/schema/src/compiler/gendecoder.ts similarity index 100% rename from implementations/javascript/packages/schema/src/compiler/decoder.ts rename to implementations/javascript/packages/schema/src/compiler/gendecoder.ts diff --git a/implementations/javascript/packages/schema/src/compiler/gentype.ts b/implementations/javascript/packages/schema/src/compiler/gentype.ts new file mode 100644 index 0000000..03e1cc5 --- /dev/null +++ b/implementations/javascript/packages/schema/src/compiler/gentype.ts @@ -0,0 +1,125 @@ +import { refPosition } from "../reader"; +import * as M from "../meta"; +import { ModuleContext } from "./context"; +import { ANY_TYPE, AtomicType, CollectionType, FieldMap, SimpleType, Type } from "./type"; + +export function typeForDefinition(mod: ModuleContext, d: M.Definition): Type { + if (d.label === M.$or) { + return Type.union( + new Map(d[0].map(a => [a[0], typeForAlternative(mod, a[1])]))); + } else { + return typeForAlternative(mod, d); + } +} + +function typeForAlternative(mod: ModuleContext, a: M.Alternative): SimpleType { + if (a.label === M.$and) { + const fs = new Map(); + a[0].forEach(n => gatherFields(fs, mod, n)); + return Type.record(fs); + } else { + return typeFor(mod, a); + } +} + +export function setType(mod: ModuleContext, p: M.SimplePattern): CollectionType { + return Type.set(simpleType(mod, p)); +} + +export function dictionaryType(mod: ModuleContext, + kp: M.SimplePattern, + vp: M.SimplePattern): CollectionType +{ + return Type.dictionary(simpleType(mod, kp), simpleType(mod, vp)); +} + +export function typeFor(mod: ModuleContext, p: M.Pattern): SimpleType { + if (M.isSimplePattern(p)) { + return simpleType(mod, p); + } else { + switch (p.label) { + case M.$setof: + return setType(mod, p[0]); + case M.$dictof: + return dictionaryType(mod, p[0], p[1]); + default: { + const arrayType = M.simpleArray(p); + if (arrayType === void 0) { + const fs = new Map(); + compoundFields(fs, mod, p); + return Type.record(fs); + } else { + return Type.array(simpleType(mod, arrayType)); + } + } + } + } +} + +export function simpleType(mod: ModuleContext, p: M.SimplePattern): AtomicType { + switch (p.label) { + case M.$any: + return ANY_TYPE; + case M.$atom: + switch (p[0]) { + case M.$Boolean: return Type.ref(`boolean`); + case M.$Float: return Type.ref(`_.SingleFloat`); + case M.$Double: return Type.ref(`_.DoubleFloat`); + case M.$SignedInteger: return Type.ref(`number`); + case M.$String: return Type.ref(`string`); + case M.$ByteString: return Type.ref(`_.Bytes`); + case M.$Symbol: return Type.ref(`symbol`); + } + case M.$pointer: + return Type.ref(`_ptr`); + case M.$lit: + return Type.unit(); + case M.$ref: + return M.lookup(refPosition(p), p, mod.env, + (_p) => Type.ref(p[1].description!), + (modId, modPath,_p) => { + mod.imports.add([modId, modPath]); + return Type.ref(`${modId}.${p[1].description!}`); + }); + default: + ((_p: never) => {})(p); + throw new Error("Unreachable"); + } +} + +function compoundFields(fs: FieldMap, mod: ModuleContext, p: M.CompoundPattern): void { + switch (p.label) { + case M.$rec: + gatherFields(fs, mod, p[0]); + gatherFields(fs, mod, p[1]); + break; + case M.$tuple: + p[0].forEach(pp => gatherFields(fs, mod, pp)); + break; + case M.$tuple_STAR_: { + p[0].forEach(pp => gatherFields(fs, mod, pp)); + const n = p[1]; + if (n.label === M.$named) { + fs.set(n[0].description!, Type.array(simpleType(mod, n[1]))); + } + break; + } + case M.$setof: + case M.$dictof: + break; + case M.$dict: + p[0].forEach((n, k) => gatherFields(fs, mod, M.addNameIfAbsent(n, k))); + break; + default: + ((_p: never) => {})(p); + throw new Error("Unreachable"); + } +} + +function gatherFields(fs: FieldMap, mod: ModuleContext, n: M.NamedPattern): void { + if (n.label === M.$named) { + fs.set(n[0].description!, simpleType(mod, n[1])); + } else if (M.isCompoundPattern(n)) { + compoundFields(fs, mod, n); + } +} diff --git a/implementations/javascript/packages/schema/src/compiler/type.ts b/implementations/javascript/packages/schema/src/compiler/type.ts index dcf8c54..ff72e67 100644 --- a/implementations/javascript/packages/schema/src/compiler/type.ts +++ b/implementations/javascript/packages/schema/src/compiler/type.ts @@ -1,120 +1,90 @@ -import { refPosition } from "../reader"; -import * as M from "../meta"; import { anglebrackets, braces, Item, keyvalue, opseq, seq } from "./block"; -import { ModuleContext, variantFor, variantInitFor } from "./context"; -export function typeForDefinition(mod: ModuleContext, d: M.Definition): Item { - if (d.label === M.$or) { - return opseq('never', ' | ', ... d[0].map(a => typeForAlternative(mod, a[1], a[0]))); - } else { - return typeForAlternative(mod, d, void 0); - } +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 }); } -function typeForAlternative(mod: ModuleContext, a: M.Alternative, variantName: string | undefined): Item { - if (a.label === M.$and) { - return opseq('_val', ' & ', - ... variantName === void 0 ? [] : [braces(variantFor(variantName))], - ...a[0].map(p => typeFor(mod, M.unname(p)))); - } else { - return typeFor(mod, a, variantName); - } +export const ANY_TYPE: AtomicType = Type.ref('_val'); +export const EMPTY_TYPE: AtomicType = Type.ref('never'); + +export function variantInitFor(variantName: string | undefined) : Item[] { + return variantName === void 0 ? [] : [variantFor(variantName)]; } -export function typeFor(mod: ModuleContext, p: M.Pattern, variantName?: string): Item { - let typeItem: Item; - - if (M.isSimplePattern(p)) { - typeItem = typeForSimple(mod, p); - } else { - switch (p.label) { - case M.$setof: - typeItem = seq(`_.KeyedSet`, anglebrackets(typeForSimple(mod, p[0]), '_ptr')); - break; - case M.$dictof: - typeItem = seq(`_.KeyedDictionary`, anglebrackets(typeForSimple(mod, p[0]), - typeForSimple(mod, p[1]), - '_ptr')); - break; - default: { - const arrayType = M.simpleArray(p); - if (arrayType === void 0) { - return braces(... variantInitFor(variantName), - ... typeForCompound(mod, p)); - } else { - typeItem = seq('Array<', typeForSimple(mod, arrayType), '>'); - break; - } - } - } - } - - if (variantName === void 0) { - return typeItem; - } else { - return braces(variantFor(variantName), keyvalue('value', typeItem)); - } +export function variantFor(variantName: string): Item { + return keyvalue('_variant', JSON.stringify(variantName)); } -function typeForSimple(mod: ModuleContext, p: M.SimplePattern): Item { - switch (p.label) { - case M.$atom: - switch (p[0]) { - case M.$Boolean: return `boolean`; - case M.$Float: return `_.SingleFloat`; - case M.$Double: return `_.DoubleFloat`; - case M.$SignedInteger: return `number`; - case M.$String: return `string`; - case M.$ByteString: return `_.Bytes`; - case M.$Symbol: return `symbol`; - } - case M.$pointer: - return `_ptr`; - case M.$lit: - return `(typeof ${mod.literal(p[0])})`; - case M.$ref: - return M.lookup(refPosition(p), p, mod.env, - (_p) => p[1].description!, - (p) => typeForAlternative(mod, p, void 0), - (modId, modPath,_p) => { - mod.imports.add([modId, modPath]); - return `${modId}.${p[1].description!}`; - }); +export function renderVariant([variantName, t]: [string, SimpleType]): Item { + let fields: Item[]; + switch (t.kind) { + case 'unit': + fields = []; + break; + case 'ref': + case 'set': + case 'dictionary': + case 'array': + fields = [keyvalue('value', renderType(t))]; + break; + case 'record': + fields = Array.from(t.fields).map(([nn, tt]) => keyvalue(nn, renderType(tt))); + break; default: - ((_p: never) => {})(p); + ((_: never) => {})(t); + throw new Error("Unreachable"); + } + return braces(variantFor(variantName), ... fields); +} + +export function renderType(t: Type): Item { + switch (t.kind) { + case 'union': return opseq('never', ' | ', ... + Array.from(t.variants).flatMap(renderVariant)); + case 'unit': return 'null'; + case 'ref': return t.typeName; + case 'set': return seq('_.KeyedSet', anglebrackets( + renderType(t.type), + '_ptr')); + case 'dictionary': return seq('_.KeyedDictionary', anglebrackets( + renderType(t.key), + renderType(t.value), + '_ptr')); + case 'array': return seq('Array', anglebrackets(renderType(t.type))); + case 'record': return braces(... Array.from(t.fields).map(([nn, tt]) => + keyvalue(nn, renderType(tt)))); + default: + ((_: never) => {})(t); throw new Error("Unreachable"); } } - -function typeForCompound(mod: ModuleContext, p: M.CompoundPattern): Item[] { - switch (p.label) { - case M.$rec: - return [... typeField(mod, p[0]), ... typeField(mod, p[1])]; - case M.$tuple: - return p[0].flatMap(pp => typeField(mod, pp)); - case M.$tuple_STAR_: { - const n = p[1]; - return [... p[0].flatMap(pp => typeField(mod, pp)), - ... ((n.label === M.$named) - ? [keyvalue(n[0].description!, - seq('Array<', typeForSimple(mod, n[1]), '>'))] - : [])]; - } - case M.$setof: - case M.$dictof: - throw new Error('Internal error: setof and dictof are handled in typeFor()'); - case M.$dict: - return Array.from(p[0]).flatMap(([k, n]) => typeField(mod, M.addNameIfAbsent(n, k))); - default: - ((_p: never) => {})(p); - throw new Error("Unreachable"); - } -} - -function typeField(mod: ModuleContext, n: M.NamedPattern): Item[] { - return (n.label === M.$named) - ? [keyvalue(n[0].description!, typeForSimple(mod, n[1]))] - : (M.isCompoundPattern(n) - ? typeForCompound(mod, n) - : []); -} diff --git a/implementations/javascript/packages/schema/src/index.ts b/implementations/javascript/packages/schema/src/index.ts index 8bcfa46..1ceb266 100644 --- a/implementations/javascript/packages/schema/src/index.ts +++ b/implementations/javascript/packages/schema/src/index.ts @@ -1,3 +1,2 @@ export * from './reader'; export * from './compiler'; -export * from './base'; diff --git a/implementations/javascript/packages/schema/src/meta.ts b/implementations/javascript/packages/schema/src/meta.ts index 5067533..b0fe39f 100644 --- a/implementations/javascript/packages/schema/src/meta.ts +++ b/implementations/javascript/packages/schema/src/meta.ts @@ -1,10 +1,12 @@ -import { Value, is, Position, stringify } from '@preserves/core'; +import { Value, is, Position } from '@preserves/core'; import * as M from './gen/schema'; -import { BASE } from './base'; import { SchemaSyntaxError } from './error'; +import type { AtomicType } from './compiler/type'; export * from './gen/schema'; +export type Builtin = { type: AtomicType, pattern: M.Alternative }; + export type Input = Value; export function isValidToken(s: string, allowLeadingUnderscore = false): boolean { @@ -42,7 +44,6 @@ export function lookup(namePos: Position | null, name: M.Ref, env: Environment, kLocal: (p: M.Definition) => R, - kBase: (p: M.Alternative) => R, kOther: (modId: string, modPath: string, p: M.Definition | null) => R): R { for (const e of env) { @@ -65,11 +66,6 @@ export function lookup(namePos: Position | null, } } - if (M.Ref._.module(name).length === 0) { - const p = M.Schema._._field0(BASE).get(M.$definitions).get(M.Ref._.name(name)); - if (p !== void 0) return kBase(p as M.Alternative); - } - throw new SchemaSyntaxError(`Undefined reference: ${formatRef(name)}`, namePos); } @@ -86,7 +82,7 @@ export function unname( export function nameFor( p: M.NamedSimplePattern_ | R): string | undefined { - return (p.label === M.$named) ? stringify(p[0]) : void 0; + return (p.label === M.$named) ? p[0].description! : void 0; } export function addNameIfAbsent(p: M.NamedSimplePattern, k: Input): M.NamedSimplePattern { @@ -114,7 +110,8 @@ export function simpleArray(p: M.CompoundPattern): M.SimplePattern | undefined { export function namelike(x: Input): string | undefined { if (typeof x === 'string') return x; - if (typeof x === 'symbol') return stringify(x); + if (typeof x === 'symbol') return x.description!; if (typeof x === 'number') return '' + x; + if (typeof x === 'boolean') return '' + x; return void 0; } diff --git a/implementations/javascript/packages/schema/src/reader.ts b/implementations/javascript/packages/schema/src/reader.ts index 4984315..cc5ed36 100644 --- a/implementations/javascript/packages/schema/src/reader.ts +++ b/implementations/javascript/packages/schema/src/reader.ts @@ -122,6 +122,10 @@ export function parseSchema(toplevelTokens: Array, ])])); } +function namedMustBeSimple(p: Position | null): never { + throw new SchemaSyntaxError('Named patterns must be Simple patterns', p); +} + function parseDefinition(name: symbol, body: Array): Definition { let nextAnonymousAlternativeNumber = 0; function alternativeName([input, p]: readonly [Array, Alternative]) @@ -138,26 +142,33 @@ function parseDefinition(name: symbol, body: Array): Definition { return [p[1].description!, p]; } if (p.label === M.$lit) { - switch (typeof p[0]) { - case 'symbol': return [p[0].description!, p]; - case 'string': return [p[0], p]; - case 'boolean': - case 'number': - return ['' + p[0], p]; - default: - break; - } + const s = M.namelike(p[0]); + if (s !== void 0) return [s, p]; } return ['_anonymous' + nextAnonymousAlternativeNumber++, p]; } + function patternName([input, p]: readonly [Array, Pattern]) : M.NamedPattern { + const n = findName(input) || findName(input[0]); + if (n !== false) { + if (!M.isSimplePattern(p)) namedMustBeSimple(position(input[0])); + return Record(M.$named, [n, p]); + } + return p; + } + + // TODO: deal with situation where there's an or of ands, where + // the branches of the and arenamed. The parsing is ambiguous, and + // with the current code I think (?) you end up with the same name + // attached to the or-branch as to the leftmost and-branch. + return parseOp(body, M.ORSYM, p => [p, parseOp(p, M.ANDSYM, - p => parsePattern(name, p), - ps => Record(M.$and, [ps]), - p => p as Alternative)] as const, + p => [p, parsePattern(name, p)] as const, + ps => Record(M.$and, [ps.map(patternName)]), + p => p[1] as Alternative)] as const, ps => Record(M.$or, [ps.map(alternativeName)]), p => p[1] as Definition); } @@ -168,7 +179,18 @@ function parsePattern(name: symbol, body0: Array): Pattern { const item = peel(item0); function complain(): never { invalidPattern(stringify(name), item, pos); } if (typeof item === 'symbol') { - return parseRef(stringify(name), pos, item); + switch (item) { + case Symbol.for('any'): return Record(M.$any, []); + case Symbol.for('bool'): return Record(M.$atom, [M.$Boolean]); + case Symbol.for('float'): return Record(M.$atom, [M.$Float]); + case Symbol.for('double'): return Record(M.$atom, [M.$Double]); + case Symbol.for('int'): return Record(M.$atom, [M.$SignedInteger]); + case Symbol.for('string'): return Record(M.$atom, [M.$String]); + case Symbol.for('bytes'): return Record(M.$atom, [M.$ByteString]); + case Symbol.for('symbol'): return Record(M.$atom, [M.$Symbol]); + case Symbol.for('ref'): return Record(M.$pointer, []); + default: return parseRef(stringify(name), pos, item); + } } else if (Record.isRecord, never>(item)) { const label = item.label; if (Record.isRecord(label)) { @@ -214,9 +236,8 @@ function parsePattern(name: symbol, body0: Array): Pattern { if (name === false) { return recur(b); } - return Record(M.$named, [name, parseSimple(b, () => { - throw new SchemaSyntaxError(`Named patterns must be Simple patterns`, position(b)); - })]); + return Record(M.$named, [name, parseSimple(b, () => + namedMustBeSimple(position(b)))]); }; } const maybeNamed = _maybeNamed(walk);