@galacean/engine-shader-compiler 2.0.0-alpha.32 → 2.0.0-alpha.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -365,6 +365,9 @@ var ShaderCompilerUtils = /*#__PURE__*/ function() {
365
365
  };
366
366
  ShaderCompilerUtils.createGSError = function createGSError(message, errorName, source, location, file) {
367
367
  console.error(message);
368
+ var err = new Error(message);
369
+ err.name = errorName;
370
+ return err;
368
371
  };
369
372
  return ShaderCompilerUtils;
370
373
  }();
@@ -553,36 +556,46 @@ var ParserUtils = /*#__PURE__*/ function() {
553
556
  }).filter(Boolean);
554
557
  };
555
558
  /**
556
- * Return the lexeme of `expr` when it is (transitively) a single bare identifier
557
- * wrapped in primary/postfix expression nodes, e.g. `o` → "o". Returns null for
558
- * compound expressions (`o.x`, `foo(..)`, `arr[0]`, …). Useful for callers that
559
- * want to apply a substitution rule only at the root of an expression, never on
560
- * swizzles or nested member access.
561
- */ ParserUtils.extractDirectIdentLexeme = function extractDirectIdentLexeme(expr) {
562
- var cur = expr;
563
- while(cur){
564
- if (_instanceof(cur, ASTNode.VariableIdentifier)) {
565
- var child = cur.children[0];
566
- return _instanceof(child, BaseToken) ? child.lexeme : null;
567
- }
568
- // `( expression )` form on PrimaryExpression — descend through the
569
- // wrapped Expression so `(v)` resolves to `v`. All other compound
570
- // forms (member access, function call, comma, …) bail out.
571
- if (_instanceof(cur, ASTNode.PrimaryExpression) && cur.children.length === 3) {
572
- cur = cur.children[1];
559
+ * Walk single-child precedence-chain wrappers down to a `VariableIdentifier`,
560
+ * returning the leaf node (or `undefined` for any compound expression).
561
+ *
562
+ * `allowParens` chooses between two callers' needs:
563
+ * - `true` — descend through `( expression )` form, so `(v)` resolves to
564
+ * `v`. Use when the caller substitutes at the expression root
565
+ * (aliasing, renaming); user-written parens carry no extra meaning.
566
+ * - `false` — any 3-child `PrimaryExpression` aborts; `(v)` stays
567
+ * compound. Use when the caller treats the unwrapped node as a single
568
+ * token (IO-struct arg drop, alias detection); user-written parens
569
+ * must not collapse.
570
+ */ ParserUtils.unwrapBareIdentifier = function unwrapBareIdentifier(node, options) {
571
+ var cur = node;
572
+ while(true){
573
+ if (_instanceof(cur, ASTNode.VariableIdentifier)) return cur;
574
+ if (options.allowParens && _instanceof(cur, ASTNode.PrimaryExpression) && cur.children.length === 3) {
575
+ var inner = cur.children[1];
576
+ if (!_instanceof(inner, TreeNode)) return undefined;
577
+ cur = inner;
573
578
  continue;
574
579
  }
575
- // Any precedence-chain wrapper with a single child forwards through.
576
- // Covers PostfixExpression / PrimaryExpression(len=1) / Assignment /
577
- // Conditional / LogicalOr / … all the way down — including the verbose
578
- // build's full chain and the release build's elision-flattened chain.
579
580
  if (_instanceof(cur, ASTNode.ExpressionAstNode) && cur.children.length === 1) {
580
- cur = cur.children[0];
581
+ var child = cur.children[0];
582
+ if (!_instanceof(child, TreeNode)) return undefined;
583
+ cur = child;
581
584
  continue;
582
585
  }
583
- return null;
586
+ return undefined;
584
587
  }
585
- return null;
588
+ };
589
+ /**
590
+ * Lexeme variant of `unwrapBareIdentifier({ allowParens: true })` for callers
591
+ * that already work with strings. Returns `null` for compound expressions.
592
+ */ ParserUtils.extractDirectIdentLexeme = function extractDirectIdentLexeme(expr) {
593
+ var ident = ParserUtils.unwrapBareIdentifier(expr, {
594
+ allowParens: true
595
+ });
596
+ if (!ident) return null;
597
+ var child = ident.children[0];
598
+ return _instanceof(child, BaseToken) ? child.lexeme : null;
586
599
  };
587
600
  ParserUtils.isTerminal = function isTerminal(sm) {
588
601
  return sm < NoneTerminal.START;
@@ -892,7 +905,7 @@ var GSErrorName = /*#__PURE__*/ function(GSErrorName) {
892
905
  _inherits(Lexer, BaseLexer1);
893
906
  function Lexer(source, macroDefineList) {
894
907
  var _this;
895
- _this = BaseLexer1.call(this, source) || this, _this.macroDefineList = macroDefineList, _this._needScanMacroConditionExpression = false, _this._inMacroDefineValue = false, _this._macroDefineExpectsNameToken = false, _this._macroDefineExpectsParamsToken = false, _this._macroDefineDirectiveStart = -1, _this._branchStack = [], _this._pendingBranchPushDefined = null;
908
+ _this = BaseLexer1.call(this, source) || this, _this.macroDefineList = macroDefineList, _this._needScanMacroConditionExpression = false, _this._inMacroDefineValue = false, _this._macroDefineExpectsNameToken = false, _this._macroDefineExpectsParamsToken = false, _this._currentMacroName = "", _this._currentMacroParamsLexeme = undefined, _this._currentMacroValueStart = -1, _this._branchStack = [], _this._pendingBranchPushDefined = null;
896
909
  return _this;
897
910
  }
898
911
  var _proto = Lexer.prototype;
@@ -1288,11 +1301,12 @@ var GSErrorName = /*#__PURE__*/ function(GSErrorName) {
1288
1301
  var token = BaseToken.pool.get();
1289
1302
  var word = buffer.join("");
1290
1303
  if (word === "#define") {
1291
- // Mark the directive's start offset (the `#`) so the regex-based
1292
- // registrar can later slice out the full directive text from `_source`.
1293
- // Two paths share the same registrar — single source of truth.
1294
- this._macroDefineDirectiveStart = start.index;
1295
- if (this._defineHasValue()) {
1304
+ var peek = this._peekMacroDefine();
1305
+ if (peek && peek.isExpression) {
1306
+ // AST path: the value will be tokenized by the lexer's `_inMacroDefineValue`
1307
+ // state machine and re-parsed by the expression grammar. State machine
1308
+ // fills `_currentMacroName` / `_currentMacroParamsLexeme` / `_currentMacroValueStart`
1309
+ // as it walks the value; `_emitMacroDefineEnd` consumes them.
1296
1310
  this._inMacroDefineValue = true;
1297
1311
  // The next word is the macro name: force ID type even if a prior `#define`
1298
1312
  // has already registered the name into `macroDefineList` (redefinition or
@@ -1300,10 +1314,13 @@ var GSErrorName = /*#__PURE__*/ function(GSErrorName) {
1300
1314
  this._macroDefineExpectsNameToken = true;
1301
1315
  token.set(Keyword.MACRO_DEFINE, "#define", start);
1302
1316
  } else {
1317
+ // Legacy path: opaque token sequence. Register directly from the peek
1318
+ // result without re-walking the directive.
1303
1319
  this._scanUtilBreakLine(buffer);
1304
1320
  var lexeme = "\n" + buffer.join("") + "\n";
1305
- // Legacy path: register immediately by parsing the buffered directive.
1306
- this._registerMacroDefine(this._source.slice(this._macroDefineDirectiveStart, this._currentIndex));
1321
+ if (peek) {
1322
+ this._registerMacroDefine(peek.name, peek.paramsLexeme, peek.valueStart, peek.valueEnd);
1323
+ }
1307
1324
  token.set(Keyword.MACRO_DEFINE_EXPRESSION, lexeme, start);
1308
1325
  }
1309
1326
  } else {
@@ -1320,66 +1337,151 @@ var GSErrorName = /*#__PURE__*/ function(GSErrorName) {
1320
1337
  return c === "\n" || c === "\r";
1321
1338
  };
1322
1339
  /**
1323
- * Peek forward from `#define` and decide whether this directive should use the
1324
- * expression-AST path or the legacy opaque path.
1340
+ * Walk a `#define` directive head once and produce everything both routing
1341
+ * and registration need: name range, optional params range, value range,
1342
+ * and whether the value parses as an `expression`.
1343
+ *
1344
+ * The replacement list is split three ways:
1325
1345
  *
1326
- * The AST path's only purpose is to enable the shader compiler's structural rewrites
1327
- * (varying flatten, struct-property reference tracking) on macro values that
1328
- * mention struct members. Every other shape — bare identifiers, numeric
1329
- * literals, type-alias keywords (`#define FxaaFloat2 vec2`), constructor
1330
- * calls, qualifier fragments, `#define COMMA ,` etc. — is correctly handled
1331
- * by the GLSL driver via textual substitution and needs no AST.
1346
+ * - **AST path** (`isExpression = true`): value parses as `expression`.
1347
+ * Covers identifiers, literals, parenthesized sub-expressions, operator
1348
+ * expressions, function calls, top-level comma lists (per C99 §6.10.3).
1332
1349
  *
1333
- * So the routing rule is positive and information-driven: **AST iff the
1334
- * replacement list contains a `.` member-access operator**. This is robust
1335
- * against new GLSL keywords / extensions and incidentally moves all simple
1336
- * constants and type aliases off the 18-layer expression precedence chain.
1337
- */ _proto._defineHasValue = function _defineHasValue() {
1350
+ * - **Legacy opaque path** (`isExpression = false`, no throw): the three
1351
+ * GLSL-ES-§3.4-legal-but-not-an-expression shapes with real-world use —
1352
+ * 1. empty value e.g. `#define COMMON_INCLUDED`
1353
+ * 2. single type/qualifier keyword e.g. `#define FxaaFloat float`
1354
+ * 3. type-qualifier list e.g. `#define TEX_PARAM(s) mediump sampler2D s`
1355
+ *
1356
+ * Note on X-macro support: the classical C X-macro pattern (a list
1357
+ * macro re-expanded with redefined `X(...)`) works fine — it uses
1358
+ * function-like macros + `\` line-continuation + `#undef`, all of
1359
+ * which Galacean supports. It does NOT require unbalanced parens.
1360
+ *
1361
+ * - **Authoring error** (throws): every other shape that's not a valid
1362
+ * `expression`. Legal token sequences in theory but not used in real
1363
+ * GLSL — almost always author mistakes. We surface one uniform
1364
+ * diagnostic with the macro name and value text and let the user fix
1365
+ * their code instead of routing politely.
1366
+ *
1367
+ * Unsupported shapes that throw (non-exhaustive — the predicate is
1368
+ * "the value doesn't reduce as `expression` and isn't one of the three
1369
+ * legacy shapes above"):
1370
+ * - leading bare punctuation `,` `;` `:` `?` `)` `]`
1371
+ * - trailing `,` or `;` e.g. `#define X a, b,`
1372
+ * - trailing binary / unary op `+` `-` `*` `/` `%` `&` `|` `^`
1373
+ * `<` `>` `=` `!` `~`
1374
+ * - trailing ternary fragment `?` or `:`
1375
+ * - unbalanced `[` / `]` e.g. `#define X a[b`
1376
+ * - unbalanced `(` / `)` e.g. `#define PAREN (`
1377
+ *
1378
+ * Returns `null` if the directive is malformed before the name. `cursor` is
1379
+ * the position past the last non-newline char (caller advances from there).
1380
+ */ _proto._peekMacroDefine = function _peekMacroDefine() {
1338
1381
  var src = this._source;
1339
1382
  var len = src.length;
1340
- // All position advancement goes through `_skipNonSemantic`, which honors
1341
- // line-continuation (`\` + newline) and skips comments. This is critical:
1342
- // `#define UV foo \\\n .field` is one logical directive whose value
1343
- // contains `.field`, and `#define HP /* a.b */ highp` must not let the
1344
- // `.` inside the comment trigger AST routing.
1345
1383
  var i = Lexer._skipNonSemantic(src, this._currentIndex, len);
1346
- // Skip macro name
1347
- if (!(i < len && BaseLexer.isAlpha(src.charCodeAt(i)))) return false;
1384
+ if (!(i < len && BaseLexer.isAlpha(src.charCodeAt(i)))) return null;
1385
+ var nameStart = i;
1348
1386
  while(i < len && BaseLexer.isAlnum(src.charCodeAt(i)))i++;
1349
- i = Lexer._skipNonSemantic(src, i, len);
1350
- // Optional `(params)`: scan past a balanced pair on the same logical line
1351
- if (src.charCodeAt(i) === 40 /* '(' */ ) {
1387
+ var name = src.slice(nameStart, i);
1388
+ // Optional `(params)` glued to the name (function-like per C99 §6.10.3/10;
1389
+ // whitespace before `(` makes it object-like — `(` becomes part of value).
1390
+ var paramsLexeme;
1391
+ if (i < len && src.charCodeAt(i) === 40 /* '(' */ ) {
1392
+ var paramsStart = i;
1352
1393
  var depth = 1;
1353
1394
  i++;
1354
1395
  while(i < len && depth > 0){
1355
1396
  i = Lexer._skipNonSemantic(src, i, len);
1397
+ if (i >= len) break;
1356
1398
  var c = src.charCodeAt(i);
1357
- if (c === 10 || c === 13) return false; // malformed: stay legacy
1399
+ if (c === 10 || c === 13) {
1400
+ // Unbalanced before newline — treat as malformed function-like.
1401
+ return {
1402
+ name: name,
1403
+ paramsLexeme: undefined,
1404
+ valueStart: i,
1405
+ valueEnd: i,
1406
+ cursor: i,
1407
+ isExpression: false
1408
+ };
1409
+ }
1358
1410
  if (c === 40) depth++;
1359
1411
  else if (c === 41 /* ')' */ ) depth--;
1360
1412
  i++;
1361
1413
  }
1414
+ paramsLexeme = src.slice(paramsStart, i);
1362
1415
  }
1363
- // Scan to end-of-directive looking for a `.` member-access operator.
1364
- // A `.` is member-access unless it's a decimal point inside a numeric
1365
- // literal — the latter has digits on both physical sides (`3.14`).
1366
- // Edge cases like `.5` go AST too (false positive, but AST path
1367
- // handles bare floats correctly).
1416
+ // Walk the value once tracking what routing needs.
1417
+ var valueStart = i;
1418
+ var parenDepth = 0;
1419
+ var bracketDepth = 0;
1420
+ var firstStart = -1;
1421
+ var firstEnd = -1;
1422
+ var firstFollowedByParen = false;
1423
+ var topLevelLast = -1;
1368
1424
  while(i < len){
1369
- i = Lexer._skipNonSemantic(src, i, len);
1370
- if (i >= len) break;
1425
+ var skipped = Lexer._skipNonSemantic(src, i, len);
1426
+ if (skipped !== i) {
1427
+ i = skipped;
1428
+ continue;
1429
+ }
1371
1430
  var c1 = src.charCodeAt(i);
1372
- if (c1 === 10 || c1 === 13) break; // real newline ends directive
1373
- if (c1 === 46 /* '.' */ ) {
1374
- var prev = i > 0 ? src.charCodeAt(i - 1) : 0;
1375
- var next = i + 1 < len ? src.charCodeAt(i + 1) : 0;
1376
- var prevIsDigit = prev >= 48 && prev <= 57;
1377
- var nextIsDigit = next >= 48 && next <= 57;
1378
- if (!(prevIsDigit && nextIsDigit)) return true;
1431
+ if (c1 === 10 || c1 === 13) break;
1432
+ if (firstStart === -1) {
1433
+ firstStart = i;
1434
+ if (BaseLexer.isAlpha(c1)) {
1435
+ while(i < len && BaseLexer.isAlnum(src.charCodeAt(i)))i++;
1436
+ firstEnd = i;
1437
+ var after = Lexer._skipNonSemantic(src, i, len);
1438
+ firstFollowedByParen = after < len && src.charCodeAt(after) === 40;
1439
+ topLevelLast = firstEnd - 1;
1440
+ continue;
1441
+ }
1379
1442
  }
1443
+ if (c1 === 40) parenDepth++;
1444
+ else if (c1 === 41) parenDepth--;
1445
+ else if (c1 === 91 /* [ */ ) bracketDepth++;
1446
+ else if (c1 === 93 /* ] */ ) bracketDepth--;
1447
+ if (parenDepth === 0 && bracketDepth === 0) topLevelLast = i;
1380
1448
  i++;
1381
1449
  }
1382
- return false;
1450
+ var result = {
1451
+ name: name,
1452
+ paramsLexeme: paramsLexeme,
1453
+ valueStart: valueStart,
1454
+ valueEnd: i,
1455
+ cursor: i,
1456
+ isExpression: false
1457
+ };
1458
+ // Real-world legacy shapes:
1459
+ // 1. empty value, 2. single type/qualifier kw, 3. type-qualifier list.
1460
+ if (firstStart === -1) return result;
1461
+ if (firstEnd !== -1 && !firstFollowedByParen && Lexer._isNonExpressionLeadingKeyword(src.slice(firstStart, firstEnd))) {
1462
+ return result;
1463
+ }
1464
+ // Authoring errors. Anything that's neither a legal `expression` nor one
1465
+ // of the three legacy shapes above gets a single uniform diagnostic —
1466
+ // the user sees the macro name and the value text, that's enough to
1467
+ // locate and fix. We don't categorize further; the rule for users is
1468
+ // simply "value must be a valid GLSL expression".
1469
+ //
1470
+ // Legal expression starts: alnum (identifier / literal), `(` (group),
1471
+ // `.` (GLSL ES §4.1.4 leading-dot float literal like `.5`), `-`/`+`/`!`/`~`
1472
+ // (unary). Legal expression ends: alnum (identifier / literal), `)` (group
1473
+ // close), `]` (array-index close). Everything else at the head or
1474
+ // top-level tail is an authoring error.
1475
+ var head = src.charCodeAt(firstStart);
1476
+ var tail = topLevelLast >= 0 ? src.charCodeAt(topLevelLast) : 0;
1477
+ var headIllegal = !BaseLexer.isAlnum(head) && head !== 40 /* ( */ && head !== 46 /* . */ && head !== 45 /* - */ && head !== 43 /* + */ && head !== 33 /* ! */ && head !== 126; /* ~ */
1478
+ var tailIllegal = !BaseLexer.isAlnum(tail) && tail !== 41 /* ) */ && tail !== 93; /* ] */
1479
+ if (parenDepth !== 0 || bracketDepth !== 0 || headIllegal || tailIllegal) {
1480
+ var valueText = src.slice(firstStart, i).replace(/\s+/g, " ").trim();
1481
+ this.throwError(this.getShaderPosition(0), "#define " + name + ': invalid replacement list — not a valid GLSL expression ("' + valueText + '")');
1482
+ }
1483
+ result.isExpression = true;
1484
+ return result;
1383
1485
  };
1384
1486
  /** Consuming wrapper around `_skipNonSemantic` operating on the lexer's
1385
1487
  * current position. Used by the `#define`-value scan state machine. Goes
@@ -1423,11 +1525,16 @@ var GSErrorName = /*#__PURE__*/ function(GSErrorName) {
1423
1525
  this.advance(1);
1424
1526
  }
1425
1527
  var token = BaseToken.pool.get();
1426
- token.set(Keyword.MACRO_DEFINE_PARAMS, buffer.join(""), start);
1528
+ var lexeme = buffer.join("");
1529
+ this._currentMacroParamsLexeme = lexeme;
1530
+ // Value starts after the `)` we just consumed.
1531
+ this._currentMacroValueStart = this._currentIndex;
1532
+ token.set(Keyword.MACRO_DEFINE_PARAMS, lexeme, start);
1427
1533
  return token;
1428
1534
  };
1429
1535
  /** Emit `MACRO_DEFINE_END` at the end of a `#define` value, consuming the newline. */ _proto._emitMacroDefineEnd = function _emitMacroDefineEnd() {
1430
1536
  var start = this.getShaderPosition();
1537
+ var valueEnd = this._currentIndex;
1431
1538
  var source = this._source;
1432
1539
  if (this._currentIndex < source.length) {
1433
1540
  var c = source.charCodeAt(this._currentIndex);
@@ -1437,37 +1544,29 @@ var GSErrorName = /*#__PURE__*/ function(GSErrorName) {
1437
1544
  this.advance(1);
1438
1545
  }
1439
1546
  }
1440
- // Register the directive into macroDefineList — single source of truth.
1441
- this._registerMacroDefine(this._source.slice(this._macroDefineDirectiveStart, this._currentIndex));
1547
+ this._registerMacroDefine(this._currentMacroName, this._currentMacroParamsLexeme, this._currentMacroValueStart, valueEnd);
1442
1548
  this._inMacroDefineValue = false;
1443
1549
  var token = BaseToken.pool.get();
1444
1550
  token.set(Keyword.MACRO_DEFINE_END, "\n", start);
1445
1551
  return token;
1446
1552
  };
1447
- // Parse a `#define <name>[(params)] [value]` directive (already lexed to
1448
- // its newline) and register it. Both AST and legacy paths funnel through
1449
- // here — single source of truth, no drift between two analyzers.
1450
- _proto._registerMacroDefine = function _registerMacroDefine(directive) {
1451
- // Fold line-continuations before matching: per C/GLSL preprocessor rules
1452
- // `\` immediately before a newline removes both characters and stitches
1453
- // the next physical line onto this one. The regex's value group rejects
1454
- // newlines, so without folding any multi-line directive (`#define X a \\\n + b`)
1455
- // would NO MATCH and registration would silently fail.
1456
- var folded = directive.replace(Lexer._lineContinuationReg, "");
1457
- var m = Lexer._defineDirectiveReg.exec(folded);
1458
- if (!m) return;
1459
- var name = m[1];
1460
- var paramsStr = m[3];
1461
- var valueRaw = m[4];
1462
- var params = paramsStr ? paramsStr.split(",").map(function(p) {
1553
+ // Register a `#define` directive. Both AST and legacy paths funnel through
1554
+ // here using ranges already extracted by `_peekMacroDefine` / the value
1555
+ // state machine — no re-parsing the directive text.
1556
+ _proto._registerMacroDefine = function _registerMacroDefine(name, paramsLexeme, valueStart, valueEnd) {
1557
+ var params = paramsLexeme ? paramsLexeme.slice(1, -1) // strip enclosing `(` `)`
1558
+ .split(",").map(function(p) {
1463
1559
  return p.trim();
1464
1560
  }).filter(Boolean) : [];
1465
- var refMatch = valueRaw ? Lexer._referenceReg.exec(valueRaw.trim()) : null;
1561
+ // Dedup key: normalized value text (same lexical view the token-stream
1562
+ // path sees — comments stripped, whitespace collapsed). Two `#define`s
1563
+ // with the same name but different value text produce different keys,
1564
+ // so disjoint-branch entries stay separate.
1565
+ var dedupKey = (paramsLexeme != null ? paramsLexeme : "") + "=" + Lexer._normalizeValueText(this._source, valueStart, valueEnd);
1466
1566
  var info = {
1467
- isFunction: m[2] !== undefined,
1468
- name: name,
1567
+ isFunction: paramsLexeme !== undefined,
1469
1568
  params: params,
1470
- referenceName: refMatch ? refMatch[1] : "",
1569
+ dedupKey: dedupKey,
1471
1570
  branch: this._branchStack.length === 0 ? EMPTY_BRANCH : this._branchStack.slice()
1472
1571
  };
1473
1572
  var arr = this.macroDefineList[name];
@@ -1477,14 +1576,11 @@ var GSErrorName = /*#__PURE__*/ function(GSErrorName) {
1477
1576
  ];
1478
1577
  return;
1479
1578
  }
1480
- // Skip duplicates from re-includes / re-definitions in the same `#ifdef`
1481
- // branch. Different branches → different entries (the call-site filter
1482
- // picks the visible one); same branch + same shape → drop.
1579
+ // Same key + same branch → duplicate (re-include). Different branches stay
1580
+ // separate so the visibility filter picks the right entry at each call site.
1483
1581
  for(var i = 0, n = arr.length; i < n; i++){
1484
1582
  var e = arr[i];
1485
- if (e.isFunction === info.isFunction && e.referenceName === info.referenceName && e.params.length === info.params.length && e.params.every(function(p, idx) {
1486
- return p === info.params[idx];
1487
- }) && Lexer.sameBranch(e.branch, info.branch)) return;
1583
+ if (e.dedupKey === dedupKey && Lexer.sameBranch(e.branch, info.branch)) return;
1488
1584
  }
1489
1585
  arr.push(info);
1490
1586
  };
@@ -1514,6 +1610,8 @@ var GSErrorName = /*#__PURE__*/ function(GSErrorName) {
1514
1610
  // Bypass MACRO_CALL classification for this one word. Used for the name of
1515
1611
  // a `#define` directive so redefining a known macro still yields a declarer ID.
1516
1612
  this._macroDefineExpectsNameToken = false;
1613
+ this._currentMacroName = word;
1614
+ this._currentMacroParamsLexeme = undefined;
1517
1615
  // C99 §6.10.3/3: the macro is function-like only when `(` appears *immediately*
1518
1616
  // after the name (no intervening whitespace). `#define FOO (1+2)` is
1519
1617
  // object-like with value `(1+2)`, not a function-like macro `FOO()` with
@@ -1521,6 +1619,10 @@ var GSErrorName = /*#__PURE__*/ function(GSErrorName) {
1521
1619
  // on the next `scanToken`, so a space-separated `(` stays part of the value.
1522
1620
  if (this._inMacroDefineValue) {
1523
1621
  this._macroDefineExpectsParamsToken = this.getCurChar() === "(";
1622
+ // For object-like macros, the value begins where the cursor is now
1623
+ // (after the name, before whitespace skipping). Function-like macros
1624
+ // overwrite this in `_scanMacroDefineParams` after capturing `(...)`.
1625
+ this._currentMacroValueStart = this._currentIndex;
1524
1626
  }
1525
1627
  token.set(ETokenType.ID, word, start);
1526
1628
  } else if (this._isVisibleMacro(word)) {
@@ -1660,6 +1762,12 @@ var GSErrorName = /*#__PURE__*/ function(GSErrorName) {
1660
1762
  }
1661
1763
  return true;
1662
1764
  };
1765
+ /** GLSL type / qualifier keywords that aren't expression starters when standing alone.
1766
+ * `true` / `false` are excluded — they're `primary_expression` literals.
1767
+ * `#`-prefixed entries are excluded — they're preprocessor directive names. */ Lexer._isNonExpressionLeadingKeyword = function _isNonExpressionLeadingKeyword(lexeme) {
1768
+ var kw = Lexer._lexemeTable[lexeme];
1769
+ return kw !== undefined && kw !== Keyword.True && kw !== Keyword.False && !lexeme.startsWith("#");
1770
+ };
1663
1771
  /**
1664
1772
  * If `i` points at a `\` immediately followed by a newline (`\n`, `\r`, or
1665
1773
  * `\r\n`), return the index just past the pair (a single atom in the C/GLSL
@@ -1667,8 +1775,7 @@ var GSErrorName = /*#__PURE__*/ function(GSErrorName) {
1667
1775
  * source of truth for line-continuation detection — callers along the
1668
1776
  * directive-scanning path (`_skipNonSemantic`, `_scanUtilBreakLine`,
1669
1777
  * `_scanMacroDefineParams`) all delegate here so the rule stays in one
1670
- * place. The string-level `_lineContinuationReg` covers the regex path
1671
- * used by `_registerMacroDefine` for one-shot folding.
1778
+ * place.
1672
1779
  */ Lexer._skipLineContinuation = function _skipLineContinuation(src, i, len) {
1673
1780
  if (src.charCodeAt(i) !== 92 /* '\\' */ || i + 1 >= len) return i;
1674
1781
  var n = src.charCodeAt(i + 1);
@@ -1711,6 +1818,34 @@ var GSErrorName = /*#__PURE__*/ function(GSErrorName) {
1711
1818
  }
1712
1819
  return i;
1713
1820
  };
1821
+ /** Render a `[start, end)` value range as space-separated significant chars,
1822
+ * using the same comment / line-continuation rules `_skipNonSemantic`
1823
+ * applies on the token-stream path. Used as the dedup key body. */ Lexer._normalizeValueText = function _normalizeValueText(src, start, end) {
1824
+ var out = "";
1825
+ var i = start;
1826
+ var pendingSpace = false;
1827
+ while(i < end){
1828
+ var skipped = Lexer._skipNonSemantic(src, i, end);
1829
+ if (skipped !== i) {
1830
+ pendingSpace = out.length > 0;
1831
+ i = skipped;
1832
+ continue;
1833
+ }
1834
+ var c = src.charCodeAt(i);
1835
+ if (c === 32 || c === 9) {
1836
+ pendingSpace = out.length > 0;
1837
+ i++;
1838
+ continue;
1839
+ }
1840
+ if (pendingSpace) {
1841
+ out += " ";
1842
+ pendingSpace = false;
1843
+ }
1844
+ out += src[i];
1845
+ i++;
1846
+ }
1847
+ return out;
1848
+ };
1714
1849
  return Lexer;
1715
1850
  }(BaseLexer);
1716
1851
  Lexer._lexemeTable = {
@@ -1787,12 +1922,6 @@ Lexer._lexemeTable = {
1787
1922
  "#endif": Keyword.MACRO_ENDIF,
1788
1923
  "#undef": Keyword.MACRO_UNDEF
1789
1924
  };
1790
- // Single source of truth for `#define` parsing — fed from both AST and legacy paths.
1791
- Lexer._defineDirectiveReg = /^\s*#define\s+(\w+)[ ]*(\(([^)]*)\))?(?:[ \t]+([^\n\r]*?))?\s*$/;
1792
- // Bare identifier or call form only — mixed-operator values reject.
1793
- Lexer._referenceReg = /^([a-zA-Z_]\w*)(?:\s*\(.*\))?$/;
1794
- // C preprocessor line continuation.
1795
- Lexer._lineContinuationReg = /\\(?:\r\n|\n|\r)/g;
1796
1925
  // Synthetic `__if_<n>` per `#if` — polarity flip makes `#else` mutually exclusive in `isVisibleFrom`.
1797
1926
  Lexer._ifCounter = 0;
1798
1927
 
@@ -2307,7 +2436,7 @@ var SymbolTable = /*#__PURE__*/ function() {
2307
2436
  for(var i = 0, n = entry.length; i < n; i++){
2308
2437
  if (entry[i].isInMacroBranch) continue;
2309
2438
  if (entry[i].equal(symbol)) {
2310
- console.warn("Replace symbol:", symbol.ident);
2439
+ engineCore.Logger.warn("Replace symbol:", symbol.ident);
2311
2440
  entry[i] = symbol;
2312
2441
  return;
2313
2442
  }
@@ -2540,7 +2669,7 @@ var SemanticAnalyzer = /*#__PURE__*/ function() {
2540
2669
  console.error(message);
2541
2670
  };
2542
2671
  _proto.reportWarning = function reportWarning(loc, message) {
2543
- console.warn(new GSError(GSErrorName.CompilationWarn, message, loc, ShaderCompiler._processingPassText).toString());
2672
+ engineCore.Logger.warn(new GSError(GSErrorName.CompilationWarn, message, loc, ShaderCompiler._processingPassText).toString());
2544
2673
  };
2545
2674
  _create_class(SemanticAnalyzer, [
2546
2675
  {
@@ -3995,34 +4124,13 @@ var TreeNode = /*#__PURE__*/ function() {
3995
4124
  var child = this.children[0];
3996
4125
  var referenceGlobalSymbolNames = this.referenceGlobalSymbolNames;
3997
4126
  var symbols = this._symbols;
3998
- var lookupSymbol = SemanticAnalyzer._lookupSymbol;
3999
- var needFindNames;
4000
- // FXAA-style cross-arm shadowing: same name is a macro in one
4001
- // preprocessor arm and a variable in the mutually-exclusive arm.
4002
- // At a MACRO_CALL use site, also probe the macro name itself so
4003
- // the sibling-arm declaration is marked as referenced and codegen
4004
- // keeps it. Grammar half of the fix is in 87cb2b5f0.
4005
- var macroNameAsVarLookup = null;
4006
- if (_instanceof(child, BaseToken)) {
4007
- needFindNames = [
4008
- child.lexeme
4009
- ];
4010
- } else {
4011
- var callSymbol = child;
4012
- needFindNames = callSymbol.referenceSymbolNames;
4013
- var macroName = callSymbol.macroName;
4014
- if (macroName && needFindNames.indexOf(macroName) === -1) {
4015
- needFindNames = needFindNames.concat(macroName);
4016
- macroNameAsVarLookup = macroName;
4017
- }
4018
- }
4127
+ // Real references — every name must resolve; miss is an authoring error.
4128
+ var needFindNames = _instanceof(child, BaseToken) ? [
4129
+ child.lexeme
4130
+ ] : child.referenceSymbolNames;
4019
4131
  for(var i = 0; i < needFindNames.length; i++){
4020
4132
  var name = needFindNames[i];
4021
- // `macroDefineList` short-circuit; bypass for the macro name itself
4022
- // so cross-arm shadowing can resolve the sibling-arm declaration.
4023
- if (sa.macroDefineList[name] && name !== macroNameAsVarLookup) {
4024
- continue;
4025
- }
4133
+ if (sa.macroDefineList[name]) continue;
4026
4134
  // only `macro_call` CFG can reference fnSymbols, others fnSymbols are referenced in `function_call_generic` CFG
4027
4135
  if (!_instanceof(child, BaseToken) && BuiltinFunction.isExist(name)) {
4028
4136
  continue;
@@ -4032,29 +4140,24 @@ var TreeNode = /*#__PURE__*/ function() {
4032
4140
  this.typeInfo = builtinVar.type;
4033
4141
  continue;
4034
4142
  }
4035
- lookupSymbol.set(name, ESymbolType.Any);
4036
- sa.symbolTableStack.lookupAll(lookupSymbol, true, symbols);
4037
- if (!symbols.length) ; else {
4038
- // Expression-style macros have their own value AST; its real type isn't
4039
- // the type of any single `referenceSymbolNames` entry (`v` in `v.v_uv`
4040
- // is a `Varyings` struct but the macro call site's type should be the
4041
- // member type). Skip type inference for those and keep TypeAny.
4042
- if (_instanceof(child, BaseToken) || !child.hasAstValue) {
4043
- var _symbols__dataType;
4044
- this.typeInfo = (_symbols__dataType = symbols[0].dataType) == null ? void 0 : _symbols__dataType.type;
4045
- }
4046
- var currentScopeSymbol = sa.symbolTableStack.scope.getSymbol(lookupSymbol, true);
4047
- if (currentScopeSymbol) {
4048
- if ((_instanceof(currentScopeSymbol, FnSymbol) || currentScopeSymbol.isGlobalVariable) && referenceGlobalSymbolNames.indexOf(name) === -1) {
4049
- referenceGlobalSymbolNames.push(name);
4050
- }
4051
- } else if (symbols.some(function(s) {
4052
- return _instanceof(s, FnSymbol) || s.isGlobalVariable;
4053
- }) && referenceGlobalSymbolNames.indexOf(name) === -1) {
4054
- referenceGlobalSymbolNames.push(name);
4055
- }
4143
+ var hit = VariableIdentifier._lookupAndMarkGlobalReference(sa, name, symbols, referenceGlobalSymbolNames, this.location);
4144
+ // Expression-style macros have their own value AST; its real type isn't
4145
+ // the type of any single `referenceSymbolNames` entry (`v` in `v.v_uv`
4146
+ // is a `Varyings` struct but the macro call site's type should be the
4147
+ // member type). Skip type inference for those and keep TypeAny.
4148
+ if (hit && (_instanceof(child, BaseToken) || !child.hasAstValue)) {
4149
+ var _symbols__dataType;
4150
+ this.typeInfo = (_symbols__dataType = symbols[0].dataType) == null ? void 0 : _symbols__dataType.type;
4056
4151
  }
4057
4152
  }
4153
+ // FXAA-style cross-arm shadowing: at a MACRO_CALL use site, silently
4154
+ // probe the macro name itself so any sibling-arm `var` declaration is
4155
+ // marked as referenced and codegen keeps it. Miss is the common
4156
+ // single-arm case — no warning, no type inference. Grammar half of the
4157
+ // cross-arm fix is in 87cb2b5f0.
4158
+ if (!_instanceof(child, BaseToken)) {
4159
+ VariableIdentifier._probeCrossArmShadowing(sa, child, needFindNames, symbols, referenceGlobalSymbolNames);
4160
+ }
4058
4161
  };
4059
4162
  _proto.codeGen = function codeGen(visitor) {
4060
4163
  return this.setCache(visitor.visitVariableIdentifier(this));
@@ -4067,6 +4170,40 @@ var TreeNode = /*#__PURE__*/ function() {
4067
4170
  return child.codeGen(visitor);
4068
4171
  }
4069
4172
  };
4173
+ /** Run the cross-arm shadowing probe for a MACRO_CALL site. No-op when the
4174
+ * probe isn't meaningful: no macro name, name already resolved as a real
4175
+ * reference, or name is a builtin (builtins can't be shadowed by a
4176
+ * sibling-arm decl). */ VariableIdentifier._probeCrossArmShadowing = function _probeCrossArmShadowing(sa, child, needFindNames, symbols, referenceGlobalSymbolNames) {
4177
+ var macroName = child.macroName;
4178
+ if (!macroName) return;
4179
+ if (needFindNames.indexOf(macroName) !== -1) return; // already looked up as a real reference
4180
+ if (BuiltinFunction.isExist(macroName) || BuiltinVariable.getVar(macroName)) return; // builtins can't be shadowed
4181
+ VariableIdentifier._lookupAndMarkGlobalReference(sa, macroName, symbols, referenceGlobalSymbolNames, null);
4182
+ };
4183
+ /** Look up `name` in the symbol stack and, if a global var/fn declaration
4184
+ * exists, push it into `referenceGlobalSymbolNames`. Returns `true` iff
4185
+ * the lookup hit (caller can then derive type info). When `missWarnLoc`
4186
+ * is non-null, a miss reports a "declared before used" warning; pass
4187
+ * `null` for silent probes (e.g. FXAA-style cross-arm shadowing).
4188
+ *
4189
+ * Mutation contract: `symbols` is used as scratch storage — `lookupAll`
4190
+ * clears and refills it. On hit, the caller may read `symbols[0]` for
4191
+ * type info before the next call overwrites the contents. */ VariableIdentifier._lookupAndMarkGlobalReference = function _lookupAndMarkGlobalReference(sa, name, symbols, referenceGlobalSymbolNames, missWarnLoc) {
4192
+ var lookupSymbol = SemanticAnalyzer._lookupSymbol;
4193
+ lookupSymbol.set(name, ESymbolType.Any);
4194
+ sa.symbolTableStack.lookupAll(lookupSymbol, true, symbols);
4195
+ if (!symbols.length) {
4196
+ return false;
4197
+ }
4198
+ var currentScopeSymbol = sa.symbolTableStack.scope.getSymbol(lookupSymbol, true);
4199
+ var isGlobal = currentScopeSymbol ? _instanceof(currentScopeSymbol, FnSymbol) || currentScopeSymbol.isGlobalVariable : symbols.some(function(s) {
4200
+ return _instanceof(s, FnSymbol) || s.isGlobalVariable;
4201
+ });
4202
+ if (isGlobal && referenceGlobalSymbolNames.indexOf(name) === -1) {
4203
+ referenceGlobalSymbolNames.push(name);
4204
+ }
4205
+ return true;
4206
+ };
4070
4207
  return VariableIdentifier;
4071
4208
  }(TreeNode);
4072
4209
  ASTNode.VariableIdentifier = VariableIdentifier;
@@ -4323,18 +4460,16 @@ var TreeNode = /*#__PURE__*/ function() {
4323
4460
  MacroBranch = __decorate([
4324
4461
  ASTNodeDecorator(NoneTerminal.macro_branch)
4325
4462
  ], MacroBranch);
4326
- var MacroCallSymbol = /*#__PURE__*/ function(TreeNode) {
4327
- _inherits(MacroCallSymbol, TreeNode);
4463
+ var MacroCallSymbol = /*#__PURE__*/ function(TreeNode1) {
4464
+ _inherits(MacroCallSymbol, TreeNode1);
4328
4465
  function MacroCallSymbol() {
4329
4466
  var _this;
4330
- _this = TreeNode.apply(this, arguments) || this, _this.referenceSymbolNames = [], /** True iff every `MacroDefineInfo` visible from this call site's branch
4467
+ _this = TreeNode1.apply(this, arguments) || this, _this.referenceSymbolNames = [], /** True iff every `MacroDefineInfo` visible from this call site's branch
4331
4468
  * has a `valueAst` (i.e. was parsed via the `macro_define` CFG rule).
4332
4469
  * Call sites use this to skip naive type inference: an AST-form macro
4333
4470
  * drives the call site's type through its own value subtree, not through
4334
4471
  * a root of `referenceSymbolNames`. Mixed forms across branches → false,
4335
- * fall back to legacy inference. */ _this.hasAstValue = false, /** True when the macro is defined as function-like (`#define NAME(params) …`).
4336
- * Used by `MacroCallFunction` codegen to pick between the two call shapes
4337
- * — object-like-macro-as-function-name vs true function-like macro. */ _this.isFunctionLikeMacro = false;
4472
+ * fall back to legacy inference. */ _this.hasAstValue = false, /** `#define NAME(params) …` form — drives function-like vs object-like codegen. */ _this.isFunctionLikeMacro = false, /** Every visible replacement is a non-builtin identifier — assume user fn alias. */ _this.aliasesNonBuiltinIdent = false;
4338
4473
  return _this;
4339
4474
  }
4340
4475
  var _proto = MacroCallSymbol.prototype;
@@ -4342,6 +4477,7 @@ var TreeNode = /*#__PURE__*/ function() {
4342
4477
  this.referenceSymbolNames.length = 0;
4343
4478
  this.hasAstValue = false;
4344
4479
  this.isFunctionLikeMacro = false;
4480
+ this.aliasesNonBuiltinIdent = false;
4345
4481
  };
4346
4482
  _proto.semanticAnalyze = function semanticAnalyze(sa) {
4347
4483
  var nameToken = this.children[0];
@@ -4359,6 +4495,7 @@ var TreeNode = /*#__PURE__*/ function() {
4359
4495
  var visibleCount = 0;
4360
4496
  var allAst = true;
4361
4497
  var isFn = false;
4498
+ var allAliasNonBuiltinIdent = true;
4362
4499
  if (defList) {
4363
4500
  for(var i = 0, n = defList.length; i < n; i++){
4364
4501
  var info = defList[i];
@@ -4366,8 +4503,30 @@ var TreeNode = /*#__PURE__*/ function() {
4366
4503
  visibleCount++;
4367
4504
  if (info.valueAst == null) allAst = false;
4368
4505
  if (info.isFunction) isFn = true;
4369
- var ref = info.referenceName;
4370
- if (ref && info.params.indexOf(ref) === -1 && refs.indexOf(ref) === -1) refs.push(ref);
4506
+ // Harvest references from the value AST. Legacy-form macros (no
4507
+ // `valueAst`) hold non-expression token sequences with no user
4508
+ // identifiers, so nothing to collect.
4509
+ if (info.valueAst) {
4510
+ MacroCallSymbol._collectIdentifierRefs(info.valueAst, info.params, refs);
4511
+ }
4512
+ // aliasesNonBuiltinIdent: macro replacement is a single non-builtin
4513
+ // identifier — best-effort proxy for "this macro call site aliases a
4514
+ // user fn", since uniform/const aliases would surface as a GLSL
4515
+ // compile error later anyway. Keyword replacements (`vec3`, `mat4`)
4516
+ // reach here with `valueAst === undefined` (opaque path), so they
4517
+ // automatically fail without an explicit keyword guard.
4518
+ if (info.isFunction || !info.valueAst) {
4519
+ allAliasNonBuiltinIdent = false;
4520
+ } else {
4521
+ var leadingIdent = ParserUtils.unwrapBareIdentifier(info.valueAst, {
4522
+ allowParens: false
4523
+ });
4524
+ var leadingChild = leadingIdent == null ? void 0 : leadingIdent.children[0];
4525
+ var leadingId = _instanceof(leadingChild, BaseToken) ? leadingChild.lexeme : undefined;
4526
+ if (!leadingId || BuiltinFunction.isExist(leadingId)) {
4527
+ allAliasNonBuiltinIdent = false;
4528
+ }
4529
+ }
4371
4530
  }
4372
4531
  }
4373
4532
  // Require *every* visible entry to be AST-form before taking the AST
@@ -4376,6 +4535,25 @@ var TreeNode = /*#__PURE__*/ function() {
4376
4535
  // instead of polluting the call site with TypeAny.
4377
4536
  this.hasAstValue = visibleCount > 0 && allAst;
4378
4537
  this.isFunctionLikeMacro = isFn;
4538
+ this.aliasesNonBuiltinIdent = visibleCount > 0 && allAliasNonBuiltinIdent;
4539
+ };
4540
+ /** Push every leaf `VariableIdentifier`'s lexeme into `out`, skipping
4541
+ * function-like parameter names (local to the macro, not call-site refs)
4542
+ * and duplicates. */ MacroCallSymbol._collectIdentifierRefs = function _collectIdentifierRefs(node, params, out) {
4543
+ if (_instanceof(node, VariableIdentifier)) {
4544
+ var child = node.children[0];
4545
+ if (_instanceof(child, BaseToken)) {
4546
+ var name = child.lexeme;
4547
+ if (params.indexOf(name) === -1 && out.indexOf(name) === -1) out.push(name);
4548
+ }
4549
+ return;
4550
+ }
4551
+ var children = node.children;
4552
+ if (!children) return;
4553
+ for(var i = 0, n = children.length; i < n; i++){
4554
+ var c = children[i];
4555
+ if (_instanceof(c, TreeNode)) MacroCallSymbol._collectIdentifierRefs(c, params, out);
4556
+ }
4379
4557
  };
4380
4558
  return MacroCallSymbol;
4381
4559
  }(TreeNode);
@@ -4387,7 +4565,7 @@ var TreeNode = /*#__PURE__*/ function() {
4387
4565
  _inherits(MacroCallFunction, TreeNode);
4388
4566
  function MacroCallFunction() {
4389
4567
  var _this;
4390
- _this = TreeNode.apply(this, arguments) || this, _this.referenceSymbolNames = [], _this.macroName = "", _this.hasAstValue = false, _this.isFunctionLikeMacro = false;
4568
+ _this = TreeNode.apply(this, arguments) || this, _this.referenceSymbolNames = [], _this.macroName = "", _this.hasAstValue = false, _this.isFunctionLikeMacro = false, _this.aliasesNonBuiltinIdent = false;
4391
4569
  return _this;
4392
4570
  }
4393
4571
  var _proto = MacroCallFunction.prototype;
@@ -4396,6 +4574,7 @@ var TreeNode = /*#__PURE__*/ function() {
4396
4574
  this.macroName = "";
4397
4575
  this.hasAstValue = false;
4398
4576
  this.isFunctionLikeMacro = false;
4577
+ this.aliasesNonBuiltinIdent = false;
4399
4578
  };
4400
4579
  _proto.semanticAnalyze = function semanticAnalyze(sa) {
4401
4580
  var child = this.children[0];
@@ -4403,6 +4582,7 @@ var TreeNode = /*#__PURE__*/ function() {
4403
4582
  this.macroName = child.macroName;
4404
4583
  this.hasAstValue = child.hasAstValue;
4405
4584
  this.isFunctionLikeMacro = child.isFunctionLikeMacro;
4585
+ this.aliasesNonBuiltinIdent = child.aliasesNonBuiltinIdent;
4406
4586
  };
4407
4587
  _proto.codeGen = function codeGen(visitor) {
4408
4588
  return this.setCache(visitor.visitMacroCallFunction(this));
@@ -4438,7 +4618,10 @@ var TreeNode = /*#__PURE__*/ function() {
4438
4618
  params = ParserUtils.parseMacroParamList(children[2].lexeme);
4439
4619
  valueIdx = 3;
4440
4620
  }
4441
- if (_instanceof(children[valueIdx], AssignmentExpression)) {
4621
+ // Grammar's macro_define value is `expression` (not `assignment_expression`),
4622
+ // so the value child is always an `Expression` wrapper — even for a single
4623
+ // assignment-expression value, which appears as `Expression > AssignmentExpression`.
4624
+ if (_instanceof(children[valueIdx], Expression)) {
4442
4625
  this.valueExpression = children[valueIdx];
4443
4626
  }
4444
4627
  // The Lexer already registered a regex-derived entry for this directive.
@@ -4461,14 +4644,14 @@ var TreeNode = /*#__PURE__*/ function() {
4461
4644
  if (upgradable) {
4462
4645
  upgradable.valueAst = this.valueExpression;
4463
4646
  } else {
4464
- // No matching preprocessor entry (e.g. the lexer was fed the directive
4465
- // directly without a `Preprocessor.parse` pass). Push a fresh entry.
4647
+ // No matching preprocessor entry (lexer fed directly, no
4648
+ // `Preprocessor.parse` pass). Synthetic key based on shape is enough
4649
+ // — this path's only dedup unit is this AST-direct registration.
4466
4650
  var info = {
4467
4651
  isFunction: this.isFunction,
4468
- name: this.macroName,
4469
4652
  params: params,
4470
4653
  valueAst: this.valueExpression,
4471
- referenceName: "",
4654
+ dedupKey: this.macroName + "#ast/" + (this.isFunction ? params.join(",") : ""),
4472
4655
  branch: definingBranch
4473
4656
  };
4474
4657
  if (entries) entries.push(info);
@@ -4476,6 +4659,13 @@ var TreeNode = /*#__PURE__*/ function() {
4476
4659
  info
4477
4660
  ];
4478
4661
  }
4662
+ // Close the form-param scope pushed at `MACRO_DEFINE_PARAMS` shift. By
4663
+ // the time this reduce fires, every `VariableIdentifier` inside the
4664
+ // value expression has already resolved against the params. Object-like
4665
+ // macros never pushed a scope, so nothing to pop.
4666
+ if (this.isFunction) {
4667
+ sa.popScope();
4668
+ }
4479
4669
  };
4480
4670
  _proto.codeGen = function codeGen(visitor) {
4481
4671
  return this.setCache(visitor.visitMacroDefine(this));
@@ -4700,30 +4890,24 @@ var TempArray = /*#__PURE__*/ function() {
4700
4890
  if (_instanceof(paramList, ASTNode.FunctionCallParameterList)) {
4701
4891
  var astNodes = paramList.paramNodes;
4702
4892
  var context = VisitorContext.context;
4703
- // `MacroCallFunction` covers two call shapes sharing the same AST:
4704
- // (a) object-like macro whose value is a function name, used as a call —
4705
- // `#define FN foo` + `FN(varyings, …)`. The driver expands `FN` to
4706
- // `foo`, and `foo` is a shader function whose IO-struct params
4707
- // have been flattened. The call site must drop IO-struct args to
4708
- // match the flattened signature — same rule as `visitFunctionCall`.
4709
- // (b) true function-like macro — `#define MAX3(a,b,c) …` + `MAX3(v.x, …)`.
4710
- // The shader compiler doesn't expand the macro; the driver does, and the
4711
- // `#define` fixes the parameter count. Args must be preserved
4712
- // verbatim — a member-access arg like `v.v_uv` unwraps to a root
4713
- // identifier whose type is an IO struct, but dropping the arg
4714
- // would change the macro's arity.
4715
- //
4716
- // `isFunctionLikeMacro` is set by `MacroCallSymbol.semanticAnalyze` from
4717
- // `macroDefineList[name][*].isFunction` and carries the definition shape.
4718
- var params = node.isFunctionLikeMacro ? astNodes : astNodes.filter(function(arg) {
4719
- if (_instanceof(arg, ASTNode.AssignmentExpression)) {
4720
- var variableParam = ParserUtils.unwrapNodeByType(arg, NoneTerminal.variable_identifier);
4721
- if (variableParam && typeof variableParam.typeInfo === "string" && context.getStructRole(variableParam.typeInfo)) {
4722
- return false;
4893
+ // Drop bare IO-struct args only when the macro aliases a user fn (whose
4894
+ // formal was flattened). All other shapes preserve args verbatim.
4895
+ var params;
4896
+ if (node.isFunctionLikeMacro || !node.aliasesNonBuiltinIdent) {
4897
+ params = astNodes;
4898
+ } else {
4899
+ params = astNodes.filter(function(arg) {
4900
+ if (_instanceof(arg, ASTNode.AssignmentExpression)) {
4901
+ var variableParam = ParserUtils.unwrapBareIdentifier(arg, {
4902
+ allowParens: false
4903
+ });
4904
+ if (variableParam && typeof variableParam.typeInfo === "string" && context.getStructRole(variableParam.typeInfo)) {
4905
+ return false;
4906
+ }
4723
4907
  }
4724
- }
4725
- return true;
4726
- });
4908
+ return true;
4909
+ });
4910
+ }
4727
4911
  var paramsCode = "";
4728
4912
  for(var i = 0, length = params.length; i < length; i++){
4729
4913
  var argNode = params[i];
@@ -5928,6 +6112,15 @@ var GLES300Visitor = /*#__PURE__*/ function(GLESVisitor) {
5928
6112
  ShaderInstructionEncoder._DIRECTIVE_RE = /^[ \t]*#[ \t]*(if|ifdef|ifndef|elif|else|endif|define|undef)\b(.*)/;
5929
6113
  ShaderInstructionEncoder._FUNC_MACRO_RE = /^(\w+)\(([^)]*)\)\s*(.*)/;
5930
6114
 
6115
+ var Production = function Production(goal, derivation) {
6116
+ this.goal = goal;
6117
+ this.derivation = derivation;
6118
+ this.id = Production._id++;
6119
+ Production.pool.set(this.id, this);
6120
+ };
6121
+ Production._id = 0;
6122
+ Production.pool = new Map();
6123
+
5931
6124
  var StateItem = /*#__PURE__*/ function() {
5932
6125
  function StateItem(production, position, lookahead) {
5933
6126
  this._needReInfer = true;
@@ -6290,9 +6483,14 @@ var EAction = /*#__PURE__*/ function(EAction) {
6290
6483
  /** Resolve shift-reduce/reduce-reduce conflict detect */ _proto._addAction = function _addAction(table, terminal, action) {
6291
6484
  var exist = table.get(terminal);
6292
6485
  if (exist && !GrammarUtils.isActionEqual(exist, action)) {
6293
- // Resolve dangling else ambiguity
6294
- if (terminal === Keyword.ELSE && exist.action === EAction.Shift && action.action === EAction.Reduce) {
6295
- return;
6486
+ // Known shift-preferred conflicts (see TargetParser.y `%expect 2`).
6487
+ // Enforce shift regardless of the order `_inferNextState` registers
6488
+ // actions: when `exist` is already Shift and `action` is Reduce, keep
6489
+ // `exist` (early-return); the reverse order (`exist` Reduce, `action`
6490
+ // Shift) falls through to `table.set` below and Shift overwrites
6491
+ // Reduce. Order-independent by construction.
6492
+ if (LALR1._isKnownShiftPreferred(terminal, exist, action)) {
6493
+ if (exist.action === EAction.Shift && action.action === EAction.Reduce) return;
6296
6494
  }
6297
6495
  }
6298
6496
  table.set(terminal, action);
@@ -6344,18 +6542,25 @@ var EAction = /*#__PURE__*/ function(EAction) {
6344
6542
  this._firstSetNTStack.pop();
6345
6543
  return firstSet;
6346
6544
  };
6545
+ // Catalog of expected shift/reduce conflicts. Each entry must correspond to
6546
+ // one of TargetParser.y's `%expect`-ed conflicts; any new conflict not in
6547
+ // this list falls through to the verbose `conflict detect` warning so the
6548
+ // grammar/runtime drift is loud rather than silent.
6549
+ // - ELSE: dangling-else, bind to nearest `if`
6550
+ // - '(' + `type_specifier_nonarray → macro_call_symbol`: macro-as-type-alias
6551
+ // (#2974), prefer the expression-position `macro_call_function` over the
6552
+ // type-position reduce
6553
+ LALR1._isKnownShiftPreferred = function _isKnownShiftPreferred(terminal, exist, action) {
6554
+ if (terminal === Keyword.ELSE) return true;
6555
+ if (terminal !== ETokenType.LEFT_PAREN) return false;
6556
+ var reduce = exist.action === EAction.Reduce ? exist : action.action === EAction.Reduce ? action : null;
6557
+ if (!reduce) return false;
6558
+ var prod = Production.pool.get(reduce.target);
6559
+ return !!prod && prod.goal === NoneTerminal.type_specifier_nonarray && prod.derivation.length === 1 && prod.derivation[0] === NoneTerminal.macro_call_symbol;
6560
+ };
6347
6561
  return LALR1;
6348
6562
  }();
6349
6563
 
6350
- var Production = function Production(goal, derivation) {
6351
- this.goal = goal;
6352
- this.derivation = derivation;
6353
- this.id = Production._id++;
6354
- Production.pool.set(this.id, this);
6355
- };
6356
- Production._id = 0;
6357
- Production.pool = new Map();
6358
-
6359
6564
  var Grammar = /*#__PURE__*/ function() {
6360
6565
  function Grammar(start, productions) {
6361
6566
  this.startSymbol = start;
@@ -6425,14 +6630,14 @@ var productionAndRules = [].concat(GrammarUtils.createProductionWithOptions(None
6425
6630
  [
6426
6631
  Keyword.MACRO_DEFINE,
6427
6632
  ETokenType.ID,
6428
- NoneTerminal.assignment_expression,
6633
+ NoneTerminal.expression,
6429
6634
  Keyword.MACRO_DEFINE_END
6430
6635
  ],
6431
6636
  [
6432
6637
  Keyword.MACRO_DEFINE,
6433
6638
  ETokenType.ID,
6434
6639
  Keyword.MACRO_DEFINE_PARAMS,
6435
- NoneTerminal.assignment_expression,
6640
+ NoneTerminal.expression,
6436
6641
  Keyword.MACRO_DEFINE_END
6437
6642
  ]
6438
6643
  ], ASTNode.MacroDefine.pool), GrammarUtils.createProductionWithOptions(NoneTerminal.macro_call_symbol, [
@@ -7799,6 +8004,19 @@ var addTranslationRule = function(sa) {
7799
8004
  var actionInfo = this.stateActionTable.get(token.type);
7800
8005
  if ((actionInfo == null ? void 0 : actionInfo.action) === EAction.Shift) {
7801
8006
  traceBackStack.push(token, actionInfo.target);
8007
+ // Function-like `#define` form params live in a scope wrapping the
8008
+ // value AST, mirroring how `function_header` opens a scope for GLSL
8009
+ // function parameters. Push on shift of `MACRO_DEFINE_PARAMS`; the
8010
+ // matching `popScope` runs when `MacroDefine.semanticAnalyze` reduces
8011
+ // the production (only the function-like alternative needs it, and
8012
+ // it knows that from its own children).
8013
+ if (token.type === Keyword.MACRO_DEFINE_PARAMS) {
8014
+ sematicAnalyzer.pushScope();
8015
+ for(var _iterator = _create_for_of_iterator_helper_loose(ParserUtils.parseMacroParamList(token.lexeme)), _step; !(_step = _iterator()).done;){
8016
+ var p = _step.value;
8017
+ sematicAnalyzer.symbolTableStack.insert(new SymbolInfo(p, ESymbolType.VAR));
8018
+ }
8019
+ }
7802
8020
  nextToken = tokens.next();
7803
8021
  } else if ((actionInfo == null ? void 0 : actionInfo.action) === EAction.Accept) {
7804
8022
  sematicAnalyzer.acceptRule == null ? void 0 : sematicAnalyzer.acceptRule.call(sematicAnalyzer, sematicAnalyzer);
@@ -8706,7 +8924,7 @@ ShaderCompiler._shaderPositionPool = ShaderCompilerUtils.createObjectPool(Shader
8706
8924
  ShaderCompiler._shaderRangePool = ShaderCompilerUtils.createObjectPool(ShaderRange);
8707
8925
 
8708
8926
  //@ts-ignore
8709
- var version = "2.0.0-alpha.32";
8927
+ var version = "2.0.0-alpha.34";
8710
8928
  var mode = "Release";
8711
8929
  console.log("Galacean Engine Shader Compiler Version: " + version + " | Mode: " + mode);
8712
8930