Fix types.
This commit is contained in:
parent
c2fe82e71d
commit
889d38bbb8
|
@ -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, [])],
|
|
||||||
])],
|
|
||||||
])]));
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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));
|
|
||||||
}
|
|
||||||
|
|
|
@ -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})`];
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
||||||
: []);
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
export * from './reader';
|
export * from './reader';
|
||||||
export * from './compiler';
|
export * from './compiler';
|
||||||
export * from './base';
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue