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

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