import { Reader, Annotated, Dictionary, is, KeyedDictionary, peel, preserves, Record, strip, Tuple } from '@preserves/core'; import { Input, NamedPattern, Pattern, Schema } from './meta'; import * as M from './meta'; function splitBy(items: Array, separator: T): Array> { const groups: Array> = []; let group: Array = []; 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(source: string): Schema { const toplevelTokens = new Reader(source, { includeAnnotations: true }).readToEnd(); return parseSchema(toplevelTokens); } export function parseSchema(toplevelTokens: Array): Schema { const toplevelClauses = splitBy(peel(toplevelTokens) as Array, M.DOT); let version: number | undefined = void 0; let definitions = new KeyedDictionary(); 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 (!M.isValidToken(name.description!)) { throw new Error(preserves`Invalid definition name: ${name}`); } 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): Pattern { return parseOp(body, M.ORSYM, p => parseOp(p, M.ANDSYM, p => parseBase(name, p))); } function parseOp(body: Array, op: Input, k: (p: Array) => 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(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): Pattern { body = peel(body) as Array; 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, never>(item)) { const label = item.label; if (Record.isRecord(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.___tuple_STAR_, [ 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(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(([k, vp]) => [strip(k), walk(vp)])]); } } else if (Set.isSet(item)) { if (item.size !== 1) complain(); const [vp] = item.entries(); return Record(M.___setof, [walk(vp)]); } else { return Record(M.___lit, [strip(item)]); } }