preserves/implementations/javascript/packages/schema/src/compiler/context.ts

149 lines
4.8 KiB
TypeScript

import { Dictionary, KeyedSet, Position } from "@preserves/core";
import { refPosition } from "../reader";
import * as M from "../meta";
import { block, braces, commas, formatItems, Item, keyvalue, seq } from "./block";
import { ANY_TYPE, FieldType, Type } from "../type";
import { renderType, variantInitFor } from "./rendertype";
export interface CompilerOptions {
preservesModule?: string;
defaultEmbeddedType?: M.Ref;
warn?(message: string, pos: Position | null): void;
}
export interface Capture {
fieldName: string;
sourceExpr: string;
}
export const RECURSION_LIMIT = 128;
export class ModuleContext {
readonly env: M.Environment;
readonly schema: M.Schema;
readonly options: CompilerOptions;
readonly literals = new Dictionary<M._embedded, string>();
readonly typedefs: Item[] = [];
readonly functiondefs: Item[] = [];
readonly imports = new KeyedSet<[string, string]>();
constructor(env: M.Environment, schema: M.Schema, options: CompilerOptions) {
this.env = env;
this.schema = schema;
this.options = options;
}
literal(v: M.Input): Item {
let varname = this.literals.get(v);
if (varname === void 0) {
varname = M.jsId('$' + v.asPreservesText(), () => '__lit' + this.literals.size);
this.literals.set(v, varname);
}
return varname;
}
derefPattern(p: M.Definition, refCount = 0): M.Definition {
if (refCount > RECURSION_LIMIT) {
throw new Error('Recursion limit exceeded');
}
if (p._variant === 'Pattern' &&
p.value._variant === 'SimplePattern' &&
p.value.value._variant === 'Ref')
{
return M.lookup(refPosition(p.value.value.value),
p.value.value.value,
this.env,
(p) => this.derefPattern(p, refCount + 1),
(_modId, _modPath, pp) => this.derefPattern(pp ?? p, refCount + 1));
} else {
return p;
}
}
defineType(f: Item): void {
this.typedefs.push(f);
}
defineFunction(f: (ctx: FunctionContext) => Item): void {
this.functiondefs.push(f(new FunctionContext(this)));
}
resolver(): (ref: M.Ref) => FieldType {
return (ref) => M.lookup(refPosition(ref), ref, this.env,
(_p) => Type.ref(ref.name.description!),
(modId, modPath,_p) => {
this.imports.add([modId, modPath]);
return Type.ref(`${modId}.${ref.name.description!}`);
});
}
}
export class FunctionContext {
readonly mod: ModuleContext;
tempCounter = 0;
temps: Map<string, { type: Item, names: string[] }> = new Map();
captures: Capture[] = [];
variantName: string | undefined = void 0;
constructor(mod: ModuleContext) {
this.mod = mod;
}
gentempname(): string {
return '_tmp' + this.tempCounter++;
}
gentemp(vartype: Type = ANY_TYPE): string {
const typeitem = renderType(vartype);
const typestr = formatItems([typeitem], Infinity);
const varname = this.gentempname();
let e = this.temps.get(typestr);
if (e === void 0) {
e = { type: typeitem, names: [] };
this.temps.set(typestr, e);
}
e.names.push(varname);
return varname;
}
block(f: () => Item[]): Item {
const oldTemps = this.temps;
this.temps = new Map();
const items = f();
const ts = this.temps;
this.temps = oldTemps;
return block(
... Array.from(ts).map(([_typestr, { type, names }]) =>
seq(`let `, commas(... names), `: (`, type, `) | undefined`)),
... items);
}
withCapture<R>(
fieldName: string | undefined, sourceExpr: string, ks: (sourceExpr: string) => R): R
{
if (fieldName !== void 0) this.captures.push({ fieldName, sourceExpr });
const result = ks(sourceExpr);
if (fieldName !== void 0) this.captures.pop();
return result;
}
convertCapture(
fieldName: string | undefined, sourceExpr: string, ks: () => Item[]): Item
{
return this.withCapture(fieldName, sourceExpr, sourceExpr =>
seq(`if (${sourceExpr} !== void 0) `, this.block(() => ks())));
}
buildCapturedCompound(dest: string): Item {
const fields = [
... variantInitFor(this.variantName),
... this.captures.map(({ fieldName, sourceExpr }) =>
keyvalue(fieldName, sourceExpr))
];
return seq(`${dest} = `, fields.length === 0 ? `null` : braces(... fields));
}
}