2021-03-18 10:15:10 +00:00
|
|
|
import { Dictionary, KeyedSet, Position } from "@preserves/core";
|
|
|
|
import { refPosition } from "../reader";
|
|
|
|
import * as M from "../meta";
|
2021-03-18 21:33:37 +00:00
|
|
|
import { block, braces, commas, formatItems, Item, keyvalue, seq } from "./block";
|
2021-03-19 22:42:43 +00:00
|
|
|
import { ANY_TYPE, renderType, Type, variantInitFor } from "./type";
|
2021-03-18 10:15:10 +00:00
|
|
|
|
|
|
|
export interface CompilerOptions {
|
|
|
|
preservesModule?: string;
|
|
|
|
defaultPointer?: M.Ref;
|
|
|
|
warn?(message: string, pos: Position | null): void;
|
|
|
|
}
|
|
|
|
|
2021-03-18 21:33:37 +00:00
|
|
|
export interface Capture {
|
|
|
|
fieldName: string;
|
|
|
|
sourceExpr: string;
|
|
|
|
}
|
|
|
|
|
2021-03-23 10:36:55 +00:00
|
|
|
export const RECURSION_LIMIT = 128;
|
|
|
|
|
2021-03-18 10:15:10 +00:00
|
|
|
export class ModuleContext {
|
|
|
|
readonly env: M.Environment;
|
|
|
|
readonly schema: M.Schema;
|
|
|
|
readonly options: CompilerOptions;
|
|
|
|
|
|
|
|
readonly literals = new Dictionary<never, 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) {
|
2021-03-21 21:02:34 +00:00
|
|
|
varname = M.jsId('$' + v.asPreservesText(), () => '__lit' + this.literals.size);
|
2021-03-18 10:15:10 +00:00
|
|
|
this.literals.set(v, varname);
|
|
|
|
}
|
|
|
|
return varname;
|
|
|
|
}
|
|
|
|
|
2021-03-23 10:36:55 +00:00
|
|
|
derefPattern(p: M.Definition, refCount = 0): M.Definition {
|
|
|
|
if (refCount > RECURSION_LIMIT) {
|
|
|
|
throw new Error('Recursion limit exceeded');
|
|
|
|
}
|
|
|
|
if (p._variant === 'Alternative' &&
|
|
|
|
p.value._variant === 'Pattern' &&
|
|
|
|
p.value.value._variant === 'SimplePattern' &&
|
|
|
|
p.value.value.value._variant === 'Ref')
|
2021-03-22 11:13:34 +00:00
|
|
|
{
|
2021-03-23 10:36:55 +00:00
|
|
|
return M.lookup(refPosition(p.value.value.value.value),
|
|
|
|
p.value.value.value.value,
|
|
|
|
this.env,
|
|
|
|
(p) => this.derefPattern(p, refCount + 1),
|
|
|
|
(_modId, _modPath, pp) => this.derefPattern(pp ?? p, refCount + 1));
|
2021-03-18 10:15:10 +00:00
|
|
|
} else {
|
2021-03-23 10:36:55 +00:00
|
|
|
return p;
|
2021-03-18 10:15:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
defineType(f: Item): void {
|
|
|
|
this.typedefs.push(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
defineFunction(f: (ctx: FunctionContext) => Item): void {
|
|
|
|
this.functiondefs.push(f(new FunctionContext(this)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class FunctionContext {
|
|
|
|
readonly mod: ModuleContext;
|
|
|
|
|
|
|
|
tempCounter = 0;
|
2021-03-18 21:33:37 +00:00
|
|
|
temps: Map<string, { type: Item, names: string[] }> = new Map();
|
2021-03-19 22:42:43 +00:00
|
|
|
|
2021-03-18 21:33:37 +00:00
|
|
|
captures: Capture[] = [];
|
|
|
|
variantName: string | undefined = void 0;
|
2021-03-18 10:15:10 +00:00
|
|
|
|
|
|
|
constructor(mod: ModuleContext) {
|
|
|
|
this.mod = mod;
|
|
|
|
}
|
|
|
|
|
2021-03-18 21:33:37 +00:00
|
|
|
gentempname(): string {
|
|
|
|
return '_tmp' + this.tempCounter++;
|
2021-03-18 10:15:10 +00:00
|
|
|
}
|
|
|
|
|
2021-03-19 22:42:43 +00:00
|
|
|
gentemp(vartype: Type = ANY_TYPE): string {
|
|
|
|
const typeitem = renderType(vartype);
|
|
|
|
const typestr = formatItems([typeitem], Infinity);
|
2021-03-18 21:33:37 +00:00
|
|
|
const varname = this.gentempname();
|
|
|
|
let e = this.temps.get(typestr);
|
|
|
|
if (e === void 0) {
|
2021-03-19 22:42:43 +00:00
|
|
|
e = { type: typeitem, names: [] };
|
2021-03-18 21:33:37 +00:00
|
|
|
this.temps.set(typestr, e);
|
|
|
|
}
|
|
|
|
e.names.push(varname);
|
|
|
|
return varname;
|
2021-03-18 10:15:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
block(f: () => Item[]): Item {
|
|
|
|
const oldTemps = this.temps;
|
2021-03-18 21:33:37 +00:00
|
|
|
this.temps = new Map();
|
2021-03-18 10:15:10 +00:00
|
|
|
const items = f();
|
|
|
|
const ts = this.temps;
|
|
|
|
this.temps = oldTemps;
|
|
|
|
return block(
|
2021-03-18 21:33:37 +00:00
|
|
|
... Array.from(ts).map(([_typestr, { type, names }]) =>
|
2021-03-19 22:42:43 +00:00
|
|
|
seq(`let `, commas(... names), `: (`, type, `) | undefined`)),
|
2021-03-18 10:15:10 +00:00
|
|
|
... items);
|
|
|
|
}
|
2021-03-18 21:33:37 +00:00
|
|
|
|
2021-03-18 21:41:27 +00:00
|
|
|
withCapture<R>(
|
|
|
|
fieldName: string | undefined, sourceExpr: string, ks: (sourceExpr: string) => R): R
|
2021-03-18 21:33:37 +00:00
|
|
|
{
|
|
|
|
if (fieldName !== void 0) this.captures.push({ fieldName, sourceExpr });
|
2021-03-18 21:41:27 +00:00
|
|
|
const result = ks(sourceExpr);
|
2021-03-18 21:33:37 +00:00
|
|
|
if (fieldName !== void 0) this.captures.pop();
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2021-03-18 21:41:27 +00:00
|
|
|
convertCapture(
|
|
|
|
fieldName: string | undefined, sourceExpr: string, ks: (sourceExpr: string) => Item[]): Item
|
|
|
|
{
|
|
|
|
return this.withCapture(fieldName, sourceExpr, sourceExpr =>
|
|
|
|
seq(`if (${sourceExpr} !== void 0) `, this.block(() => ks(sourceExpr))));
|
|
|
|
}
|
|
|
|
|
2021-03-18 21:33:37 +00:00
|
|
|
buildCapturedCompound(dest: string): Item {
|
2021-03-23 10:36:55 +00:00
|
|
|
const fields = [
|
2021-03-18 21:33:37 +00:00
|
|
|
... variantInitFor(this.variantName),
|
|
|
|
... this.captures.map(({ fieldName, sourceExpr }) =>
|
2021-03-23 10:36:55 +00:00
|
|
|
keyvalue(fieldName, sourceExpr))
|
|
|
|
];
|
|
|
|
return seq(`${dest} = `, fields.length === 0 ? `null` : braces(... fields));
|
2021-03-18 21:33:37 +00:00
|
|
|
}
|
|
|
|
}
|