Fix types.

This commit is contained in:
Tony Garnock-Jones 2021-03-19 23:42:43 +01:00
parent c2fe82e71d
commit 889d38bbb8
10 changed files with 279 additions and 183 deletions

View File

@ -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<never>([
[M.$version, 1],
[M.$pointer, false],
[M.$definitions, new Dictionary<never, M.Alternative>([
[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, [])],
])],
])]));

View File

@ -2,16 +2,19 @@ import { Annotated, Bytes, Dictionary, Fold, fold, Record, stringify, Tuple, Val
import * as M from "./meta"; import * as M from "./meta";
import { CompilerOptions, ModuleContext } from "./compiler/context"; import { CompilerOptions, ModuleContext } from "./compiler/context";
import { brackets, Formatter, Item, parens, seq } from "./compiler/block"; 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 { 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 { export function compile(env: M.Environment, schema: M.Schema, options: CompilerOptions = {}): string {
const mod = new ModuleContext(env, schema, options); const mod = new ModuleContext(env, schema, options);
const pointerName = M.Schema._._field0(schema).get(M.$pointer); const pointerName = M.Schema._._field0(schema).get(M.$pointer);
mod.defineType(seq(`export type _ptr = `, 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>;`); 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)) { for (const [name, def] of M.Schema._._field0(schema).get(M.$definitions)) {
mod.defineType( 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)) { for (const [name0, def] of M.Schema._._field0(schema).get(M.$definitions)) {

View File

@ -2,6 +2,7 @@ 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";
export interface CompilerOptions { export interface CompilerOptions {
preservesModule?: string; preservesModule?: string;
@ -45,7 +46,6 @@ export class ModuleContext {
derefPattern([_name, p]: [string, M.Alternative]): M.Definition { derefPattern([_name, p]: [string, M.Alternative]): M.Definition {
if (p.label === M.$ref) { if (p.label === M.$ref) {
return M.lookup(refPosition(p), p, this.env, return M.lookup(refPosition(p), p, this.env,
(p) => p,
(p) => p, (p) => p,
(_modId, _modPath, pp) => pp ?? p); (_modId, _modPath, pp) => pp ?? p);
} else { } else {
@ -67,6 +67,7 @@ export class FunctionContext {
tempCounter = 0; tempCounter = 0;
temps: Map<string, { type: Item, names: string[] }> = new Map(); temps: Map<string, { type: Item, names: string[] }> = new Map();
captures: Capture[] = []; captures: Capture[] = [];
variantName: string | undefined = void 0; variantName: string | undefined = void 0;
@ -78,13 +79,13 @@ export class FunctionContext {
return '_tmp' + this.tempCounter++; return '_tmp' + this.tempCounter++;
} }
gentemp(... vartypePieces: Item[]): string { gentemp(vartype: Type = ANY_TYPE): string {
const vartype = vartypePieces.length === 0 ? '_val | undefined' : seq(... vartypePieces); const typeitem = renderType(vartype);
const typestr = formatItems([vartype], Infinity); const typestr = formatItems([typeitem], Infinity);
const varname = this.gentempname(); const varname = this.gentempname();
let e = this.temps.get(typestr); let e = this.temps.get(typestr);
if (e === void 0) { if (e === void 0) {
e = { type: vartype, names: [] }; e = { type: typeitem, names: [] };
this.temps.set(typestr, e); this.temps.set(typestr, e);
} }
e.names.push(varname); e.names.push(varname);
@ -99,7 +100,7 @@ export class FunctionContext {
this.temps = oldTemps; this.temps = oldTemps;
return block( return block(
... Array.from(ts).map(([_typestr, { type, names }]) => ... Array.from(ts).map(([_typestr, { type, names }]) =>
seq(`let `, commas(... names), `: `, type)), seq(`let `, commas(... names), `: (`, type, `) | undefined`)),
... items); ... items);
} }
@ -126,11 +127,3 @@ export class FunctionContext {
keyvalue(fieldName, sourceExpr)))); 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));
}

View File

@ -1,8 +1,9 @@
import { FunctionContext } from "./context"; import { FunctionContext } from "./context";
import * as M from '../meta'; import * as M from '../meta';
import { block, Item, seq } from "./block"; import { block, Item, seq } from "./block";
import { typeFor } from "./type"; import { simpleType, dictionaryType, setType, typeFor } from "./gentype";
import { refPosition } from "../reader"; import { refPosition } from "../reader";
import { ANY_TYPE, Type } from "./type";
export function converterForDefinition( export function converterForDefinition(
ctx: FunctionContext, ctx: FunctionContext,
@ -45,9 +46,13 @@ function converterForAlternative(
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) {
return [ctx.withCapture('value', if (typeFor(ctx.mod, p).kind === 'unit') {
simpleValue, return [ctx.buildCapturedCompound(dest)];
() => ctx.buildCapturedCompound(dest))]; } else {
return [ctx.withCapture('value',
simpleValue,
() => ctx.buildCapturedCompound(dest))];
}
} else { } else {
return [`${dest} = ${simpleValue}`]; return [`${dest} = ${simpleValue}`];
} }
@ -69,7 +74,7 @@ function converterForTuple(ctx: FunctionContext,
if (variablePattern === void 0) { if (variablePattern === void 0) {
return k(); return k();
} else { } 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}`, return [ps.length > 0 ? `${vN} = ${src}.slice(${ps.length})` : `${vN} = ${src}`,
converterForArray(ctx, variablePattern, vN, false, k)]; converterForArray(ctx, variablePattern, vN, false, k)];
} }
@ -92,8 +97,7 @@ function converterForArray(ctx: FunctionContext,
k: (dest: string) => Item[]): Item k: (dest: string) => Item[]): Item
{ {
const postCheck = () => { const postCheck = () => {
const r = ctx.gentemp( const r = ctx.gentemp(Type.array(simpleType(ctx.mod, M.unname(arrayType))));
`Array<`, typeFor(ctx.mod, M.unname(arrayType)), `> | undefined`);
const v = ctx.gentempname(); const v = ctx.gentempname();
return [ return [
seq(`${r} = []`), seq(`${r} = []`),
@ -119,14 +123,14 @@ function converterFor(
let maybeName = M.nameFor(np); let maybeName = M.nameFor(np);
if (M.isSimplePattern(p)) { 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), return [... converterForSimple(ctx, p, src, dest),
ctx.convertCapture(maybeName, dest, ks)]; ctx.convertCapture(maybeName, dest, ks)];
} else { } else {
switch (p.label) { switch (p.label) {
case M.$setof: { case M.$setof: {
const setPattern = p[0]; 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(); const v = ctx.gentempname();
return [ return [
seq(`if (_.Set.isSet<_ptr>(${src})) `, ctx.block(() => [ seq(`if (_.Set.isSet<_ptr>(${src})) `, ctx.block(() => [
@ -141,7 +145,7 @@ function converterFor(
case M.$dictof: { case M.$dictof: {
const keyPattern = p[0]; const keyPattern = p[0];
const valPattern = p[1]; 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 v = ctx.gentempname();
const k = ctx.gentempname(); const k = ctx.gentempname();
return [ return [
@ -174,6 +178,8 @@ function converterForSimple(
dest: string): Item[] dest: string): Item[]
{ {
switch (p.label) { switch (p.label) {
case M.$any:
return [`${dest} = ${src}`];
case M.$atom: { case M.$atom: {
let test: Item; let test: Item;
switch (p[0]) { switch (p[0]) {
@ -188,11 +194,10 @@ function converterForSimple(
return [seq(`${dest} = `, test, ` ? ${src} : void 0`)]; return [seq(`${dest} = `, test, ` ? ${src} : void 0`)];
} }
case M.$lit: 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: case M.$ref:
return M.lookup(refPosition(p), p, ctx.mod.env, return M.lookup(refPosition(p), p, ctx.mod.env,
(_p) => [`${dest} = to${p[1].description!}(${src})`], (_p) => [`${dest} = to${p[1].description!}(${src})`],
(p) => converterForAlternative(ctx, p, src, dest),
(modId, modPath,_p) => { (modId, modPath,_p) => {
ctx.mod.imports.add([modId, modPath]); ctx.mod.imports.add([modId, modPath]);
return [`${dest} = ${modId}.decode${p[1].description!}(${src})`]; return [`${dest} = ${modId}.decode${p[1].description!}(${src})`];

View File

@ -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);
}
}

View File

@ -1,120 +1,90 @@
import { refPosition } from "../reader";
import * as M from "../meta";
import { anglebrackets, braces, Item, keyvalue, opseq, seq } from "./block"; import { anglebrackets, braces, Item, keyvalue, opseq, seq } from "./block";
import { ModuleContext, variantFor, variantInitFor } from "./context";
export function typeForDefinition(mod: ModuleContext, d: M.Definition): Item { export type Type =
if (d.label === M.$or) { | { kind: 'union', variants: VariantMap } // zero: never
return opseq('never', ' | ', ... d[0].map(a => typeForAlternative(mod, a[1], a[0]))); | SimpleType
} else {
return typeForAlternative(mod, d, void 0); 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 });
} }
function typeForAlternative(mod: ModuleContext, a: M.Alternative, variantName: string | undefined): Item { export const ANY_TYPE: AtomicType = Type.ref('_val');
if (a.label === M.$and) { export const EMPTY_TYPE: AtomicType = Type.ref('never');
return opseq('_val', ' & ',
... variantName === void 0 ? [] : [braces(variantFor(variantName))], export function variantInitFor(variantName: string | undefined) : Item[] {
...a[0].map(p => typeFor(mod, M.unname(p)))); return variantName === void 0 ? [] : [variantFor(variantName)];
} else {
return typeFor(mod, a, variantName);
}
} }
export function typeFor(mod: ModuleContext, p: M.Pattern, variantName?: string): Item { export function variantFor(variantName: string): Item {
let typeItem: Item; return keyvalue('_variant', JSON.stringify(variantName));
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));
}
} }
function typeForSimple(mod: ModuleContext, p: M.SimplePattern): Item { export function renderVariant([variantName, t]: [string, SimpleType]): Item {
switch (p.label) { let fields: Item[];
case M.$atom: switch (t.kind) {
switch (p[0]) { case 'unit':
case M.$Boolean: return `boolean`; fields = [];
case M.$Float: return `_.SingleFloat`; break;
case M.$Double: return `_.DoubleFloat`; case 'ref':
case M.$SignedInteger: return `number`; case 'set':
case M.$String: return `string`; case 'dictionary':
case M.$ByteString: return `_.Bytes`; case 'array':
case M.$Symbol: return `symbol`; fields = [keyvalue('value', renderType(t))];
} break;
case M.$pointer: case 'record':
return `_ptr`; fields = Array.from(t.fields).map(([nn, tt]) => keyvalue(nn, renderType(tt)));
case M.$lit: break;
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!}`;
});
default: 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"); 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)
: []);
}

View File

@ -1,3 +1,2 @@
export * from './reader'; export * from './reader';
export * from './compiler'; export * from './compiler';
export * from './base';

View File

@ -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 * as M from './gen/schema';
import { BASE } from './base';
import { SchemaSyntaxError } from './error'; import { SchemaSyntaxError } from './error';
import type { AtomicType } from './compiler/type';
export * from './gen/schema'; export * from './gen/schema';
export type Builtin = { type: AtomicType, pattern: M.Alternative };
export type Input = Value<never>; export type Input = Value<never>;
export function isValidToken(s: string, allowLeadingUnderscore = false): boolean { export function isValidToken(s: string, allowLeadingUnderscore = false): boolean {
@ -42,7 +44,6 @@ export function lookup<R>(namePos: Position | null,
name: M.Ref, name: M.Ref,
env: Environment, env: Environment,
kLocal: (p: M.Definition) => R, kLocal: (p: M.Definition) => R,
kBase: (p: M.Alternative) => R,
kOther: (modId: string, modPath: string, p: M.Definition | null) => R): R kOther: (modId: string, modPath: string, p: M.Definition | null) => R): R
{ {
for (const e of env) { for (const e of env) {
@ -65,11 +66,6 @@ export function lookup<R>(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); throw new SchemaSyntaxError(`Undefined reference: ${formatRef(name)}`, namePos);
} }
@ -86,7 +82,7 @@ export function unname<R extends M.Pattern | M.SimplePattern>(
export function nameFor<R extends M.Pattern | M.SimplePattern>( export function nameFor<R extends M.Pattern | M.SimplePattern>(
p: M.NamedSimplePattern_ | R): string | undefined 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 { 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 { export function namelike(x: Input): string | undefined {
if (typeof x === 'string') return x; 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 === 'number') return '' + x;
if (typeof x === 'boolean') return '' + x;
return void 0; return void 0;
} }

View File

@ -122,6 +122,10 @@ export function parseSchema(toplevelTokens: Array<Input>,
])])); ])]));
} }
function namedMustBeSimple(p: Position | null): never {
throw new SchemaSyntaxError('Named patterns must be Simple patterns', p);
}
function parseDefinition(name: symbol, body: Array<Input>): Definition { function parseDefinition(name: symbol, body: Array<Input>): Definition {
let nextAnonymousAlternativeNumber = 0; let nextAnonymousAlternativeNumber = 0;
function alternativeName([input, p]: readonly [Array<Input>, Alternative]) function alternativeName([input, p]: readonly [Array<Input>, Alternative])
@ -138,26 +142,33 @@ function parseDefinition(name: symbol, body: Array<Input>): Definition {
return [p[1].description!, p]; return [p[1].description!, p];
} }
if (p.label === M.$lit) { if (p.label === M.$lit) {
switch (typeof p[0]) { const s = M.namelike(p[0]);
case 'symbol': return [p[0].description!, p]; if (s !== void 0) return [s, p];
case 'string': return [p[0], p];
case 'boolean':
case 'number':
return ['' + p[0], p];
default:
break;
}
} }
return ['_anonymous' + nextAnonymousAlternativeNumber++, p]; return ['_anonymous' + nextAnonymousAlternativeNumber++, p];
} }
function patternName([input, p]: readonly [Array<Input>, 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, return parseOp(body,
M.ORSYM, M.ORSYM,
p => [p, parseOp(p, p => [p, parseOp(p,
M.ANDSYM, M.ANDSYM,
p => parsePattern(name, p), p => [p, parsePattern(name, p)] as const,
ps => Record(M.$and, [ps]), ps => Record(M.$and, [ps.map(patternName)]),
p => p as Alternative)] as const, p => p[1] as Alternative)] as const,
ps => Record(M.$or, [ps.map(alternativeName)]), ps => Record(M.$or, [ps.map(alternativeName)]),
p => p[1] as Definition); p => p[1] as Definition);
} }
@ -168,7 +179,18 @@ function parsePattern(name: symbol, body0: Array<Input>): Pattern {
const item = peel(item0); const item = peel(item0);
function complain(): never { invalidPattern(stringify(name), item, pos); } function complain(): never { invalidPattern(stringify(name), item, pos); }
if (typeof item === 'symbol') { if (typeof item === 'symbol') {
return parseRef(stringify(name), pos, item); switch (item) {
case Symbol.for('any'): return Record<typeof M.$any, []>(M.$any, []);
case Symbol.for('bool'): return Record<typeof M.$atom, [M.AtomKind]>(M.$atom, [M.$Boolean]);
case Symbol.for('float'): return Record<typeof M.$atom, [M.AtomKind]>(M.$atom, [M.$Float]);
case Symbol.for('double'): return Record<typeof M.$atom, [M.AtomKind]>(M.$atom, [M.$Double]);
case Symbol.for('int'): return Record<typeof M.$atom, [M.AtomKind]>(M.$atom, [M.$SignedInteger]);
case Symbol.for('string'): return Record<typeof M.$atom, [M.AtomKind]>(M.$atom, [M.$String]);
case Symbol.for('bytes'): return Record<typeof M.$atom, [M.AtomKind]>(M.$atom, [M.$ByteString]);
case Symbol.for('symbol'): return Record<typeof M.$atom, [M.AtomKind]>(M.$atom, [M.$Symbol]);
case Symbol.for('ref'): return Record<typeof M.$pointer, []>(M.$pointer, []);
default: return parseRef(stringify(name), pos, item);
}
} else if (Record.isRecord<Input, Tuple<Input>, never>(item)) { } else if (Record.isRecord<Input, Tuple<Input>, never>(item)) {
const label = item.label; const label = item.label;
if (Record.isRecord<Input, [], never>(label)) { if (Record.isRecord<Input, [], never>(label)) {
@ -214,9 +236,8 @@ function parsePattern(name: symbol, body0: Array<Input>): Pattern {
if (name === false) { if (name === false) {
return recur(b); return recur(b);
} }
return Record(M.$named, [name, parseSimple(b, () => { return Record(M.$named, [name, parseSimple(b, () =>
throw new SchemaSyntaxError(`Named patterns must be Simple patterns`, position(b)); namedMustBeSimple(position(b)))]);
})]);
}; };
} }
const maybeNamed = _maybeNamed(walk); const maybeNamed = _maybeNamed(walk);