134 lines
4.1 KiB
TypeScript
134 lines
4.1 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";
|
|
|
|
export interface CompilerOptions {
|
|
preservesModule?: string;
|
|
defaultPointer?: M.Ref;
|
|
warn?(message: string, pos: Position | null): void;
|
|
}
|
|
|
|
export interface Capture {
|
|
fieldName: string;
|
|
sourceExpr: string;
|
|
}
|
|
|
|
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) {
|
|
const s = v.asPreservesText()
|
|
.replace('_', '__')
|
|
.replace('*', '_STAR_');
|
|
varname = M.isValidToken('_' + s, true) ? '$' + s : '__lit' + this.literals.size;
|
|
this.literals.set(v, varname);
|
|
}
|
|
return varname;
|
|
}
|
|
|
|
derefPattern([_name, p]: [string, M.Alternative]): M.Definition {
|
|
if (p.label === M.$ref) {
|
|
return M.lookup(refPosition(p), p, this.env,
|
|
(p) => p,
|
|
(p) => p,
|
|
(_modId, _modPath, pp) => pp ?? p);
|
|
} else {
|
|
return p;
|
|
}
|
|
}
|
|
|
|
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;
|
|
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(... vartypePieces: Item[]): string {
|
|
const vartype = vartypePieces.length === 0 ? '_val | undefined' : seq(... vartypePieces);
|
|
const typestr = formatItems([vartype], Infinity);
|
|
const varname = this.gentempname();
|
|
let e = this.temps.get(typestr);
|
|
if (e === void 0) {
|
|
e = { type: vartype, 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)),
|
|
... items);
|
|
}
|
|
|
|
withCapture(fieldName: string | undefined,
|
|
sourceExpr: string,
|
|
ks: (sourceExpr: string) => Item[],
|
|
withCheck = true): Item
|
|
{
|
|
if (fieldName !== void 0) this.captures.push({ fieldName, sourceExpr });
|
|
const result = withCheck
|
|
? seq(`if (${sourceExpr} !== void 0) `, this.block(() => ks(sourceExpr)))
|
|
: block(... ks(sourceExpr));
|
|
if (fieldName !== void 0) this.captures.pop();
|
|
return result;
|
|
}
|
|
|
|
buildCapturedCompound(dest: string): Item {
|
|
return seq(`${dest} = `, braces(
|
|
... variantInitFor(this.variantName),
|
|
... this.captures.map(({ 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));
|
|
}
|