import { CharStream, Interval, ParseTree, Token, TokenStream } from "antlr4ng"; import { Constructor } from "../reflection/IMember.js"; import { AttributeRenderer } from "../AttributeRenderer.js"; import { ErrorType } from "../misc/ErrorType.js"; import { ModelAdaptor } from "../ModelAdaptor.js"; import { STErrorListener } from "../STErrorListener.js"; import { STWriter } from "../STWriter.js"; /** * An instance of the StringTemplate. It consists primarily of * a {@link ST#impl reference} to its implementation (shared among all * instances) and a hash table of {@link ST#locals attributes}. Because * of dynamic scoping, we also need a reference to any enclosing instance. For * example, in a deeply nested template for an HTML page body, we could still * reference the title attribute defined in the outermost page template. *

* To use templates, you create one (usually via {@link STGroup}) and then inject * attributes using {@link #add}. To render its attacks, use {@link ST#render()}.

*

* TODO: {@link ST#locals} is not actually a hash table like the documentation * says.

*/ export interface IST { /** The implementation for this template among all instances of same template . */ impl?: ICompiledST; groupThatCreatedThisInstance: ISTGroup; /** * Safe to simultaneously write via {@link #add}, which is synchronized. * Reading during exec is, however, NOT synchronized. So, not thread safe * to add attributes while it is being evaluated. Initialized to * {@link #EMPTY_ATTR} to distinguish {@code null} from empty. */ locals?: unknown[]; /** * Inject an attribute (name/value pair). If there is already an attribute * with that name, this method turns the attribute into an * {@link AttributeList} with both the previous and the new attribute as * elements. This method will never alter a {@link List} that you inject. * If you send in a {@link List} and then inject a single value element, * {@code add} copies original list and adds the new value. The * attribute name cannot be null or contain '.'. *

* Return {@code this} so we can chain:

*

* {@code t.add("x", 1).add("y", "hi")}

*/ add(name: string, value: unknown): IST; /** * Remove an attribute value entirely (can't remove attribute definitions). */ remove(name: string): void; /** * Set {@code locals} attribute value when you only know the name, not the * index. This is ultimately invoked by calling {@code ST#add} from * outside so toss an exception to notify them. */ rawSetAttribute(name: string, value: unknown): void; isAnonSubtemplate(): boolean; /** * Split {@code aggrName.{propName1,propName2}} into list * {@code [propName1, propName2]} and the {@code aggrName}. Spaces are * allowed around {@code ','}. */ addAggr(aggregateSpec: string, ...values: unknown[]): IST; render(lineWidth?: number): string; render(locale: Intl.Locale, lineWidth?: number): string; write(out: STWriter): number; write(out: STWriter, locale: Intl.Locale): number; write(out: STWriter, listener: STErrorListener): number; write(out: STWriter, locale: Intl.Locale, listener: STErrorListener): number; } /** * This class knows how to execute template byte codes relative to a particular * {@link STGroup}. To execute the byte codes, we need an output stream and a * reference to an {@link ST} instance. That instance's {@link ST#impl} field * points at a {@link CompiledST}, which contains all of the byte codes and * other information relevant to execution. *

* This interpreter is a stack-based bytecode interpreter. All operands go onto * an operand stack.

*

* If {@link #debug} set, we track interpreter events. For now, I am only * tracking instance creation events. These are used by {@link STViz} to pair up * output chunks with the template expressions that generate them.

*

* We create a new interpreter for each invocation of * {@link ST#render}, {@link ST#inspect}, or {@link ST#getEvents}.

*/ export interface IInterpreter { } export interface IErrorManager { runTimeError(interp: IInterpreter, scope: IInstanceScope, error: ErrorType): void; runTimeError(interp: IInterpreter, scope: IInstanceScope, error: ErrorType, arg: unknown): void; runTimeError(interp: IInterpreter, scope: IInstanceScope, error: ErrorType, e: Error, arg: unknown): void; runTimeError(interp: IInterpreter, scope: IInstanceScope, error: ErrorType, arg: unknown, arg2: unknown): void; runTimeError(interp: IInterpreter, scope: IInstanceScope, error: ErrorType, arg: unknown, arg2: unknown, arg3: unknown): void; iOError(self: IST | undefined, error: ErrorType, e: Error, arg?: unknown): void; internalError(self: IST | undefined, msg: string, e?: Error): void; } export interface IInterpEvent { } export interface IEvalTemplateEvent extends IInterpEvent { } export interface IInstanceScope { /** Template that invoked us. */ readonly parent?: IInstanceScope; /** Template we're executing. */ /** Current instruction pointer. */ ip: number; earlyEval: boolean; } /** * A directory or directory tree of {@code .st} template files and/or group files. * Individual template files contain formal template definitions. In a sense, * it's like a single group file broken into multiple files, one for each template. * ST v3 had just the pure template inside, not the template name and header. * Name inside must match filename (minus suffix). */ export interface ISTGroup { /** * Every group can import templates/dictionaries from other groups. * The list must be synchronized (see {@link STGroup#importTemplates}). */ readonly imports: ISTGroup[]; /** * The {@link ErrorManager} for entire group; all compilations and executions. * This gets copied to parsers, walkers, and interpreters. */ errMgr: IErrorManager; delimiterStartChar: string; delimiterStopChar: string; iterateAcrossValues: boolean; getName(): string; rawGetDictionary(name: string): Map | undefined; isDictionary(name: string): boolean; /** * Get renderer for class {@code T} associated with this group. *

* For non-imported groups and object-to-render of class {@code T}, use renderer * (if any) registered for {@code T}. For imports, any renderer * set on import group is ignored even when using an imported template. * You should set the renderer on the main group * you use (or all to be sure). I look at import groups as * "helpers" that should give me templates and nothing else. If you * have multiple renderers for {@code String}, say, then just make uber combined * renderer with more specific format names.

*/ getAttributeRenderer(attributeType: Constructor): AttributeRenderer | undefined; lookupImportedTemplate(name: string): ICompiledST | null; unload(): void; getEmbeddedInstanceOf(interp: IInterpreter, scope: IInstanceScope, name: string): IST; /** * Differentiate so we can avoid having creation events for regions, * map operations, and other implicit "new ST" events during rendering. */ createStringTemplateInternally(implOrProto: ICompiledST | IST): IST; compile(params: ICompilerParameters): ICompiledST | undefined; getModelAdaptor(attributeType: Constructor): ModelAdaptor; /** * Register a renderer for all objects of a particular "kind" for all * templates evaluated relative to this group. Use {@code r} to render if * object in question is an instance of {@code attributeType}. Recursively * set renderer into all import groups. */ registerRenderer(attributeType: Constructor, r: AttributeRenderer, recursive?: boolean): void; lookupTemplate(name: string): ICompiledST | null; defineTemplate(templateName: string, template: string): ICompiledST; defineTemplate(name: string, argsS: string, template: string): ICompiledST; defineTemplate(fullyQualifiedTemplateName: string, nameT: Token, args?: IFormalArgument[], template?: string, templateToken?: Token): ICompiledST; /** * Load template stream into this group. {@code unqualifiedFileName} is * {@code "a.st"}. The {@code prefix} is path from group root to * {@code unqualifiedFileName} like {@code "/subdir"} if file is in * {@code /subdir/a.st}. */ loadTemplateFile(prefix: string, unqualifiedFileName: string, templateStream: CharStream): ICompiledST | undefined; setListener(listener: STErrorListener): void; } /** * The result of compiling an {@link ST}. Contains all the bytecode instructions, * string table, bytecode address to source code map, and other bookkeeping * info. It's the implementation of an ST you might say. All instances * of the same template share a single implementation ({@link ST#impl} field). */ export interface ICompiledST { name?: string; /** * Every template knows where it is relative to the group that loaded it. * The prefix is the relative path from the root. {@code "/prefix/name"} is * the fully qualified name of this template. All calls to * {@link STGroup#getInstanceOf} calls must use fully qualified names. A * {@code "/"} is added to the front if you don't specify one. Template * references within template code, however, uses relative names, unless of * course the name starts with {@code "/"}. *

* This has nothing to do with the outer filesystem path to the group dir or * group file.

*

* We set this as we load/compile the template.

*

* Always ends with {@code "/"}.

*/ prefix: string; /** * The original, immutable pattern (not really used again after * initial "compilation"). Useful for debugging. Even for * sub templates, this is entire overall template. */ template: string; /** The token that begins template definition; could be {@code <@r>} of region. */ templateDefStartToken?: Token; /** Overall token stream for template (debug only). */ tokens?: TokenStream; /** How do we interpret syntax of template? (debug only) */ tree?: ParseTree; readonly formalArguments?: Map; hasFormalArgs: boolean; numberOfArgsWithDefaultValues: number; /** * The group that physically defines this {@link ST} definition. We use it * to initiate interpretation via {@link ST#toString}. From there, it * becomes field {@link Interpreter#group} and is fixed until rendering * completes. */ nativeGroup: ISTGroup; /** * Does this template come from a {@code <@region>...<@end>} embedded in * another template? */ isRegion: boolean; /** * If someone refs {@code <@r()>} in template t, an implicit * *

* {@code @t.r() ::= ""}

*

* is defined, but you can overwrite this def by defining your own. We need * to prevent more than one manual def though. Between this var and * {@link #isRegion} we can determine these cases.

*/ regionDefType: RegionType; isAnonSubtemplate: boolean; readonly strings: string[]; codeSize: number; sourceMap: Interval[]; readonly instructions: Int8Array; clone(): ICompiledST; /** * Used by {@link ST#add} to add args one by one without turning on full formal args definition signal. */ addArg(a: IFormalArgument): void; defineImplicitlyDefinedTemplates(group: ISTGroup): void; defineArgDefaultValueTemplates(group: ISTGroup): void; dump(): void; } /** * Represents the name of a formal argument defined in a template: *
 *  test(a,b,x=defaultValue) ::= "  "
 * 
Each template has a set of these formal arguments or sets * {@link CompiledST#hasFormalArgs} to {@code false} (indicating that no * arguments were specified such as when we create a template with * {@code new ST(...)}). * *

* Note: originally, I tracked cardinality as well as the name of an attribute. * I'm leaving the code here as I suspect something may come of it later. * Currently, though, cardinality is not used.

*/ export interface IFormalArgument { name: string; index: number; /** If they specified default value {@code x=y}, store the token here */ defaultValueToken?: Token; defaultValue: unknown; compiledDefaultValue?: ICompiledST; } /** Parameters for the compilation step. */ export interface ICompilerParameters { srcName?: string; name?: string; args?: IFormalArgument[]; template: string; templateToken?: Token; } /** {@code <@r()>}, {@code <@r>...<@end>}, and {@code @t.r() ::= "..."} defined manually by coder */ export declare enum RegionType { /** {@code <@r()>} */ IMPLICIT = 0, /** {@code <@r>...<@end>} */ EMBEDDED = 1, /** {@code @t.r() ::= "..."} */ EXPLICIT = 2 } export declare const isCompiledST: (value: unknown) => value is ICompiledST; export declare namespace ISTGroup { const DEFAULT_KEY = "default"; const DICT_KEY = "key"; }