First steps toward schema
This commit is contained in:
parent
c27aa7579e
commit
62bab41bed
|
@ -10,30 +10,23 @@ import { fromJS } from "./fromjs";
|
||||||
export type DictionaryType = 'Dictionary' | 'Set';
|
export type DictionaryType = 'Dictionary' | 'Set';
|
||||||
export const DictionaryType = Symbol.for('DictionaryType');
|
export const DictionaryType = Symbol.for('DictionaryType');
|
||||||
|
|
||||||
export class Dictionary<V, T extends object = DefaultPointer> extends FlexMap<Value<T>, V> {
|
export class KeyedDictionary<K extends Value<T>, V, T extends object = DefaultPointer> extends FlexMap<K, V> {
|
||||||
get [DictionaryType](): DictionaryType {
|
get [DictionaryType](): DictionaryType {
|
||||||
return 'Dictionary';
|
return 'Dictionary';
|
||||||
}
|
}
|
||||||
|
|
||||||
static isDictionary<V, T extends object = DefaultPointer>(x: any): x is Dictionary<V, T> {
|
static isKeyedDictionary<K extends Value<T>, V, T extends object = DefaultPointer>(x: any): x is KeyedDictionary<K, V, T> {
|
||||||
return x?.[DictionaryType] === 'Dictionary';
|
return x?.[DictionaryType] === 'Dictionary';
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJS<V extends object = DefaultPointer, T extends object = DefaultPointer>(x: object): Dictionary<Value<V>, T> {
|
constructor(items?: readonly [K, V][]);
|
||||||
if (Dictionary.isDictionary<V, T>(x)) return x as Dictionary<Value<V>, T>;
|
constructor(items?: Iterable<readonly [K, V]>);
|
||||||
const d = new Dictionary<Value<V>, T>();
|
constructor(items?: Iterable<readonly [K, V]>) {
|
||||||
Object.entries(x).forEach(([key, value]) => d.set(key, fromJS(value)));
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(items?: readonly [Value<T>, V][]);
|
|
||||||
constructor(items?: Iterable<readonly [Value<T>, V]>);
|
|
||||||
constructor(items?: Iterable<readonly [Value<T>, V]>) {
|
|
||||||
super(canonicalString, items);
|
super(canonicalString, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
mapEntries<W, R extends object = DefaultPointer>(f: (entry: [Value<T>, V]) => [Value<R>, W]): Dictionary<W, R> {
|
mapEntries<W, S extends Value<R>, R extends object = DefaultPointer>(f: (entry: [K, V]) => [S, W]): KeyedDictionary<S, W, R> {
|
||||||
const result = new Dictionary<W, R>();
|
const result = new KeyedDictionary<S, W, R>();
|
||||||
for (let oldEntry of this.entries()) {
|
for (let oldEntry of this.entries()) {
|
||||||
const newEntry = f(oldEntry);
|
const newEntry = f(oldEntry);
|
||||||
result.set(newEntry[0], newEntry[1])
|
result.set(newEntry[0], newEntry[1])
|
||||||
|
@ -48,8 +41,8 @@ export class Dictionary<V, T extends object = DefaultPointer> extends FlexMap<Va
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
clone(): Dictionary<V, T> {
|
clone(): KeyedDictionary<K, V, T> {
|
||||||
return new Dictionary(this);
|
return new KeyedDictionary(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
toString(): string {
|
toString(): string {
|
||||||
|
@ -81,25 +74,38 @@ export class Dictionary<V, T extends object = DefaultPointer> extends FlexMap<Va
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Set<T extends object = DefaultPointer> extends FlexSet<Value<T>> {
|
export class Dictionary<V, T extends object = DefaultPointer> extends KeyedDictionary<Value<T>, V, T> {
|
||||||
|
static isDictionary<V, T extends object = DefaultPointer>(x: any): x is Dictionary<V, T> {
|
||||||
|
return x?.[DictionaryType] === 'Dictionary';
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJS<V extends object = DefaultPointer, T extends object = DefaultPointer>(x: object): Dictionary<Value<V>, T> {
|
||||||
|
if (Dictionary.isDictionary<Value<V>, T>(x)) return x as Dictionary<Value<V>, T>;
|
||||||
|
const d = new Dictionary<Value<V>, T>();
|
||||||
|
Object.entries(x).forEach(([key, value]) => d.set(key, fromJS(value)));
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KeyedSet<K extends Value<T>, T extends object = DefaultPointer> extends FlexSet<K> {
|
||||||
get [DictionaryType](): DictionaryType {
|
get [DictionaryType](): DictionaryType {
|
||||||
return 'Set';
|
return 'Set';
|
||||||
}
|
}
|
||||||
|
|
||||||
static isSet<T extends object = DefaultPointer>(x: any): x is Set<T> {
|
static isKeyedSet<K extends Value<T>, T extends object = DefaultPointer>(x: any): x is KeyedSet<K, T> {
|
||||||
return x?.[DictionaryType] === 'Set';
|
return x?.[DictionaryType] === 'Set';
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(items?: Iterable<Value<T>>) {
|
constructor(items?: Iterable<K>) {
|
||||||
super(canonicalString, items);
|
super(canonicalString, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
map<R extends object = DefaultPointer>(f: (value: Value<T>) => Value<R>): Set<R> {
|
map<S extends Value<R>, R extends object = DefaultPointer>(f: (value: K) => S): KeyedSet<S, R> {
|
||||||
return new Set(_iterMap(this[Symbol.iterator](), f));
|
return new KeyedSet(_iterMap(this[Symbol.iterator](), f));
|
||||||
}
|
}
|
||||||
|
|
||||||
filter(f: (value: Value<T>) => boolean): Set<T> {
|
filter(f: (value: K) => boolean): KeyedSet<K, T> {
|
||||||
const result = new Set<T>();
|
const result = new KeyedSet<K, T>();
|
||||||
for (let k of this) if (f(k)) result.add(k);
|
for (let k of this) if (f(k)) result.add(k);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -122,7 +128,7 @@ export class Set<T extends object = DefaultPointer> extends FlexSet<Value<T>> {
|
||||||
|
|
||||||
[PreserveOn](encoder: Encoder<T>) {
|
[PreserveOn](encoder: Encoder<T>) {
|
||||||
if (encoder.canonical) {
|
if (encoder.canonical) {
|
||||||
const pieces = Array.from(this).map<[Bytes, Value<T>]>(k => [canonicalEncode(k), k]);
|
const pieces = Array.from(this).map<[Bytes, K]>(k => [canonicalEncode(k), k]);
|
||||||
pieces.sort((a, b) => Bytes.compare(a[0], b[0]));
|
pieces.sort((a, b) => Bytes.compare(a[0], b[0]));
|
||||||
encoder.encodevalues(Tag.Set, pieces.map(e => e[1]));
|
encoder.encodevalues(Tag.Set, pieces.map(e => e[1]));
|
||||||
} else {
|
} else {
|
||||||
|
@ -130,3 +136,9 @@ export class Set<T extends object = DefaultPointer> extends FlexSet<Value<T>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Set<T extends object = DefaultPointer> extends KeyedSet<Value<T>, T> {
|
||||||
|
static isSet<T extends object = DefaultPointer>(x: any): x is Set<T> {
|
||||||
|
return x?.[DictionaryType] === 'Set';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -137,3 +137,28 @@ export function mapPointers<T extends object, R extends object>(
|
||||||
{
|
{
|
||||||
return fold(v, new MapFold(f));
|
return fold(v, new MapFold(f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isPointer<T extends object>(v: Value<T>): v is T {
|
||||||
|
return fold(v, {
|
||||||
|
boolean(_b: boolean): boolean { return false; },
|
||||||
|
single(_f: number): boolean { return false; },
|
||||||
|
double(_f: number): boolean { return false; },
|
||||||
|
integer(_i: number): boolean { return false; },
|
||||||
|
string(_s: string): boolean { return false; },
|
||||||
|
bytes(_b: Bytes): boolean { return false; },
|
||||||
|
symbol(_s: symbol): boolean { return false; },
|
||||||
|
|
||||||
|
record(_r: Record<Value<T>, Tuple<Value<T>>, T>, _k: Fold<T, boolean>): boolean {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
array(_a: Array<Value<T>>, _k: Fold<T, boolean>): boolean { return false; },
|
||||||
|
set(_s: Set<T>, _k: Fold<T, boolean>): boolean { return false; },
|
||||||
|
dictionary(_d: Dictionary<Value<T>, T>, _k: Fold<T, boolean>): boolean {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
annotated(_a: Annotated<T>, _k: Fold<T, boolean>): boolean { return false; },
|
||||||
|
|
||||||
|
pointer(_t: T, _k: Fold<T, boolean>): boolean { return true; },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -16,3 +16,7 @@ export * from './symbols';
|
||||||
export * from './text';
|
export * from './text';
|
||||||
export * from './values';
|
export * from './values';
|
||||||
export * as Constants from './constants';
|
export * as Constants from './constants';
|
||||||
|
|
||||||
|
const _Array = Array;
|
||||||
|
type _Array<T> = Array<T>;
|
||||||
|
export { _Array as Array };
|
||||||
|
|
|
@ -71,6 +71,7 @@ export class Reader<T extends object> {
|
||||||
|
|
||||||
skipws() {
|
skipws() {
|
||||||
while (true) {
|
while (true) {
|
||||||
|
if (this.atEnd()) break;
|
||||||
if (!isSpace(this.peek())) break;
|
if (!isSpace(this.peek())) break;
|
||||||
this.index++;
|
this.index++;
|
||||||
}
|
}
|
||||||
|
@ -101,6 +102,15 @@ export class Reader<T extends object> {
|
||||||
return u;
|
return u;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readToEnd(): Array<Value<T>> {
|
||||||
|
const acc = [];
|
||||||
|
while (true) {
|
||||||
|
this.skipws();
|
||||||
|
if (this.atEnd()) return acc;
|
||||||
|
acc.push(this.next());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
next(): Value<T> {
|
next(): Value<T> {
|
||||||
return this.wrap(this._next());
|
return this.wrap(this._next());
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Record, KeyedDictionary } from '..';
|
||||||
|
import { AtomKind, Pattern, Schema } from './meta';
|
||||||
|
import * as M from './meta';
|
||||||
|
|
||||||
|
export const BASE: Schema = Record(M.SCHEMA, [1, new KeyedDictionary<symbol, Pattern, never>([
|
||||||
|
[Symbol.for('any'), Record(M.AND, [[]])],
|
||||||
|
[Symbol.for('bool'), Record(M.ATOM, [M.BOOLEAN as AtomKind])],
|
||||||
|
[Symbol.for('float'), Record(M.ATOM, [M.FLOAT as AtomKind])],
|
||||||
|
[Symbol.for('double'), Record(M.ATOM, [M.DOUBLE as AtomKind])],
|
||||||
|
[Symbol.for('int'), Record(M.ATOM, [M.SIGNEDINTEGER as AtomKind])],
|
||||||
|
[Symbol.for('string'), Record(M.ATOM, [M.STRING as AtomKind])],
|
||||||
|
[Symbol.for('bytes'), Record(M.ATOM, [M.BYTESTRING as AtomKind])],
|
||||||
|
[Symbol.for('symbol'), Record(M.ATOM, [M.SYMBOL as AtomKind])],
|
||||||
|
[Symbol.for('ref'), Record(M.POINTER, [])],
|
||||||
|
])]);
|
|
@ -0,0 +1,160 @@
|
||||||
|
export type Item = Emittable | string;
|
||||||
|
|
||||||
|
export class Formatter {
|
||||||
|
width = 80;
|
||||||
|
indentDelta = ' ';
|
||||||
|
currentIndent = '\n';
|
||||||
|
buffer: Array<string> = [];
|
||||||
|
|
||||||
|
get indentSize(): number { return this.indentDelta.length; }
|
||||||
|
set indentSize(n: number) { this.indentDelta = new Array(n + 1).join(' '); }
|
||||||
|
|
||||||
|
write(i: Item) {
|
||||||
|
if (typeof i === 'string') {
|
||||||
|
this.buffer.push(i);
|
||||||
|
} else {
|
||||||
|
i.writeOn(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newline() {
|
||||||
|
this.write(this.currentIndent);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return this.buffer.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
withIndent(f: () => void): void {
|
||||||
|
const oldIndent = this.currentIndent;
|
||||||
|
try {
|
||||||
|
this.currentIndent = this.currentIndent + this.indentDelta;
|
||||||
|
f();
|
||||||
|
} finally {
|
||||||
|
this.currentIndent = oldIndent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clone(): Formatter {
|
||||||
|
const f = Object.assign(new Formatter(), this);
|
||||||
|
f.buffer = [];
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class Emittable {
|
||||||
|
abstract writeOn(f: Formatter): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Sequence extends Emittable {
|
||||||
|
items: Array<Item>;
|
||||||
|
|
||||||
|
constructor(items: Array<Item>) {
|
||||||
|
super();
|
||||||
|
this.items = items;
|
||||||
|
}
|
||||||
|
|
||||||
|
get separator(): string { return ''; }
|
||||||
|
get terminator(): string { return ''; }
|
||||||
|
|
||||||
|
writeOn(f: Formatter): void {
|
||||||
|
let needSeparator = false;
|
||||||
|
this.items.forEach(i => {
|
||||||
|
if (needSeparator) {
|
||||||
|
f.write(this.separator);
|
||||||
|
} else {
|
||||||
|
needSeparator = true;
|
||||||
|
}
|
||||||
|
f.write(i);
|
||||||
|
});
|
||||||
|
f.write(this.terminator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CommaSequence extends Sequence {
|
||||||
|
get separator(): string { return ', '; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class Grouping extends CommaSequence {
|
||||||
|
abstract get open(): string;
|
||||||
|
abstract get close(): string;
|
||||||
|
|
||||||
|
writeHorizontally(f: Formatter): void {
|
||||||
|
f.write(this.open);
|
||||||
|
super.writeOn(f);
|
||||||
|
f.write(this.close);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeVertically(f: Formatter): void {
|
||||||
|
f.write(this.open);
|
||||||
|
if (this.items.length > 0) {
|
||||||
|
f.withIndent(() => {
|
||||||
|
this.items.forEach((i, index) => {
|
||||||
|
f.newline();
|
||||||
|
f.write(i);
|
||||||
|
const delim = index === this.items.length - 1 ? this.terminator : this.separator;
|
||||||
|
f.write(delim.trimRight());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
f.newline();
|
||||||
|
}
|
||||||
|
f.write(this.close);
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOn(f: Formatter): void {
|
||||||
|
const g = f.clone();
|
||||||
|
this.writeHorizontally(g);
|
||||||
|
const s = g.toString();
|
||||||
|
if (s.length <= f.width) {
|
||||||
|
f.write(s);
|
||||||
|
} else {
|
||||||
|
this.writeVertically(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Parens extends Grouping {
|
||||||
|
get open(): string { return '('; }
|
||||||
|
get close(): string { return ')'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OperatorSequence extends Parens {
|
||||||
|
operator: string;
|
||||||
|
|
||||||
|
constructor(operator: string, items: Array<Item>) {
|
||||||
|
super(items);
|
||||||
|
this.operator = operator;
|
||||||
|
}
|
||||||
|
|
||||||
|
get separator(): string { return this.operator; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Brackets extends Grouping {
|
||||||
|
get open(): string { return '['; }
|
||||||
|
get close(): string { return ']'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AngleBrackets extends Grouping {
|
||||||
|
get open(): string { return '<'; }
|
||||||
|
get close(): string { return '>'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Braces extends Grouping {
|
||||||
|
get open(): string { return '{'; }
|
||||||
|
get close(): string { return '}'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Block extends Braces {
|
||||||
|
get separator(): string { return '; ' }
|
||||||
|
get terminator(): string { return ';' }
|
||||||
|
}
|
||||||
|
|
||||||
|
export const seq = (... items: Item[]) => new Sequence(items);
|
||||||
|
export const commas = (... items: Item[]) => new CommaSequence(items);
|
||||||
|
export const parens = (... items: Item[]) => new Parens(items);
|
||||||
|
export const opseq = (zero: string, op: string, ... items: Item[]) =>
|
||||||
|
(items.length === 0) ? zero : new OperatorSequence(op, items);
|
||||||
|
export const brackets = (... items: Item[]) => new Brackets(items);
|
||||||
|
export const anglebrackets = (... items: Item[]) => new AngleBrackets(items);
|
||||||
|
export const braces = (... items: Item[]) => new Braces(items);
|
||||||
|
export const block = (... items: Item[]) => new Block(items);
|
|
@ -0,0 +1,286 @@
|
||||||
|
import { Pattern, NamedPattern, Schema, Input } from "./meta";
|
||||||
|
import * as M from './meta';
|
||||||
|
import { Annotated, Bytes, Dictionary, Fold, fold, preserves, Record, Tuple, Value } from "..";
|
||||||
|
import { Formatter, parens, seq, Item, opseq, block, commas, brackets, anglebrackets } from "./block";
|
||||||
|
|
||||||
|
function fnblock(... items: Item[]): Item {
|
||||||
|
return seq('((() => ', block(... items), ')())');
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompileEnvEntry = {
|
||||||
|
moduleName: string,
|
||||||
|
modulePath: string,
|
||||||
|
schema: Schema,
|
||||||
|
inline: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function compile(env: Array<CompileEnvEntry>, schema: Schema, preservesModule = 'preserves'): string {
|
||||||
|
const literals = new Dictionary<string, never>();
|
||||||
|
const types: Array<Item> = [];
|
||||||
|
const predicates: Array<Item> = [];
|
||||||
|
let temps: Array<string> = [];
|
||||||
|
|
||||||
|
function environmentWith<R>(name: symbol,
|
||||||
|
kLocal: () => R,
|
||||||
|
kOther: (e: CompileEnvEntry, p: Pattern) => R): R {
|
||||||
|
if (Schema._.definitions(schema).has(name)) {
|
||||||
|
return kLocal();
|
||||||
|
}
|
||||||
|
for (const e of env) {
|
||||||
|
const p = Schema._.definitions(e.schema).get(name);
|
||||||
|
if (p !== void 0) {
|
||||||
|
return kOther(e, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(`Undefined reference: ${name.description!}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPredicate(name: symbol, v: string): Item {
|
||||||
|
return environmentWith(name,
|
||||||
|
() => `is${name.description!}(${v})`,
|
||||||
|
(e, p) => {
|
||||||
|
if (e.inline) {
|
||||||
|
return walk(v, p);
|
||||||
|
} else {
|
||||||
|
return `${e.moduleName}.is${name.description!}(${v})`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function gentemp(): string {
|
||||||
|
const varname = '_tmp' + temps.length;
|
||||||
|
temps.push(varname);
|
||||||
|
return varname;
|
||||||
|
}
|
||||||
|
|
||||||
|
function literal(v: Input): Item {
|
||||||
|
let varname = literals.get(v);
|
||||||
|
if (varname === void 0) {
|
||||||
|
varname = '__lit' + literals.size;
|
||||||
|
literals.set(v, varname);
|
||||||
|
}
|
||||||
|
return varname;
|
||||||
|
}
|
||||||
|
|
||||||
|
function typeFor(p: Pattern): 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.LIT:
|
||||||
|
return `(typeof ${literal(p[0])})`;
|
||||||
|
case M.REF:
|
||||||
|
return environmentWith(
|
||||||
|
p[0],
|
||||||
|
() => p[0].description!,
|
||||||
|
(e, pp) => {
|
||||||
|
if (e.inline) {
|
||||||
|
return typeFor(pp);
|
||||||
|
} else {
|
||||||
|
return `${e.moduleName}.${p[0].description!}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
case M.OR:
|
||||||
|
return opseq('never', ' | ', ... p[0].map(pp => typeFor(pp)));
|
||||||
|
case M.AND:
|
||||||
|
return opseq('_.Value', ' & ', ... p[0].map(pp => typeFor(pp)));
|
||||||
|
case M.POINTER:
|
||||||
|
return `any`; // TODO: what to do here?
|
||||||
|
case M.REC:
|
||||||
|
return seq('_.Record', anglebrackets(typeFor(p[0]), typeFor(p[1])));
|
||||||
|
case M.TUPLE:
|
||||||
|
return brackets(... p[0].map(pp => typeFor(unname(pp))));
|
||||||
|
case M.TUPLESTAR:
|
||||||
|
if (p[0].length === 0) {
|
||||||
|
return seq('Array<', typeFor(unname(p[1])), '>');
|
||||||
|
} else {
|
||||||
|
return brackets(... p[0].map(pp => typeFor(unname(pp))),
|
||||||
|
seq('... Array<', typeFor(unname(p[1])), '>'));
|
||||||
|
}
|
||||||
|
case M.SETOF:
|
||||||
|
return seq('_.KeyedSet<', typeFor(p[0]), '>');
|
||||||
|
case M.DICTOF:
|
||||||
|
return seq('_.KeyedDictionary', anglebrackets(typeFor(p[0]), typeFor(p[1])));
|
||||||
|
case M.DICT:
|
||||||
|
return parens(seq('_.Dictionary<_.Value> & ', block(
|
||||||
|
... Array.from(p[0]).map(([k, vp]) =>
|
||||||
|
seq(`get(k: typeof ${literal(k)}): `, typeFor(vp))))));
|
||||||
|
default:
|
||||||
|
((_p: never) => {})(p);
|
||||||
|
throw new Error("Unreachable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function walk(v: string, p: Pattern, recordOkAsTuple = false): Item {
|
||||||
|
switch (p.label) {
|
||||||
|
case M.ATOM:
|
||||||
|
switch (p[0]) {
|
||||||
|
case M.BOOLEAN: return `typeof ${v} === 'boolean'`;
|
||||||
|
case M.FLOAT: return `_.Float.isSingle(${v})`;
|
||||||
|
case M.DOUBLE: return `_.Float.isDouble(${v})`;
|
||||||
|
case M.SIGNEDINTEGER: return `typeof ${v} === 'number'`;
|
||||||
|
case M.STRING: return `typeof ${v} === 'string'`;
|
||||||
|
case M.BYTESTRING: return `_.Bytes.isBytes(${v})`;
|
||||||
|
case M.SYMBOL: return `typeof ${v} === 'symbol'`;
|
||||||
|
}
|
||||||
|
case M.LIT:
|
||||||
|
return `_.is(${v}, ${literal(p[0])})`;
|
||||||
|
case M.REF:
|
||||||
|
return applyPredicate(p[0], v);
|
||||||
|
case M.OR:
|
||||||
|
return opseq('false', ' || ', ... p[0].map(pp => walk(v, pp)));
|
||||||
|
case M.AND:
|
||||||
|
return opseq('true', ' && ', ... p[0].map(pp => walk(v, pp)));
|
||||||
|
case M.POINTER:
|
||||||
|
return `_.isPointer(${v})`;
|
||||||
|
case M.REC:
|
||||||
|
return opseq('true', ' && ',
|
||||||
|
`_.Record.isRecord(${v})`,
|
||||||
|
walk(`${v}.label`, p[0]),
|
||||||
|
walk(v, p[1], true));
|
||||||
|
case M.TUPLE:
|
||||||
|
return opseq('true', ' && ',
|
||||||
|
... (recordOkAsTuple ? []
|
||||||
|
: [`_.Array.isArray(${v})`, `!_.Record.isRecord(${v})`]),
|
||||||
|
`(${v}.length === ${p[0].length})`,
|
||||||
|
... p[0].map((pp, i) => walk(`${v}[${i}]`, unname(pp))));
|
||||||
|
case M.TUPLESTAR:
|
||||||
|
return opseq('true', ' && ',
|
||||||
|
... (recordOkAsTuple ? []
|
||||||
|
: [`_.Array.isArray(${v})`, `!_.Record.isRecord(${v})`]),
|
||||||
|
`(${v}.length >= ${p[0].length})`,
|
||||||
|
seq(`${v}.slice(${p[0].length})`,
|
||||||
|
`.every(v => `,
|
||||||
|
parens(walk('v', unname(p[1]))),
|
||||||
|
`)`),
|
||||||
|
... p[0].map((pp, i) => walk(`${v}[${i}]`, unname(pp))));
|
||||||
|
case M.SETOF:
|
||||||
|
return opseq('true', ' && ',
|
||||||
|
`_.Set.isSet(${v})`,
|
||||||
|
fnblock(
|
||||||
|
seq(`for (const vv of ${v}) `, block(
|
||||||
|
seq('if (!(', walk('vv', p[0]), ')) return false'))),
|
||||||
|
seq('return true')));
|
||||||
|
case M.DICTOF:
|
||||||
|
return opseq('true', ' && ',
|
||||||
|
`_.Dictionary.isDictionary(${v})`,
|
||||||
|
fnblock(
|
||||||
|
seq(`for (const e of ${v}) `, block(
|
||||||
|
seq('if (!(', walk('e[0]', p[0]), ')) return false'),
|
||||||
|
seq('if (!(', walk('e[1]', p[1]), ')) return false'))),
|
||||||
|
seq('return true')));
|
||||||
|
case M.DICT:
|
||||||
|
return opseq('true', ' && ',
|
||||||
|
`_.Dictionary.isDictionary(${v})`,
|
||||||
|
... Array.from(p[0]).map(([k, vp]) => {
|
||||||
|
const tmp = gentemp();
|
||||||
|
return parens(seq(
|
||||||
|
`(${tmp} = ${v}.get(${literal(k)})) !== void 0 && `,
|
||||||
|
walk(tmp, vp)));
|
||||||
|
}));
|
||||||
|
default:
|
||||||
|
((_p: never) => {})(p);
|
||||||
|
throw new Error("Unreachable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unname(p: NamedPattern): Pattern {
|
||||||
|
return (p.label === M.NAMED) ? p[1] : p;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [name0, pattern] of Schema._.definitions(schema)) {
|
||||||
|
const name = name0 as symbol;
|
||||||
|
temps = [];
|
||||||
|
const recognizer = walk('v', pattern);
|
||||||
|
types.push(
|
||||||
|
seq(`export type ${name.description!} = `, typeFor(pattern)));
|
||||||
|
predicates.push(
|
||||||
|
seq('export function ', `is${name.description!}`,
|
||||||
|
'(v: any): v is ', name.description!, ' ',
|
||||||
|
block(
|
||||||
|
... temps.length > 0 ? [seq('let ', commas(... temps), ': any')] : [],
|
||||||
|
seq('return ', recognizer))));
|
||||||
|
}
|
||||||
|
|
||||||
|
const f = new Formatter();
|
||||||
|
f.write(`import * as _ from ${JSON.stringify(preservesModule)};\n`);
|
||||||
|
f.newline();
|
||||||
|
|
||||||
|
for (const [lit, varname] of literals) {
|
||||||
|
f.write(seq(`const ${varname} = `, sourceCodeFor(lit), `;\n`));
|
||||||
|
}
|
||||||
|
f.newline();
|
||||||
|
|
||||||
|
types.forEach(t => {
|
||||||
|
f.write(t);
|
||||||
|
f.newline();
|
||||||
|
});
|
||||||
|
f.newline();
|
||||||
|
|
||||||
|
predicates.forEach(p => {
|
||||||
|
f.write(p);
|
||||||
|
f.newline();
|
||||||
|
f.newline();
|
||||||
|
});
|
||||||
|
|
||||||
|
return f.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringSource(s: string) {
|
||||||
|
return JSON.stringify(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sourceCodeFor(v: Value<any>): Item {
|
||||||
|
return fold(v, {
|
||||||
|
boolean(b: boolean): Item { return b.toString(); },
|
||||||
|
single(f: number): Item { return f.toString(); },
|
||||||
|
double(f: number): Item { return f.toString(); },
|
||||||
|
integer(i: number): Item { return i.toString(); },
|
||||||
|
string(s: string): Item { return stringSource(s); },
|
||||||
|
bytes(b: Bytes): Item {
|
||||||
|
return seq(`Uint8Array.from(`, brackets(... Array.from(b).map(b => b.toString())), `)`);
|
||||||
|
},
|
||||||
|
symbol(s: symbol): Item { return `Symbol.for(${stringSource(s.description!)})`; },
|
||||||
|
|
||||||
|
record(r: Record<Value<any>, Tuple<Value<any>>, any>, k: Fold<any, Item>): Item {
|
||||||
|
return seq(`_.Record`, parens(k(r.label), brackets(... r.map(k))));
|
||||||
|
},
|
||||||
|
array(a: Array<Value<any>>, k: Fold<any, Item>): Item {
|
||||||
|
return brackets(... a.map(k));
|
||||||
|
},
|
||||||
|
set(s: Set<any>, k: Fold<any, Item>): Item {
|
||||||
|
return seq('new _.Set', parens(brackets(... Array.from(s).map(k))));
|
||||||
|
},
|
||||||
|
dictionary(d: Dictionary<Value<any>, any>, k: Fold<any, Item>): Item {
|
||||||
|
return seq('new _.Dictionary', parens(brackets(... Array.from(d).map(([kk,vv]) =>
|
||||||
|
brackets(k(kk), k(vv))))));
|
||||||
|
},
|
||||||
|
|
||||||
|
annotated(a: Annotated<any>, k: Fold<any, Item>): Item {
|
||||||
|
return seq('_.annotate', parens(k(a.item), ... a.annotations.map(k)));
|
||||||
|
},
|
||||||
|
|
||||||
|
pointer(t: any, _k: Fold<any, Item>): Item {
|
||||||
|
throw new Error(preserves`Cannot emit source code for construction of pointer ${t}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
import fs from 'fs';
|
||||||
|
import { readSchema } from "./reader";
|
||||||
|
import { Reader } from "../reader";
|
||||||
|
import { BASE } from "./base";
|
||||||
|
function main() {
|
||||||
|
const src = fs.readFileSync(__dirname + '/../../../../schema/schema.txt', 'utf-8');
|
||||||
|
const sch = readSchema(new Reader<never>(src, { includeAnnotations: true }).readToEnd());
|
||||||
|
console.log(compile([{ moduleName: 'BASE', modulePath: 'BASE', schema: BASE, inline: true }], sch, '..'));
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
|
@ -0,0 +1,89 @@
|
||||||
|
import { Environment, Pattern, NamedPattern, lookup } from "./meta";
|
||||||
|
import * as M from './meta';
|
||||||
|
import { Value, Float, Bytes, is, isPointer, Record, Dictionary, Set } from "..";
|
||||||
|
|
||||||
|
export function validator(env: Environment, p: Pattern): (v: Value<any>) => boolean {
|
||||||
|
function walk(p: Pattern, v: Value<any>, recordOkAsTuple = false): boolean {
|
||||||
|
switch (p.label) {
|
||||||
|
case M.ATOM:
|
||||||
|
switch (p[0]) {
|
||||||
|
case M.BOOLEAN: return typeof v === 'boolean';
|
||||||
|
case M.FLOAT: return Float.isSingle(v);
|
||||||
|
case M.DOUBLE: return Float.isDouble(v);
|
||||||
|
case M.SIGNEDINTEGER: return typeof v === 'number';
|
||||||
|
case M.STRING: return typeof v === 'string';
|
||||||
|
case M.BYTESTRING: return Bytes.isBytes(v);
|
||||||
|
case M.SYMBOL: return typeof v === 'symbol';
|
||||||
|
}
|
||||||
|
case M.LIT:
|
||||||
|
return is(v, p[0]);
|
||||||
|
case M.REF:
|
||||||
|
return walk(lookup(env, p[0]), v);
|
||||||
|
case M.OR:
|
||||||
|
for (const pp of p[0]) {
|
||||||
|
if (walk(pp, v)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
case M.AND:
|
||||||
|
for (const pp of p[0]) {
|
||||||
|
if (!walk(pp, v)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case M.POINTER:
|
||||||
|
return isPointer(v);
|
||||||
|
case M.REC:
|
||||||
|
if (!Record.isRecord(v)) return false;
|
||||||
|
if (!walk(p[0], v.label)) return false;
|
||||||
|
return walk(p[1], v, true);
|
||||||
|
case M.TUPLE:
|
||||||
|
if (!Array.isArray(v)) return false;
|
||||||
|
if (!recordOkAsTuple && Record.isRecord(v)) return false;
|
||||||
|
if (p[0].length !== v.length) return false;
|
||||||
|
for (let i = 0; i < v.length; i++) {
|
||||||
|
if (!walknamed(p[0][i], v[i])) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case M.TUPLESTAR:
|
||||||
|
if (!Array.isArray(v)) return false;
|
||||||
|
if (!recordOkAsTuple && Record.isRecord(v)) return false;
|
||||||
|
if (p[0].length > v.length) return false;
|
||||||
|
for (let i = 0; i < p[0].length; i++) {
|
||||||
|
if (!walknamed(p[0][i], v[i])) return false;
|
||||||
|
}
|
||||||
|
for (let i = p[0].length; i < v.length; i++) {
|
||||||
|
if (!walknamed(p[1], v[i])) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case M.SETOF:
|
||||||
|
if (!Set.isSet(v)) return false;
|
||||||
|
for (const vv of v) {
|
||||||
|
if (!walk(p[0], vv)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case M.DICTOF:
|
||||||
|
if (!Dictionary.isDictionary<Value<any>>(v)) return false;
|
||||||
|
for (const e of v) {
|
||||||
|
if (!walk(p[0], e[0])) return false;
|
||||||
|
if (!walk(p[1], e[1])) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case M.DICT:
|
||||||
|
if (!Dictionary.isDictionary<Value<any>>(v)) return false;
|
||||||
|
for (const e of p[0]) {
|
||||||
|
const vv = v.get(e[0]);
|
||||||
|
if (vv === void 0) return false;
|
||||||
|
if (!walk(e[1], vv)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
((_p: never) => {})(p);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function walknamed(p: NamedPattern, v: Value<any>): boolean {
|
||||||
|
return (p.label === M.NAMED) ? walk(p[1], v) : walk(p, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
return v => walk(p, v);
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { Dictionary, KeyedDictionary, Record, Value, preserves } from '..';
|
||||||
|
|
||||||
|
export type Input = Value<never>;
|
||||||
|
|
||||||
|
export const AND = Symbol.for('and');
|
||||||
|
export const ANDSYM = Symbol.for('&');
|
||||||
|
export const ATOM = Symbol.for('atom');
|
||||||
|
export const BOOLEAN = Symbol.for('Boolean');
|
||||||
|
export const BYTESTRING = Symbol.for('ByteString');
|
||||||
|
export const DICT = Symbol.for('dict');
|
||||||
|
export const DICTOF = Symbol.for('dictof');
|
||||||
|
export const DOT = Symbol.for('.');
|
||||||
|
export const DOTDOTDOT = Symbol.for('...');
|
||||||
|
export const DOUBLE = Symbol.for('Double');
|
||||||
|
export const EQUALS = Symbol.for('=');
|
||||||
|
export const FLOAT = Symbol.for('Float');
|
||||||
|
export const LIT = Symbol.for('lit');
|
||||||
|
export const NAMED = Symbol.for('named');
|
||||||
|
export const OR = Symbol.for('or');
|
||||||
|
export const ORSYM = Symbol.for('/');
|
||||||
|
export const POINTER = Symbol.for('pointer');
|
||||||
|
export const REC = Symbol.for('rec');
|
||||||
|
export const REF = Symbol.for('ref');
|
||||||
|
export const SCHEMA = Symbol.for('schema');
|
||||||
|
export const SETOF = Symbol.for('setof');
|
||||||
|
export const SIGNEDINTEGER = Symbol.for('SignedInteger');
|
||||||
|
export const STRING = Symbol.for('String');
|
||||||
|
export const SYMBOL = Symbol.for('Symbol');
|
||||||
|
export const TUPLE = Symbol.for('tuple');
|
||||||
|
export const TUPLESTAR = Symbol.for('tuple*');
|
||||||
|
export const VERSION = Symbol.for('version');
|
||||||
|
|
||||||
|
export type Environment = Array<Schema>;
|
||||||
|
|
||||||
|
export const Schema = Record.makeConstructor<{
|
||||||
|
version: number,
|
||||||
|
definitions: KeyedDictionary<symbol, Pattern, never>,
|
||||||
|
}>()(Symbol.for('schema'), ['version', 'definitions']);
|
||||||
|
export type Schema = ReturnType<typeof Schema>;
|
||||||
|
|
||||||
|
export type AtomKind =
|
||||||
|
| typeof BOOLEAN
|
||||||
|
| typeof FLOAT
|
||||||
|
| typeof DOUBLE
|
||||||
|
| typeof SIGNEDINTEGER
|
||||||
|
| typeof STRING
|
||||||
|
| typeof BYTESTRING
|
||||||
|
| typeof SYMBOL;
|
||||||
|
|
||||||
|
export type Pattern =
|
||||||
|
| Record<typeof ATOM, [AtomKind], never>
|
||||||
|
| Record<typeof LIT, [Input], never>
|
||||||
|
| Record<typeof REF, [symbol], never>
|
||||||
|
| Record<typeof OR, [Array<Pattern>], never>
|
||||||
|
| Record<typeof AND, [Array<Pattern>], never>
|
||||||
|
| Record<typeof POINTER, [], never>
|
||||||
|
| Record<typeof REC, [Pattern, Pattern], never>
|
||||||
|
| Record<typeof TUPLE, [Array<NamedPattern>], never>
|
||||||
|
| Record<typeof TUPLESTAR, [Array<NamedPattern>, NamedPattern], never>
|
||||||
|
| Record<typeof SETOF, [Pattern], never>
|
||||||
|
| Record<typeof DICTOF, [Pattern, Pattern], never>
|
||||||
|
| Record<typeof DICT, [Dictionary<Pattern, never>], never>;
|
||||||
|
|
||||||
|
export type NamedPattern = Record<typeof NAMED, [symbol, Pattern], never> | Pattern;
|
||||||
|
|
||||||
|
export function lookup(env: Environment, name: symbol): Pattern {
|
||||||
|
for (const s of env) {
|
||||||
|
const p = Schema._.definitions(s).get(name);
|
||||||
|
if (p !== void 0) return p;
|
||||||
|
}
|
||||||
|
throw new Error(preserves`Schema: unbound name ${name}`);
|
||||||
|
}
|
|
@ -0,0 +1,163 @@
|
||||||
|
import { Annotated, Dictionary, is, KeyedDictionary, peel, preserves, Record, strip, Tuple } from '..';
|
||||||
|
|
||||||
|
import { Input, NamedPattern, Pattern, Schema } from './meta';
|
||||||
|
import * as M from './meta';
|
||||||
|
|
||||||
|
function splitBy<T>(items: Array<T>, separator: T): Array<Array<T>> {
|
||||||
|
const groups: Array<Array<T>> = [];
|
||||||
|
let group: Array<T> = [];
|
||||||
|
function finish() {
|
||||||
|
if (group.length > 0) {
|
||||||
|
groups.push(group);
|
||||||
|
group = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const item of items) {
|
||||||
|
if (is(item, separator)) {
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
group.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finish();
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
function invalidClause(clause: Input): never {
|
||||||
|
throw new Error(preserves`Invalid Schema clause: ${clause}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function invalidPattern(name: symbol, item: Input): never {
|
||||||
|
throw new Error(preserves`Invalid pattern in ${name}: ${item}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readSchema(toplevelTokens: Array<Input>): Schema {
|
||||||
|
const toplevelClauses = splitBy(peel(toplevelTokens) as Array<Input>, M.DOT);
|
||||||
|
let version: number | undefined = void 0;
|
||||||
|
let definitions = new KeyedDictionary<symbol, Pattern, never>();
|
||||||
|
for (const clause0 of toplevelClauses) {
|
||||||
|
const clause = clause0.map(peel);
|
||||||
|
if (!Array.isArray(clause)) {
|
||||||
|
invalidClause(clause);
|
||||||
|
} else if (clause.length >= 2 && is(clause[1], M.EQUALS)) {
|
||||||
|
if (typeof clause[0] !== 'symbol') invalidClause(clause);
|
||||||
|
const name = clause[0];
|
||||||
|
if (definitions.has(name)) {
|
||||||
|
throw new Error(preserves`Duplicate definition: ${clause}`);
|
||||||
|
}
|
||||||
|
definitions.set(name, parseDefinition(name, clause.slice(2).map(peel)));
|
||||||
|
} else if (clause.length === 2 && is(clause[0], M.VERSION)) {
|
||||||
|
if (typeof clause[1] !== 'number') invalidClause(clause);
|
||||||
|
version = clause[1];
|
||||||
|
} else {
|
||||||
|
invalidClause(clause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (version === void 0) {
|
||||||
|
throw new Error("Schema: missing version declaration.");
|
||||||
|
}
|
||||||
|
if (version !== 1) {
|
||||||
|
throw new Error("Schema: unsupported version " + version);
|
||||||
|
}
|
||||||
|
return Record(M.SCHEMA, [version, definitions]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseDefinition(name: symbol, body: Array<Input>): Pattern {
|
||||||
|
return parseOp(body, M.ORSYM, p => parseOp(p, M.ANDSYM, p => parseBase(name, p)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseOp(body: Array<Input>, op: Input, k: (p: Array<Input>) => Pattern): Pattern {
|
||||||
|
const pieces = splitBy(body, op);
|
||||||
|
if (pieces.length === 1) return k(pieces[0]);
|
||||||
|
switch (op) {
|
||||||
|
case M.ORSYM: return Record(M.OR, [pieces.map(k)]);
|
||||||
|
case M.ANDSYM: return Record(M.AND, [pieces.map(k)]);
|
||||||
|
default: throw new Error("Internal error: unexpected operator");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findName(x: Input): symbol | false {
|
||||||
|
if (!Annotated.isAnnotated<never>(x)) return false;
|
||||||
|
for (const a0 of x.annotations) {
|
||||||
|
const a = peel(a0);
|
||||||
|
if (typeof a === 'symbol') return a;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseBase(name: symbol, body: Array<Input>): Pattern {
|
||||||
|
body = peel(body) as Array<Input>;
|
||||||
|
if (body.length !== 1) invalidPattern(name, body);
|
||||||
|
const item = peel(body[0]);
|
||||||
|
const walk = (b: Input): Pattern => parseBase(name, [b]);
|
||||||
|
const namedwalk = (b: Input): NamedPattern => {
|
||||||
|
const name = findName(b);
|
||||||
|
if (name === false) return walk(b);
|
||||||
|
return Record(M.NAMED, [name, walk(b)]);
|
||||||
|
};
|
||||||
|
const walkitems = (b: Input): Pattern[] => {
|
||||||
|
b = peel(b);
|
||||||
|
if (!Array.isArray(b)) complain();
|
||||||
|
return b.map(walk);
|
||||||
|
};
|
||||||
|
|
||||||
|
function complain(): never {
|
||||||
|
invalidPattern(name, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof item === 'symbol') {
|
||||||
|
const s = item.description;
|
||||||
|
if (s === void 0) complain();
|
||||||
|
if (s[0] === '=') return Record(M.LIT, [Symbol.for(s.slice(1))]);
|
||||||
|
return Record(M.REF, [item]);
|
||||||
|
} else if (Record.isRecord<Input, Tuple<Input>, never>(item)) {
|
||||||
|
const label = item.label;
|
||||||
|
if (Record.isRecord<Input, [], never>(label)) {
|
||||||
|
if (label.length !== 0) complain();
|
||||||
|
switch (label.label) {
|
||||||
|
case M.LIT:
|
||||||
|
if (item.length !== 1) complain();
|
||||||
|
return Record(M.LIT, [item[0]]);
|
||||||
|
case M.OR:
|
||||||
|
if (item.length !== 1) complain();
|
||||||
|
return Record(M.OR, [walkitems(item[0])]);
|
||||||
|
case M.AND:
|
||||||
|
if (item.length !== 1) complain();
|
||||||
|
return Record(M.AND, [walkitems(item[0])]);
|
||||||
|
case M.REC:
|
||||||
|
if (item.length !== 2) complain();
|
||||||
|
return Record(M.REC, [walk(item[0]), walk(item[1])]);
|
||||||
|
default:
|
||||||
|
complain();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Record(M.REC, [Record(M.LIT, [label]), Record(M.TUPLE, [item.map(namedwalk)])]);
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(item)) {
|
||||||
|
if (is(item[item.length - 1], M.DOTDOTDOT)) {
|
||||||
|
if (item.length < 2) complain();
|
||||||
|
return Record(M.TUPLESTAR, [
|
||||||
|
item.slice(0, item.length - 2).map(namedwalk),
|
||||||
|
namedwalk(item[item.length - 2]),
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
return Record(M.TUPLE, [item.map(namedwalk)]);
|
||||||
|
}
|
||||||
|
} else if (Dictionary.isDictionary<Input, never>(item)) {
|
||||||
|
if (item.size === 2 && item.has(M.DOTDOTDOT)) {
|
||||||
|
const v = item.clone();
|
||||||
|
v.delete(M.DOTDOTDOT);
|
||||||
|
const [[kp, vp]] = v.entries();
|
||||||
|
return Record(M.DICTOF, [walk(kp), walk(vp)]);
|
||||||
|
} else {
|
||||||
|
return Record(M.DICT, [item.mapEntries<Pattern, Input, never>(([k, vp]) =>
|
||||||
|
[strip(k), walk(vp)])]);
|
||||||
|
}
|
||||||
|
} else if (Set.isSet<never>(item)) {
|
||||||
|
if (item.size !== 1) complain();
|
||||||
|
const [vp] = item.entries();
|
||||||
|
return Record(M.SETOF, [walk(vp)]);
|
||||||
|
} else {
|
||||||
|
return Record(M.LIT, [strip(item)]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
@<EmacsMode "-*- preserves -*-">
|
||||||
|
|
||||||
|
version 1 .
|
||||||
|
|
||||||
|
Schema = <schema {
|
||||||
|
version: Version
|
||||||
|
definitions: { symbol: Pattern ...:... }
|
||||||
|
}>.
|
||||||
|
|
||||||
|
; version 1 .
|
||||||
|
Version = 1 .
|
||||||
|
|
||||||
|
Pattern = <<or> [
|
||||||
|
; special builtins or <<atom> symbol>
|
||||||
|
<atom @atomKind <<or> [=Boolean =Float =Double =SignedInteger =String =ByteString =Symbol]>>
|
||||||
|
|
||||||
|
; =symbol, <<lit> any>, or plain non-symbol atom
|
||||||
|
<lit @value any>
|
||||||
|
|
||||||
|
; symbol
|
||||||
|
<ref @name symbol>
|
||||||
|
|
||||||
|
; Pattern / Pattern / ...
|
||||||
|
; <<or> [Pattern Pattern ...]>
|
||||||
|
; and the empty pattern is <<or> []>
|
||||||
|
<or @patterns [Pattern ...]>
|
||||||
|
|
||||||
|
; Pattern & Pattern & ...
|
||||||
|
; <<and> [Pattern Pattern ...]>
|
||||||
|
; and the universal pattern, "any", is <<and> []>
|
||||||
|
<and @patterns [Pattern ...]>
|
||||||
|
|
||||||
|
; <label a b c> ----> <rec <lit label> <tuple [<ref a> <ref b> <ref c>]>>
|
||||||
|
; except for record labels
|
||||||
|
; <<rec> x y> ---> <rec <ref x> <ref y>>
|
||||||
|
<rec @label Pattern @fields Pattern>
|
||||||
|
|
||||||
|
; [a b c] ----> <tuple [<ref a> <ref b> <ref c>]>
|
||||||
|
<tuple @patterns [NamedPattern ...]>
|
||||||
|
|
||||||
|
; [a b c ...] ----> <tuple* [<ref a> <ref b>] <ref c>]>
|
||||||
|
<tuple* @fixed [NamedPattern ...] @variable NamedPattern>
|
||||||
|
|
||||||
|
; #{p} ----> <setof <ref p>>
|
||||||
|
<setof @pattern Pattern>
|
||||||
|
|
||||||
|
; {k: v, ...:...} ----> <dictof <ref k> <ref v>>
|
||||||
|
<dictof @key Pattern @value Pattern>
|
||||||
|
|
||||||
|
; {a: b, c: d} ----> <dict {a: <ref b>, c: <ref d>}>
|
||||||
|
<dict @entries { any: Pattern ...:... }>
|
||||||
|
]>.
|
||||||
|
|
||||||
|
NamedPattern = <named @name symbol @pattern Pattern> / Pattern .
|
Loading…
Reference in New Issue