diff --git a/implementations/javascript/packages/schema/src/compiler/genconverter.ts b/implementations/javascript/packages/schema/src/compiler/genconverter.ts index 8f194b9..4225a53 100644 --- a/implementations/javascript/packages/schema/src/compiler/genconverter.ts +++ b/implementations/javascript/packages/schema/src/compiler/genconverter.ts @@ -12,7 +12,7 @@ export function converterForDefinition( dest: string): Item[] { if (p._variant === 'or') { - const alts = p.patterns; + const alts = [p.pattern, ... p.patterns]; function loop(i: number): Item[] { ctx.variantName = alts[i].variantLabel; return [... converterForAlternative(ctx, alts[i].alternative, src, dest), @@ -20,7 +20,7 @@ export function converterForDefinition( ? [seq(`if (${dest} === void 0) `, ctx.block(() => loop(i + 1)))] : [])]; } - return alts.length === 0 ? [] : loop(0); + return loop(0); } else { ctx.variantName = void 0; return converterForAlternative(ctx, p.value, src, dest); @@ -34,13 +34,13 @@ function converterForAlternative( dest: string): Item[] { if (p._variant === 'and') { - const alts = p.patterns; + const alts = [p.pattern, ... p.patterns]; function loop(i: number): Item[] { return (i < alts.length) ? converterFor(ctx, alts[i], src, () => loop(i + 1)) : [ctx.buildCapturedCompound(dest)]; } - return alts.length === 0 ? [seq(`${dest} = ${src}`)] : loop(0); + return loop(0); } else { return converterFor(ctx, M.NamedPattern.anonymous(p.value), src, simpleValue => { if (simpleValue === void 0) { diff --git a/implementations/javascript/packages/schema/src/compiler/gentype.ts b/implementations/javascript/packages/schema/src/compiler/gentype.ts index cb825f4..e32b262 100644 --- a/implementations/javascript/packages/schema/src/compiler/gentype.ts +++ b/implementations/javascript/packages/schema/src/compiler/gentype.ts @@ -6,7 +6,8 @@ import { ANY_TYPE, AtomicType, CollectionType, FieldMap, SimpleType, Type } from export function typeForDefinition(mod: ModuleContext, d: M.Definition): Type { if (d._variant === 'or') { return Type.union( - new Map(d.patterns.map(a => [a.variantLabel, typeForAlternative(mod, a.alternative)]))); + new Map([d.pattern, ... d.patterns].map(a => + [a.variantLabel, typeForAlternative(mod, a.alternative)]))); } else { return typeForAlternative(mod, d.value); } @@ -15,6 +16,7 @@ export function typeForDefinition(mod: ModuleContext, d: M.Definition): Type { export function typeForAlternative(mod: ModuleContext, a: M.Alternative): SimpleType { if (a._variant === 'and') { const fs = new Map(); + gatherFields(fs, mod, a.pattern); a.patterns.forEach(n => gatherFields(fs, mod, n)); return fs.size > 0 ? Type.record(fs) : Type.unit(); } else { diff --git a/implementations/javascript/packages/schema/src/compiler/genunconverter.ts b/implementations/javascript/packages/schema/src/compiler/genunconverter.ts index 3cbba75..95e3eca 100644 --- a/implementations/javascript/packages/schema/src/compiler/genunconverter.ts +++ b/implementations/javascript/packages/schema/src/compiler/genunconverter.ts @@ -13,7 +13,7 @@ export function unconverterForDefinition( { if (def._variant === 'or') { return [seq(`switch (${src}._variant) `, block( - ... def.patterns.map(p => + ... [def.pattern, ... def.patterns].map(p => seq(`case `, JSON.stringify(p.variantLabel), `: `, ctx.block(() => { const hasValueField = p.alternative._variant === 'Pattern' && @@ -37,7 +37,7 @@ function unconverterForAlternative( const t = typeForAlternative(ctx.mod, a); if (a._variant === 'and') { const errs: [string, InsufficientInformationError][] = []; - const cs = a.patterns.flatMap(p => { + const cs = [a.pattern, ... a.patterns].flatMap(p => { if (p._variant === 'anonymous' && p.value._variant === 'SimplePattern') { return []; } else { diff --git a/implementations/javascript/packages/schema/src/gen/schema.ts b/implementations/javascript/packages/schema/src/gen/schema.ts index 96aaecb..2840fdc 100644 --- a/implementations/javascript/packages/schema/src/gen/schema.ts +++ b/implementations/javascript/packages/schema/src/gen/schema.ts @@ -40,14 +40,18 @@ export type PointerName = ({"_variant": "Ref", "value": Ref} | {"_variant": "fal export type Definitions = _.KeyedDictionary; export type Definition = ( - {"_variant": "or", "patterns": Array} | + { + "_variant": "or", + "pattern": NamedAlternative, + "patterns": Array + } | {"_variant": "Alternative", "value": Alternative} ); export type NamedAlternative = {"variantLabel": string, "alternative": Alternative}; export type Alternative = ( - {"_variant": "and", "patterns": Array} | + {"_variant": "and", "pattern": NamedPattern, "patterns": Array} | {"_variant": "Pattern", "value": Pattern} ); @@ -122,14 +126,16 @@ export namespace PointerName { export function Definitions(value: _.KeyedDictionary): Definitions {return value;} export namespace Definition { - export function or(patterns: Array): Definition {return {"_variant": "or", "patterns": patterns};}; + export function or( + {pattern, patterns}: {pattern: NamedAlternative, patterns: Array} + ): Definition {return {"_variant": "or", "pattern": pattern, "patterns": patterns};}; export function Alternative(value: Alternative): Definition {return {"_variant": "Alternative", "value": value};}; } export function NamedAlternative({variantLabel, alternative}: {variantLabel: string, alternative: Alternative}): NamedAlternative {return {"variantLabel": variantLabel, "alternative": alternative};} export namespace Alternative { - export function and(patterns: Array): Alternative {return {"_variant": "and", "patterns": patterns};}; + export function and({pattern, patterns}: {pattern: NamedPattern, patterns: Array}): Alternative {return {"_variant": "and", "pattern": pattern, "patterns": patterns};}; export function Pattern(value: Pattern): Alternative {return {"_variant": "Pattern", "value": value};}; } @@ -319,35 +325,49 @@ export function toDefinition(v: _val): undefined | Definition { let _tmp0: (null) | undefined; _tmp0 = _.is(v.label, $or) ? null : void 0; if (_tmp0 !== void 0) { - if (_.Array.isArray(v[0])) { - let _tmp1: (Array<_val>) | undefined; - let _tmp2: (Array) | undefined; - _tmp1 = v[0]; - { - _tmp2 = []; - for (const _tmp3 of _tmp1) { - let _tmp4: (NamedAlternative) | undefined; - _tmp4 = toNamedAlternative(_tmp3); - if (_tmp4 !== void 0) {_tmp2.push(_tmp4); continue;}; - _tmp2 = void 0; - break; + if (_.Array.isArray(v[0]) && v[0].length >= 1) { + let _tmp1: (NamedAlternative) | undefined; + _tmp1 = toNamedAlternative(v[0][0]); + if (_tmp1 !== void 0) { + let _tmp2: (Array<_val>) | undefined; + let _tmp3: (Array) | undefined; + _tmp2 = v[0].slice(1); + { + _tmp3 = []; + for (const _tmp4 of _tmp2) { + let _tmp5: (NamedAlternative) | undefined; + _tmp5 = toNamedAlternative(_tmp4); + if (_tmp5 !== void 0) {_tmp3.push(_tmp5); continue;}; + _tmp3 = void 0; + break; + }; + if (_tmp3 !== void 0) {result = {"_variant": "or", "pattern": _tmp1, "patterns": _tmp3};}; }; - if (_tmp2 !== void 0) {result = {"_variant": "or", "patterns": _tmp2};}; }; }; }; }; if (result === void 0) { - let _tmp5: (Alternative) | undefined; - _tmp5 = toAlternative(v); - if (_tmp5 !== void 0) {result = {"_variant": "Alternative", "value": _tmp5};}; + let _tmp6: (Alternative) | undefined; + _tmp6 = toAlternative(v); + if (_tmp6 !== void 0) {result = {"_variant": "Alternative", "value": _tmp6};}; }; return result; } export function fromDefinition(_v: Definition): _val { switch (_v._variant) { - case "or": {return _.Record($or, [_v["patterns"].map(v => fromNamedAlternative(v))]);}; + case "or": { + return _.Record( + $or, + [ + [ + fromNamedAlternative(_v["pattern"]), + ... _v["patterns"].map(v => fromNamedAlternative(v)) + ] + ] + ); + }; case "Alternative": {return fromAlternative(_v.value);}; }; } @@ -386,35 +406,49 @@ export function toAlternative(v: _val): undefined | Alternative { let _tmp0: (null) | undefined; _tmp0 = _.is(v.label, $and) ? null : void 0; if (_tmp0 !== void 0) { - if (_.Array.isArray(v[0])) { - let _tmp1: (Array<_val>) | undefined; - let _tmp2: (Array) | undefined; - _tmp1 = v[0]; - { - _tmp2 = []; - for (const _tmp3 of _tmp1) { - let _tmp4: (NamedPattern) | undefined; - _tmp4 = toNamedPattern(_tmp3); - if (_tmp4 !== void 0) {_tmp2.push(_tmp4); continue;}; - _tmp2 = void 0; - break; + if (_.Array.isArray(v[0]) && v[0].length >= 1) { + let _tmp1: (NamedPattern) | undefined; + _tmp1 = toNamedPattern(v[0][0]); + if (_tmp1 !== void 0) { + let _tmp2: (Array<_val>) | undefined; + let _tmp3: (Array) | undefined; + _tmp2 = v[0].slice(1); + { + _tmp3 = []; + for (const _tmp4 of _tmp2) { + let _tmp5: (NamedPattern) | undefined; + _tmp5 = toNamedPattern(_tmp4); + if (_tmp5 !== void 0) {_tmp3.push(_tmp5); continue;}; + _tmp3 = void 0; + break; + }; + if (_tmp3 !== void 0) {result = {"_variant": "and", "pattern": _tmp1, "patterns": _tmp3};}; }; - if (_tmp2 !== void 0) {result = {"_variant": "and", "patterns": _tmp2};}; }; }; }; }; if (result === void 0) { - let _tmp5: (Pattern) | undefined; - _tmp5 = toPattern(v); - if (_tmp5 !== void 0) {result = {"_variant": "Pattern", "value": _tmp5};}; + let _tmp6: (Pattern) | undefined; + _tmp6 = toPattern(v); + if (_tmp6 !== void 0) {result = {"_variant": "Pattern", "value": _tmp6};}; }; return result; } export function fromAlternative(_v: Alternative): _val { switch (_v._variant) { - case "and": {return _.Record($and, [_v["patterns"].map(v => fromNamedPattern(v))]);}; + case "and": { + return _.Record( + $and, + [ + [ + fromNamedPattern(_v["pattern"]), + ... _v["patterns"].map(v => fromNamedPattern(v)) + ] + ] + ); + }; case "Pattern": {return fromPattern(_v.value);}; }; } diff --git a/implementations/javascript/packages/schema/src/reader.ts b/implementations/javascript/packages/schema/src/reader.ts index ba3b83a..8a188c6 100644 --- a/implementations/javascript/packages/schema/src/reader.ts +++ b/implementations/javascript/packages/schema/src/reader.ts @@ -83,7 +83,7 @@ export function parseSchema(toplevelTokens: Array, if (definitions.has(name)) { throw new SchemaSyntaxError(preserves`Duplicate definition: ${clause}`, pos); } - definitions.set(name, parseDefinition(name, clause.slice(2))); + definitions.set(name, parseDefinition(name, pos, clause.slice(2))); } else if (clause.length === 2 && is(clause[0], M.$version)) { version = M.asVersion(peel(clause[1])); } else if (clause.length === 2 && is(clause[0], M.$pointer)) { @@ -125,7 +125,7 @@ function namedMustBeSimple(p: Position | null): never { throw new SchemaSyntaxError('Named patterns must be Simple patterns', p); } -function parseDefinition(name: symbol, body: Array): Definition { +function parseDefinition(name: symbol, pos: Position | null, body: Array): Definition { let nextAnonymousAlternativeNumber = 0; function alternativeName([input, p]: readonly [Array, Alternative]) : M.NamedAlternative @@ -183,14 +183,22 @@ function parseDefinition(name: symbol, body: Array): Definition { // 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(pos, + body, M.ORSYM, - p => [p, parseOp(p, + p => [p, parseOp(pos, + p, M.ANDSYM, p => [p, parsePattern(name, p)] as const, - ps => M.Alternative.and(ps.map(patternName)), + (p0, ps) => M.Alternative.and({ + pattern: patternName(p0), + patterns: ps.map(patternName) + }), p => M.Alternative.Pattern(p[1]))] as const, - ps => M.Definition.or(ps.map(alternativeName)), + (p0, ps) => M.Definition.or({ + pattern: alternativeName(p0), + patterns: ps.map(alternativeName) + }), p => M.Definition.Alternative(p[1])); } @@ -340,14 +348,19 @@ function parsePattern(name: symbol, body0: Array): Pattern { () => M.Pattern.CompoundPattern(parseCompound(body[0]))); } -function parseOp(body: Array, +function parseOp(pos: Position | null, + body: Array, op: Input, each: (p: Array) => Each, - combineN: (ps: Array) => Combined, + combineN: (p0: Each, ps: Array) => Combined, finish1: (p: Each) => Combined): Combined { const pieces = splitBy(body, op).map(each); - return (pieces.length === 1) ? finish1(pieces[0]) : combineN(pieces); + if (pieces.length === 0) { + throw new SchemaSyntaxError( + preserves`Invalid Schema clause: cannot handle empty ${op}`, pos); + } + return (pieces.length === 1) ? finish1(pieces[0]) : combineN(pieces[0], pieces.slice(1)); } function findName(x: Input): symbol | false { diff --git a/schema/schema.prs b/schema/schema.prs index cdba44b..49e8b6b 100644 --- a/schema/schema.prs +++ b/schema/schema.prs @@ -17,18 +17,13 @@ Definitions = { symbol: Definition ...:... }. ; Pattern / Pattern / ... ; and the empty pattern is -; -; TODO: refactor to be -; / Alternative . -; Similarly for -; -Definition = / Alternative . +Definition = / Alternative . NamedAlternative = [@variantLabel string @alternative Alternative]. ; Pattern & Pattern & ... ; and the universal pattern, "any", is -Alternative = / Pattern . +Alternative = / Pattern . Pattern = SimplePattern / CompoundPattern .