refactor(shader-lab): tidy Preprocessor (single chunk cache, dead code cleanup)

- Merge _macroDefineIncludeMap and the would-be output cache into a single
  _chunkCache: Map<path, {output, macros}>. Cache hits now skip the entire
  parse() recursion instead of re-running _includeReg.replace.
- Drop the parseMacro=false parameter — it was a workaround for the
  cache-not-cached-enough issue, no longer needed.
- Collapse _symbolReg + _funcCallReg into a single _referenceReg.
- Extract _registerDefine, drop dead !!valueRaw guard (the regex's [ ]+
  before value already enforces non-empty capture).
- Collapse _mergeMacroDefineLists branches with ??=, also isolates cache
  arrays from caller mutation.

Net: -35 lines, no behavior change, 24/24 shader-lab tests pass.
Performance unchanged within measurement noise.
This commit is contained in:
chenmo.gl
2026-04-25 14:46:01 +08:00
parent 94fb096c9f
commit 6ac85446cc

View File

@@ -4,31 +4,18 @@ import { ShaderLib } from "@galacean/engine";
import type { ASTNode } from "./parser/AST";
/**
* Record for a single `#define` directive.
*
* Two shapes coexist and can be combined on the same entry:
*
* - **Expression macros** — `valueAst` holds an `AssignmentExpression` subtree
* produced by the `macro_define` CFG rule. These participate in type
* inference, varying flattening, and visitor-based codegen.
* - **Legacy opaque macros** — the directive is emitted verbatim as a
* `MACRO_DEFINE_EXPRESSION` token and expanded by the GLSL driver. `valueAst`
* is undefined.
*
* `referenceName` captures the top-level external identifier the value refers
* to, if any — the callee of a function-call value (`#define F foo(a,b)`
* → `foo`) or the single-identifier value (`#define F foo` → `foo`). Empty
* for numeric literals, qualifier fragments, or structurally complex values.
* Populated by the regex pass; drives symbol-table lookup for macro call
* sites, independent of whether the value also has an AST form.
* Record for a single `#define` directive. `valueAst` is set for expression
* macros (joined into the AST pipeline); opaque macros leave it undefined and
* are emitted verbatim to the GLSL driver.
*/
export interface MacroDefineInfo {
isFunction: boolean;
name: string;
params: string[];
/** AST for expression-style macros. Absent for opaque macros. */
valueAst?: ASTNode.AssignmentExpression;
/** Top-level external identifier the value references, or empty string. */
/** Leading identifier of the value (`#define F foo` → `foo`, `#define F foo(a)`
* → `foo`), or empty for literals / operator expressions. Drives symbol-table
* lookup at macro call sites. */
referenceName: string;
}
@@ -40,34 +27,34 @@ export class Preprocessor {
private static readonly _includeReg = /^[ \t]*#include +"([\w\d./]+)"/gm;
private static readonly _macroRegex =
/^\s*#define\s+(\w+)[ ]*(\(([^)]*)\))?[ ]+(\(?\w+\)?.*?)(?:\/\/.*|\/\*.*?\*\/)?\s*$/gm;
private static readonly _symbolReg = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
private static readonly _funcCallReg = /^([a-zA-Z_][a-zA-Z0-9_]*)\s*\(.*\)$/;
private static readonly _macroDefineIncludeMap = new Map<string, MacroDefineList>();
// Matches a bare identifier or a function-call whole-value form only.
// Mixed-operator values (`a + b`) are intentionally rejected.
private static readonly _referenceReg = /^([a-zA-Z_]\w*)(?:\s*\(.*\))?$/;
private static readonly _chunkCache = new Map<string, { output: string; macros: MacroDefineList }>();
/**
* @internal
*/
static _repeatIncludeSet = new Set<string>();
static parse(
source: string,
basePathForIncludeKey: string,
outMacroDefineList: MacroDefineList,
parseMacro = true
): string {
if (parseMacro) {
this._parseMacroDefines(source, outMacroDefineList);
}
static parse(source: string, basePathForIncludeKey: string, outMacroDefineList: MacroDefineList): string {
this._parseMacroDefines(source, outMacroDefineList);
return source.replace(this._includeReg, (_, includeName) =>
this._replace(includeName, basePathForIncludeKey, outMacroDefineList)
);
}
/**
* For each registered definition of `macroName`, push its `referenceName`
* (when non-empty and not a macro parameter) so call sites can drive
* symbol-table lookup.
*/
private static _parseMacroDefines(source: string, outMacroList: MacroDefineList): void {
this._macroRegex.lastIndex = 0;
let match: RegExpExecArray | null;
while ((match = this._macroRegex.exec(source)) !== null) {
const [, name, paramsGroup, paramsStr, valueRaw] = match;
this._registerDefine(outMacroList, name, paramsGroup ? paramsStr : undefined, valueRaw);
}
}
/** Collect unique `referenceName`s of `macroName`'s definitions, skipping
* names that shadow a macro parameter. */
static getReferenceSymbolNames(macroDefineList: MacroDefineList, macroName: string, out: string[]): void {
out.length = 0;
const infos = macroDefineList[macroName];
@@ -82,15 +69,9 @@ export class Preprocessor {
}
}
/**
* Extract the top-level identifier a value refers to, or empty string if
* the value is a numeric literal, qualifier fragment, or structurally
* complex form we don't try to introspect.
*/
private static _extractReferenceName(value: string): string {
if (this._symbolReg.test(value)) return value;
const callMatch = this._funcCallReg.exec(value);
return callMatch ? callMatch[1] : "";
const match = this._referenceReg.exec(value);
return match ? match[1] : "";
}
private static _isExist(list: MacroDefineInfo[], item: MacroDefineInfo): boolean {
@@ -103,45 +84,36 @@ export class Preprocessor {
);
}
private static _parseMacroDefines(source: string, outMacroList: MacroDefineList): void {
let match: RegExpExecArray | null;
this._macroRegex.lastIndex = 0;
private static _registerDefine(
outMacroList: MacroDefineList,
name: string,
paramsStr: string | undefined,
valueRaw: string
): void {
// `paramsStr` is `undefined` for object-like macros and the `()` contents
// (possibly empty) for function-like ones.
const isFunction = paramsStr !== undefined;
const params = paramsStr ? paramsStr.split(",").map((p) => p.trim()).filter(Boolean) : [];
const value = valueRaw.trim();
const referenceName = value ? this._extractReferenceName(value) : "";
while ((match = this._macroRegex.exec(source)) !== null) {
const [, name, paramsGroup, paramsStr, valueRaw] = match;
const isFunction = !!paramsGroup && !!valueRaw;
const params =
isFunction && paramsStr
? paramsStr
.split(",")
.map((p) => p.trim())
.filter(Boolean)
: [];
const value = valueRaw ? valueRaw.trim() : "";
const referenceName = value ? this._extractReferenceName(value) : "";
const info: MacroDefineInfo = { isFunction, name, params, referenceName };
const info: MacroDefineInfo = { isFunction, name, params, referenceName };
const arr = outMacroList[name];
if (arr) {
if (!this._isExist(arr, info)) arr.push(info);
} else {
outMacroList[name] = [info];
}
const arr = outMacroList[name];
if (arr) {
if (!this._isExist(arr, info)) arr.push(info);
} else {
outMacroList[name] = [info];
}
}
private static _mergeMacroDefineLists(from: MacroDefineList, to: MacroDefineList): void {
for (const macroName in from) {
if (to[macroName]) {
const target = to[macroName];
const src = from[macroName];
for (let i = 0; i < src.length; i++) {
const info = src[i];
if (!this._isExist(target, info)) target.push(info);
}
} else {
to[macroName] = from[macroName];
for (const name in from) {
const target = to[name] ?? (to[name] = []);
const src = from[name];
for (let i = 0, n = src.length; i < n; i++) {
const info = src[i];
if (!this._isExist(target, info)) target.push(info);
}
}
}
@@ -170,15 +142,14 @@ export class Preprocessor {
}
this._repeatIncludeSet.add(path);
if (this._macroDefineIncludeMap.has(path)) {
this._mergeMacroDefineLists(this._macroDefineIncludeMap.get(path)!, outMacroDefineList);
} else {
const chunkMacroDefineList: MacroDefineList = {};
this._parseMacroDefines(chunk, chunkMacroDefineList);
this._macroDefineIncludeMap.set(path, chunkMacroDefineList);
this._mergeMacroDefineLists(chunkMacroDefineList, outMacroDefineList);
let entry = this._chunkCache.get(path);
if (!entry) {
const macros: MacroDefineList = {};
const output = this.parse(chunk, basePathForIncludeKey, macros);
entry = { output, macros };
this._chunkCache.set(path, entry);
}
return this.parse(chunk, basePathForIncludeKey, outMacroDefineList, false);
this._mergeMacroDefineLists(entry.macros, outMacroDefineList);
return entry.output;
}
}