@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.
@@ -926,36 +926,46 @@ var ParserUtils = /*#__PURE__*/ function() {
926
926
  }).filter(Boolean);
927
927
  };
928
928
  /**
929
- * Return the lexeme of `expr` when it is (transitively) a single bare identifier
930
- * wrapped in primary/postfix expression nodes, e.g. `o` → "o". Returns null for
931
- * compound expressions (`o.x`, `foo(..)`, `arr[0]`, …). Useful for callers that
932
- * want to apply a substitution rule only at the root of an expression, never on
933
- * swizzles or nested member access.
934
- */ ParserUtils.extractDirectIdentLexeme = function extractDirectIdentLexeme(expr) {
935
- var cur = expr;
936
- while(cur){
937
- if (_instanceof(cur, ASTNode.VariableIdentifier)) {
938
- var child = cur.children[0];
939
- return _instanceof(child, BaseToken) ? child.lexeme : null;
940
- }
941
- // `( expression )` form on PrimaryExpression — descend through the
942
- // wrapped Expression so `(v)` resolves to `v`. All other compound
943
- // forms (member access, function call, comma, …) bail out.
944
- if (_instanceof(cur, ASTNode.PrimaryExpression) && cur.children.length === 3) {
945
- cur = cur.children[1];
929
+ * Walk single-child precedence-chain wrappers down to a `VariableIdentifier`,
930
+ * returning the leaf node (or `undefined` for any compound expression).
931
+ *
932
+ * `allowParens` chooses between two callers' needs:
933
+ * - `true` — descend through `( expression )` form, so `(v)` resolves to
934
+ * `v`. Use when the caller substitutes at the expression root
935
+ * (aliasing, renaming); user-written parens carry no extra meaning.
936
+ * - `false` — any 3-child `PrimaryExpression` aborts; `(v)` stays
937
+ * compound. Use when the caller treats the unwrapped node as a single
938
+ * token (IO-struct arg drop, alias detection); user-written parens
939
+ * must not collapse.
940
+ */ ParserUtils.unwrapBareIdentifier = function unwrapBareIdentifier(node, options) {
941
+ var cur = node;
942
+ while(true){
943
+ if (_instanceof(cur, ASTNode.VariableIdentifier)) return cur;
944
+ if (options.allowParens && _instanceof(cur, ASTNode.PrimaryExpression) && cur.children.length === 3) {
945
+ var inner = cur.children[1];
946
+ if (!_instanceof(inner, TreeNode)) return undefined;
947
+ cur = inner;
946
948
  continue;
947
949
  }
948
- // Any precedence-chain wrapper with a single child forwards through.
949
- // Covers PostfixExpression / PrimaryExpression(len=1) / Assignment /
950
- // Conditional / LogicalOr / … all the way down — including the verbose
951
- // build's full chain and the release build's elision-flattened chain.
952
950
  if (_instanceof(cur, ASTNode.ExpressionAstNode) && cur.children.length === 1) {
953
- cur = cur.children[0];
951
+ var child = cur.children[0];
952
+ if (!_instanceof(child, TreeNode)) return undefined;
953
+ cur = child;
954
954
  continue;
955
955
  }
956
- return null;
956
+ return undefined;
957
957
  }
958
- return null;
958
+ };
959
+ /**
960
+ * Lexeme variant of `unwrapBareIdentifier({ allowParens: true })` for callers
961
+ * that already work with strings. Returns `null` for compound expressions.
962
+ */ ParserUtils.extractDirectIdentLexeme = function extractDirectIdentLexeme(expr) {
963
+ var ident = ParserUtils.unwrapBareIdentifier(expr, {
964
+ allowParens: true
965
+ });
966
+ if (!ident) return null;
967
+ var child = ident.children[0];
968
+ return _instanceof(child, BaseToken) ? child.lexeme : null;
959
969
  };
960
970
  /**
961
971
  * Check if type `tb` is compatible with type `ta`.
@@ -1228,7 +1238,7 @@ var ParserUtils = /*#__PURE__*/ function() {
1228
1238
  _inherits(Lexer, BaseLexer1);
1229
1239
  function Lexer(source, macroDefineList) {
1230
1240
  var _this;
1231
- _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;
1241
+ _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;
1232
1242
  return _this;
1233
1243
  }
1234
1244
  var _proto = Lexer.prototype;
@@ -1624,11 +1634,12 @@ var ParserUtils = /*#__PURE__*/ function() {
1624
1634
  var token = BaseToken.pool.get();
1625
1635
  var word = buffer.join("");
1626
1636
  if (word === "#define") {
1627
- // Mark the directive's start offset (the `#`) so the regex-based
1628
- // registrar can later slice out the full directive text from `_source`.
1629
- // Two paths share the same registrar — single source of truth.
1630
- this._macroDefineDirectiveStart = start.index;
1631
- if (this._defineHasValue()) {
1637
+ var peek = this._peekMacroDefine();
1638
+ if (peek && peek.isExpression) {
1639
+ // AST path: the value will be tokenized by the lexer's `_inMacroDefineValue`
1640
+ // state machine and re-parsed by the expression grammar. State machine
1641
+ // fills `_currentMacroName` / `_currentMacroParamsLexeme` / `_currentMacroValueStart`
1642
+ // as it walks the value; `_emitMacroDefineEnd` consumes them.
1632
1643
  this._inMacroDefineValue = true;
1633
1644
  // The next word is the macro name: force ID type even if a prior `#define`
1634
1645
  // has already registered the name into `macroDefineList` (redefinition or
@@ -1636,10 +1647,13 @@ var ParserUtils = /*#__PURE__*/ function() {
1636
1647
  this._macroDefineExpectsNameToken = true;
1637
1648
  token.set(Keyword.MACRO_DEFINE, "#define", start);
1638
1649
  } else {
1650
+ // Legacy path: opaque token sequence. Register directly from the peek
1651
+ // result without re-walking the directive.
1639
1652
  this._scanUtilBreakLine(buffer);
1640
1653
  var lexeme = "\n" + buffer.join("") + "\n";
1641
- // Legacy path: register immediately by parsing the buffered directive.
1642
- this._registerMacroDefine(this._source.slice(this._macroDefineDirectiveStart, this._currentIndex));
1654
+ if (peek) {
1655
+ this._registerMacroDefine(peek.name, peek.paramsLexeme, peek.valueStart, peek.valueEnd);
1656
+ }
1643
1657
  token.set(Keyword.MACRO_DEFINE_EXPRESSION, lexeme, start);
1644
1658
  }
1645
1659
  } else {
@@ -1656,66 +1670,151 @@ var ParserUtils = /*#__PURE__*/ function() {
1656
1670
  return c === "\n" || c === "\r";
1657
1671
  };
1658
1672
  /**
1659
- * Peek forward from `#define` and decide whether this directive should use the
1660
- * expression-AST path or the legacy opaque path.
1673
+ * Walk a `#define` directive head once and produce everything both routing
1674
+ * and registration need: name range, optional params range, value range,
1675
+ * and whether the value parses as an `expression`.
1676
+ *
1677
+ * The replacement list is split three ways:
1678
+ *
1679
+ * - **AST path** (`isExpression = true`): value parses as `expression`.
1680
+ * Covers identifiers, literals, parenthesized sub-expressions, operator
1681
+ * expressions, function calls, top-level comma lists (per C99 §6.10.3).
1682
+ *
1683
+ * - **Legacy opaque path** (`isExpression = false`, no throw): the three
1684
+ * GLSL-ES-§3.4-legal-but-not-an-expression shapes with real-world use —
1685
+ * 1. empty value e.g. `#define COMMON_INCLUDED`
1686
+ * 2. single type/qualifier keyword e.g. `#define FxaaFloat float`
1687
+ * 3. type-qualifier list e.g. `#define TEX_PARAM(s) mediump sampler2D s`
1688
+ *
1689
+ * Note on X-macro support: the classical C X-macro pattern (a list
1690
+ * macro re-expanded with redefined `X(...)`) works fine — it uses
1691
+ * function-like macros + `\` line-continuation + `#undef`, all of
1692
+ * which Galacean supports. It does NOT require unbalanced parens.
1661
1693
  *
1662
- * The AST path's only purpose is to enable the shader compiler's structural rewrites
1663
- * (varying flatten, struct-property reference tracking) on macro values that
1664
- * mention struct members. Every other shape — bare identifiers, numeric
1665
- * literals, type-alias keywords (`#define FxaaFloat2 vec2`), constructor
1666
- * calls, qualifier fragments, `#define COMMA ,` etc. — is correctly handled
1667
- * by the GLSL driver via textual substitution and needs no AST.
1694
+ * - **Authoring error** (throws): every other shape that's not a valid
1695
+ * `expression`. Legal token sequences in theory but not used in real
1696
+ * GLSL — almost always author mistakes. We surface one uniform
1697
+ * diagnostic with the macro name and value text and let the user fix
1698
+ * their code instead of routing politely.
1668
1699
  *
1669
- * So the routing rule is positive and information-driven: **AST iff the
1670
- * replacement list contains a `.` member-access operator**. This is robust
1671
- * against new GLSL keywords / extensions and incidentally moves all simple
1672
- * constants and type aliases off the 18-layer expression precedence chain.
1673
- */ _proto._defineHasValue = function _defineHasValue() {
1700
+ * Unsupported shapes that throw (non-exhaustive — the predicate is
1701
+ * "the value doesn't reduce as `expression` and isn't one of the three
1702
+ * legacy shapes above"):
1703
+ * - leading bare punctuation `,` `;` `:` `?` `)` `]`
1704
+ * - trailing `,` or `;` e.g. `#define X a, b,`
1705
+ * - trailing binary / unary op `+` `-` `*` `/` `%` `&` `|` `^`
1706
+ * `<` `>` `=` `!` `~`
1707
+ * - trailing ternary fragment `?` or `:`
1708
+ * - unbalanced `[` / `]` e.g. `#define X a[b`
1709
+ * - unbalanced `(` / `)` e.g. `#define PAREN (`
1710
+ *
1711
+ * Returns `null` if the directive is malformed before the name. `cursor` is
1712
+ * the position past the last non-newline char (caller advances from there).
1713
+ */ _proto._peekMacroDefine = function _peekMacroDefine() {
1674
1714
  var src = this._source;
1675
1715
  var len = src.length;
1676
- // All position advancement goes through `_skipNonSemantic`, which honors
1677
- // line-continuation (`\` + newline) and skips comments. This is critical:
1678
- // `#define UV foo \\\n .field` is one logical directive whose value
1679
- // contains `.field`, and `#define HP /* a.b */ highp` must not let the
1680
- // `.` inside the comment trigger AST routing.
1681
1716
  var i = Lexer._skipNonSemantic(src, this._currentIndex, len);
1682
- // Skip macro name
1683
- if (!(i < len && BaseLexer.isAlpha(src.charCodeAt(i)))) return false;
1717
+ if (!(i < len && BaseLexer.isAlpha(src.charCodeAt(i)))) return null;
1718
+ var nameStart = i;
1684
1719
  while(i < len && BaseLexer.isAlnum(src.charCodeAt(i)))i++;
1685
- i = Lexer._skipNonSemantic(src, i, len);
1686
- // Optional `(params)`: scan past a balanced pair on the same logical line
1687
- if (src.charCodeAt(i) === 40 /* '(' */ ) {
1720
+ var name = src.slice(nameStart, i);
1721
+ // Optional `(params)` glued to the name (function-like per C99 §6.10.3/10;
1722
+ // whitespace before `(` makes it object-like — `(` becomes part of value).
1723
+ var paramsLexeme;
1724
+ if (i < len && src.charCodeAt(i) === 40 /* '(' */ ) {
1725
+ var paramsStart = i;
1688
1726
  var depth = 1;
1689
1727
  i++;
1690
1728
  while(i < len && depth > 0){
1691
1729
  i = Lexer._skipNonSemantic(src, i, len);
1730
+ if (i >= len) break;
1692
1731
  var c = src.charCodeAt(i);
1693
- if (c === 10 || c === 13) return false; // malformed: stay legacy
1732
+ if (c === 10 || c === 13) {
1733
+ // Unbalanced before newline — treat as malformed function-like.
1734
+ return {
1735
+ name: name,
1736
+ paramsLexeme: undefined,
1737
+ valueStart: i,
1738
+ valueEnd: i,
1739
+ cursor: i,
1740
+ isExpression: false
1741
+ };
1742
+ }
1694
1743
  if (c === 40) depth++;
1695
1744
  else if (c === 41 /* ')' */ ) depth--;
1696
1745
  i++;
1697
1746
  }
1747
+ paramsLexeme = src.slice(paramsStart, i);
1698
1748
  }
1699
- // Scan to end-of-directive looking for a `.` member-access operator.
1700
- // A `.` is member-access unless it's a decimal point inside a numeric
1701
- // literal — the latter has digits on both physical sides (`3.14`).
1702
- // Edge cases like `.5` go AST too (false positive, but AST path
1703
- // handles bare floats correctly).
1749
+ // Walk the value once tracking what routing needs.
1750
+ var valueStart = i;
1751
+ var parenDepth = 0;
1752
+ var bracketDepth = 0;
1753
+ var firstStart = -1;
1754
+ var firstEnd = -1;
1755
+ var firstFollowedByParen = false;
1756
+ var topLevelLast = -1;
1704
1757
  while(i < len){
1705
- i = Lexer._skipNonSemantic(src, i, len);
1706
- if (i >= len) break;
1758
+ var skipped = Lexer._skipNonSemantic(src, i, len);
1759
+ if (skipped !== i) {
1760
+ i = skipped;
1761
+ continue;
1762
+ }
1707
1763
  var c1 = src.charCodeAt(i);
1708
- if (c1 === 10 || c1 === 13) break; // real newline ends directive
1709
- if (c1 === 46 /* '.' */ ) {
1710
- var prev = i > 0 ? src.charCodeAt(i - 1) : 0;
1711
- var next = i + 1 < len ? src.charCodeAt(i + 1) : 0;
1712
- var prevIsDigit = prev >= 48 && prev <= 57;
1713
- var nextIsDigit = next >= 48 && next <= 57;
1714
- if (!(prevIsDigit && nextIsDigit)) return true;
1764
+ if (c1 === 10 || c1 === 13) break;
1765
+ if (firstStart === -1) {
1766
+ firstStart = i;
1767
+ if (BaseLexer.isAlpha(c1)) {
1768
+ while(i < len && BaseLexer.isAlnum(src.charCodeAt(i)))i++;
1769
+ firstEnd = i;
1770
+ var after = Lexer._skipNonSemantic(src, i, len);
1771
+ firstFollowedByParen = after < len && src.charCodeAt(after) === 40;
1772
+ topLevelLast = firstEnd - 1;
1773
+ continue;
1774
+ }
1715
1775
  }
1776
+ if (c1 === 40) parenDepth++;
1777
+ else if (c1 === 41) parenDepth--;
1778
+ else if (c1 === 91 /* [ */ ) bracketDepth++;
1779
+ else if (c1 === 93 /* ] */ ) bracketDepth--;
1780
+ if (parenDepth === 0 && bracketDepth === 0) topLevelLast = i;
1716
1781
  i++;
1717
1782
  }
1718
- return false;
1783
+ var result = {
1784
+ name: name,
1785
+ paramsLexeme: paramsLexeme,
1786
+ valueStart: valueStart,
1787
+ valueEnd: i,
1788
+ cursor: i,
1789
+ isExpression: false
1790
+ };
1791
+ // Real-world legacy shapes:
1792
+ // 1. empty value, 2. single type/qualifier kw, 3. type-qualifier list.
1793
+ if (firstStart === -1) return result;
1794
+ if (firstEnd !== -1 && !firstFollowedByParen && Lexer._isNonExpressionLeadingKeyword(src.slice(firstStart, firstEnd))) {
1795
+ return result;
1796
+ }
1797
+ // Authoring errors. Anything that's neither a legal `expression` nor one
1798
+ // of the three legacy shapes above gets a single uniform diagnostic —
1799
+ // the user sees the macro name and the value text, that's enough to
1800
+ // locate and fix. We don't categorize further; the rule for users is
1801
+ // simply "value must be a valid GLSL expression".
1802
+ //
1803
+ // Legal expression starts: alnum (identifier / literal), `(` (group),
1804
+ // `.` (GLSL ES §4.1.4 leading-dot float literal like `.5`), `-`/`+`/`!`/`~`
1805
+ // (unary). Legal expression ends: alnum (identifier / literal), `)` (group
1806
+ // close), `]` (array-index close). Everything else at the head or
1807
+ // top-level tail is an authoring error.
1808
+ var head = src.charCodeAt(firstStart);
1809
+ var tail = topLevelLast >= 0 ? src.charCodeAt(topLevelLast) : 0;
1810
+ var headIllegal = !BaseLexer.isAlnum(head) && head !== 40 /* ( */ && head !== 46 /* . */ && head !== 45 /* - */ && head !== 43 /* + */ && head !== 33 /* ! */ && head !== 126; /* ~ */
1811
+ var tailIllegal = !BaseLexer.isAlnum(tail) && tail !== 41 /* ) */ && tail !== 93; /* ] */
1812
+ if (parenDepth !== 0 || bracketDepth !== 0 || headIllegal || tailIllegal) {
1813
+ var valueText = src.slice(firstStart, i).replace(/\s+/g, " ").trim();
1814
+ this.throwError(this.getShaderPosition(0), "#define " + name + ': invalid replacement list — not a valid GLSL expression ("' + valueText + '")');
1815
+ }
1816
+ result.isExpression = true;
1817
+ return result;
1719
1818
  };
1720
1819
  /** Consuming wrapper around `_skipNonSemantic` operating on the lexer's
1721
1820
  * current position. Used by the `#define`-value scan state machine. Goes
@@ -1759,11 +1858,16 @@ var ParserUtils = /*#__PURE__*/ function() {
1759
1858
  this.advance(1);
1760
1859
  }
1761
1860
  var token = BaseToken.pool.get();
1762
- token.set(Keyword.MACRO_DEFINE_PARAMS, buffer.join(""), start);
1861
+ var lexeme = buffer.join("");
1862
+ this._currentMacroParamsLexeme = lexeme;
1863
+ // Value starts after the `)` we just consumed.
1864
+ this._currentMacroValueStart = this._currentIndex;
1865
+ token.set(Keyword.MACRO_DEFINE_PARAMS, lexeme, start);
1763
1866
  return token;
1764
1867
  };
1765
1868
  /** Emit `MACRO_DEFINE_END` at the end of a `#define` value, consuming the newline. */ _proto._emitMacroDefineEnd = function _emitMacroDefineEnd() {
1766
1869
  var start = this.getShaderPosition();
1870
+ var valueEnd = this._currentIndex;
1767
1871
  var source = this._source;
1768
1872
  if (this._currentIndex < source.length) {
1769
1873
  var c = source.charCodeAt(this._currentIndex);
@@ -1773,37 +1877,29 @@ var ParserUtils = /*#__PURE__*/ function() {
1773
1877
  this.advance(1);
1774
1878
  }
1775
1879
  }
1776
- // Register the directive into macroDefineList — single source of truth.
1777
- this._registerMacroDefine(this._source.slice(this._macroDefineDirectiveStart, this._currentIndex));
1880
+ this._registerMacroDefine(this._currentMacroName, this._currentMacroParamsLexeme, this._currentMacroValueStart, valueEnd);
1778
1881
  this._inMacroDefineValue = false;
1779
1882
  var token = BaseToken.pool.get();
1780
1883
  token.set(Keyword.MACRO_DEFINE_END, "\n", start);
1781
1884
  return token;
1782
1885
  };
1783
- // Parse a `#define <name>[(params)] [value]` directive (already lexed to
1784
- // its newline) and register it. Both AST and legacy paths funnel through
1785
- // here — single source of truth, no drift between two analyzers.
1786
- _proto._registerMacroDefine = function _registerMacroDefine(directive) {
1787
- // Fold line-continuations before matching: per C/GLSL preprocessor rules
1788
- // `\` immediately before a newline removes both characters and stitches
1789
- // the next physical line onto this one. The regex's value group rejects
1790
- // newlines, so without folding any multi-line directive (`#define X a \\\n + b`)
1791
- // would NO MATCH and registration would silently fail.
1792
- var folded = directive.replace(Lexer._lineContinuationReg, "");
1793
- var m = Lexer._defineDirectiveReg.exec(folded);
1794
- if (!m) return;
1795
- var name = m[1];
1796
- var paramsStr = m[3];
1797
- var valueRaw = m[4];
1798
- var params = paramsStr ? paramsStr.split(",").map(function(p) {
1886
+ // Register a `#define` directive. Both AST and legacy paths funnel through
1887
+ // here using ranges already extracted by `_peekMacroDefine` / the value
1888
+ // state machine — no re-parsing the directive text.
1889
+ _proto._registerMacroDefine = function _registerMacroDefine(name, paramsLexeme, valueStart, valueEnd) {
1890
+ var params = paramsLexeme ? paramsLexeme.slice(1, -1) // strip enclosing `(` `)`
1891
+ .split(",").map(function(p) {
1799
1892
  return p.trim();
1800
1893
  }).filter(Boolean) : [];
1801
- var refMatch = valueRaw ? Lexer._referenceReg.exec(valueRaw.trim()) : null;
1894
+ // Dedup key: normalized value text (same lexical view the token-stream
1895
+ // path sees — comments stripped, whitespace collapsed). Two `#define`s
1896
+ // with the same name but different value text produce different keys,
1897
+ // so disjoint-branch entries stay separate.
1898
+ var dedupKey = (paramsLexeme != null ? paramsLexeme : "") + "=" + Lexer._normalizeValueText(this._source, valueStart, valueEnd);
1802
1899
  var info = {
1803
- isFunction: m[2] !== undefined,
1804
- name: name,
1900
+ isFunction: paramsLexeme !== undefined,
1805
1901
  params: params,
1806
- referenceName: refMatch ? refMatch[1] : "",
1902
+ dedupKey: dedupKey,
1807
1903
  branch: this._branchStack.length === 0 ? EMPTY_BRANCH : this._branchStack.slice()
1808
1904
  };
1809
1905
  var arr = this.macroDefineList[name];
@@ -1813,14 +1909,11 @@ var ParserUtils = /*#__PURE__*/ function() {
1813
1909
  ];
1814
1910
  return;
1815
1911
  }
1816
- // Skip duplicates from re-includes / re-definitions in the same `#ifdef`
1817
- // branch. Different branches → different entries (the call-site filter
1818
- // picks the visible one); same branch + same shape → drop.
1912
+ // Same key + same branch → duplicate (re-include). Different branches stay
1913
+ // separate so the visibility filter picks the right entry at each call site.
1819
1914
  for(var i = 0, n = arr.length; i < n; i++){
1820
1915
  var e = arr[i];
1821
- if (e.isFunction === info.isFunction && e.referenceName === info.referenceName && e.params.length === info.params.length && e.params.every(function(p, idx) {
1822
- return p === info.params[idx];
1823
- }) && Lexer.sameBranch(e.branch, info.branch)) return;
1916
+ if (e.dedupKey === dedupKey && Lexer.sameBranch(e.branch, info.branch)) return;
1824
1917
  }
1825
1918
  arr.push(info);
1826
1919
  };
@@ -1850,6 +1943,8 @@ var ParserUtils = /*#__PURE__*/ function() {
1850
1943
  // Bypass MACRO_CALL classification for this one word. Used for the name of
1851
1944
  // a `#define` directive so redefining a known macro still yields a declarer ID.
1852
1945
  this._macroDefineExpectsNameToken = false;
1946
+ this._currentMacroName = word;
1947
+ this._currentMacroParamsLexeme = undefined;
1853
1948
  // C99 §6.10.3/3: the macro is function-like only when `(` appears *immediately*
1854
1949
  // after the name (no intervening whitespace). `#define FOO (1+2)` is
1855
1950
  // object-like with value `(1+2)`, not a function-like macro `FOO()` with
@@ -1857,6 +1952,10 @@ var ParserUtils = /*#__PURE__*/ function() {
1857
1952
  // on the next `scanToken`, so a space-separated `(` stays part of the value.
1858
1953
  if (this._inMacroDefineValue) {
1859
1954
  this._macroDefineExpectsParamsToken = this.getCurChar() === "(";
1955
+ // For object-like macros, the value begins where the cursor is now
1956
+ // (after the name, before whitespace skipping). Function-like macros
1957
+ // overwrite this in `_scanMacroDefineParams` after capturing `(...)`.
1958
+ this._currentMacroValueStart = this._currentIndex;
1860
1959
  }
1861
1960
  token.set(ETokenType.ID, word, start);
1862
1961
  } else if (this._isVisibleMacro(word)) {
@@ -1996,6 +2095,12 @@ var ParserUtils = /*#__PURE__*/ function() {
1996
2095
  }
1997
2096
  return true;
1998
2097
  };
2098
+ /** GLSL type / qualifier keywords that aren't expression starters when standing alone.
2099
+ * `true` / `false` are excluded — they're `primary_expression` literals.
2100
+ * `#`-prefixed entries are excluded — they're preprocessor directive names. */ Lexer._isNonExpressionLeadingKeyword = function _isNonExpressionLeadingKeyword(lexeme) {
2101
+ var kw = Lexer._lexemeTable[lexeme];
2102
+ return kw !== undefined && kw !== Keyword.True && kw !== Keyword.False && !lexeme.startsWith("#");
2103
+ };
1999
2104
  /**
2000
2105
  * If `i` points at a `\` immediately followed by a newline (`\n`, `\r`, or
2001
2106
  * `\r\n`), return the index just past the pair (a single atom in the C/GLSL
@@ -2003,8 +2108,7 @@ var ParserUtils = /*#__PURE__*/ function() {
2003
2108
  * source of truth for line-continuation detection — callers along the
2004
2109
  * directive-scanning path (`_skipNonSemantic`, `_scanUtilBreakLine`,
2005
2110
  * `_scanMacroDefineParams`) all delegate here so the rule stays in one
2006
- * place. The string-level `_lineContinuationReg` covers the regex path
2007
- * used by `_registerMacroDefine` for one-shot folding.
2111
+ * place.
2008
2112
  */ Lexer._skipLineContinuation = function _skipLineContinuation(src, i, len) {
2009
2113
  if (src.charCodeAt(i) !== 92 /* '\\' */ || i + 1 >= len) return i;
2010
2114
  var n = src.charCodeAt(i + 1);
@@ -2047,6 +2151,34 @@ var ParserUtils = /*#__PURE__*/ function() {
2047
2151
  }
2048
2152
  return i;
2049
2153
  };
2154
+ /** Render a `[start, end)` value range as space-separated significant chars,
2155
+ * using the same comment / line-continuation rules `_skipNonSemantic`
2156
+ * applies on the token-stream path. Used as the dedup key body. */ Lexer._normalizeValueText = function _normalizeValueText(src, start, end) {
2157
+ var out = "";
2158
+ var i = start;
2159
+ var pendingSpace = false;
2160
+ while(i < end){
2161
+ var skipped = Lexer._skipNonSemantic(src, i, end);
2162
+ if (skipped !== i) {
2163
+ pendingSpace = out.length > 0;
2164
+ i = skipped;
2165
+ continue;
2166
+ }
2167
+ var c = src.charCodeAt(i);
2168
+ if (c === 32 || c === 9) {
2169
+ pendingSpace = out.length > 0;
2170
+ i++;
2171
+ continue;
2172
+ }
2173
+ if (pendingSpace) {
2174
+ out += " ";
2175
+ pendingSpace = false;
2176
+ }
2177
+ out += src[i];
2178
+ i++;
2179
+ }
2180
+ return out;
2181
+ };
2050
2182
  return Lexer;
2051
2183
  }(BaseLexer);
2052
2184
  Lexer._lexemeTable = {
@@ -2123,12 +2255,6 @@ Lexer._lexemeTable = {
2123
2255
  "#endif": Keyword.MACRO_ENDIF,
2124
2256
  "#undef": Keyword.MACRO_UNDEF
2125
2257
  };
2126
- // Single source of truth for `#define` parsing — fed from both AST and legacy paths.
2127
- Lexer._defineDirectiveReg = /^\s*#define\s+(\w+)[ ]*(\(([^)]*)\))?(?:[ \t]+([^\n\r]*?))?\s*$/;
2128
- // Bare identifier or call form only — mixed-operator values reject.
2129
- Lexer._referenceReg = /^([a-zA-Z_]\w*)(?:\s*\(.*\))?$/;
2130
- // C preprocessor line continuation.
2131
- Lexer._lineContinuationReg = /\\(?:\r\n|\n|\r)/g;
2132
2258
  // Synthetic `__if_<n>` per `#if` — polarity flip makes `#else` mutually exclusive in `isVisibleFrom`.
2133
2259
  Lexer._ifCounter = 0;
2134
2260
 
@@ -4825,34 +4951,13 @@ var TreeNode = /*#__PURE__*/ function() {
4825
4951
  var child = this.children[0];
4826
4952
  var referenceGlobalSymbolNames = this.referenceGlobalSymbolNames;
4827
4953
  var symbols = this._symbols;
4828
- var lookupSymbol = SemanticAnalyzer._lookupSymbol;
4829
- var needFindNames;
4830
- // FXAA-style cross-arm shadowing: same name is a macro in one
4831
- // preprocessor arm and a variable in the mutually-exclusive arm.
4832
- // At a MACRO_CALL use site, also probe the macro name itself so
4833
- // the sibling-arm declaration is marked as referenced and codegen
4834
- // keeps it. Grammar half of the fix is in 87cb2b5f0.
4835
- var macroNameAsVarLookup = null;
4836
- if (_instanceof(child, BaseToken)) {
4837
- needFindNames = [
4838
- child.lexeme
4839
- ];
4840
- } else {
4841
- var callSymbol = child;
4842
- needFindNames = callSymbol.referenceSymbolNames;
4843
- var macroName = callSymbol.macroName;
4844
- if (macroName && needFindNames.indexOf(macroName) === -1) {
4845
- needFindNames = needFindNames.concat(macroName);
4846
- macroNameAsVarLookup = macroName;
4847
- }
4848
- }
4954
+ // Real references — every name must resolve; miss is an authoring error.
4955
+ var needFindNames = _instanceof(child, BaseToken) ? [
4956
+ child.lexeme
4957
+ ] : child.referenceSymbolNames;
4849
4958
  for(var i = 0; i < needFindNames.length; i++){
4850
4959
  var name = needFindNames[i];
4851
- // `macroDefineList` short-circuit; bypass for the macro name itself
4852
- // so cross-arm shadowing can resolve the sibling-arm declaration.
4853
- if (sa.macroDefineList[name] && name !== macroNameAsVarLookup) {
4854
- continue;
4855
- }
4960
+ if (sa.macroDefineList[name]) continue;
4856
4961
  // only `macro_call` CFG can reference fnSymbols, others fnSymbols are referenced in `function_call_generic` CFG
4857
4962
  if (!_instanceof(child, BaseToken) && BuiltinFunction.isExist(name)) {
4858
4963
  continue;
@@ -4862,31 +4967,24 @@ var TreeNode = /*#__PURE__*/ function() {
4862
4967
  this.typeInfo = builtinVar.type;
4863
4968
  continue;
4864
4969
  }
4865
- lookupSymbol.set(name, ESymbolType.Any);
4866
- sa.symbolTableStack.lookupAll(lookupSymbol, true, symbols);
4867
- if (!symbols.length) {
4868
- sa.reportWarning(this.location, 'Please sure the identifier "' + name + '" will be declared before used.');
4869
- } else {
4870
- // Expression-style macros have their own value AST; its real type isn't
4871
- // the type of any single `referenceSymbolNames` entry (`v` in `v.v_uv`
4872
- // is a `Varyings` struct but the macro call site's type should be the
4873
- // member type). Skip type inference for those and keep TypeAny.
4874
- if (_instanceof(child, BaseToken) || !child.hasAstValue) {
4875
- var _symbols__dataType;
4876
- this.typeInfo = (_symbols__dataType = symbols[0].dataType) == null ? void 0 : _symbols__dataType.type;
4877
- }
4878
- var currentScopeSymbol = sa.symbolTableStack.scope.getSymbol(lookupSymbol, true);
4879
- if (currentScopeSymbol) {
4880
- if ((_instanceof(currentScopeSymbol, FnSymbol) || currentScopeSymbol.isGlobalVariable) && referenceGlobalSymbolNames.indexOf(name) === -1) {
4881
- referenceGlobalSymbolNames.push(name);
4882
- }
4883
- } else if (symbols.some(function(s) {
4884
- return _instanceof(s, FnSymbol) || s.isGlobalVariable;
4885
- }) && referenceGlobalSymbolNames.indexOf(name) === -1) {
4886
- referenceGlobalSymbolNames.push(name);
4887
- }
4970
+ var hit = VariableIdentifier._lookupAndMarkGlobalReference(sa, name, symbols, referenceGlobalSymbolNames, this.location);
4971
+ // Expression-style macros have their own value AST; its real type isn't
4972
+ // the type of any single `referenceSymbolNames` entry (`v` in `v.v_uv`
4973
+ // is a `Varyings` struct but the macro call site's type should be the
4974
+ // member type). Skip type inference for those and keep TypeAny.
4975
+ if (hit && (_instanceof(child, BaseToken) || !child.hasAstValue)) {
4976
+ var _symbols__dataType;
4977
+ this.typeInfo = (_symbols__dataType = symbols[0].dataType) == null ? void 0 : _symbols__dataType.type;
4888
4978
  }
4889
4979
  }
4980
+ // FXAA-style cross-arm shadowing: at a MACRO_CALL use site, silently
4981
+ // probe the macro name itself so any sibling-arm `var` declaration is
4982
+ // marked as referenced and codegen keeps it. Miss is the common
4983
+ // single-arm case — no warning, no type inference. Grammar half of the
4984
+ // cross-arm fix is in 87cb2b5f0.
4985
+ if (!_instanceof(child, BaseToken)) {
4986
+ VariableIdentifier._probeCrossArmShadowing(sa, child, needFindNames, symbols, referenceGlobalSymbolNames);
4987
+ }
4890
4988
  };
4891
4989
  _proto.codeGen = function codeGen(visitor) {
4892
4990
  return this.setCache(visitor.visitVariableIdentifier(this));
@@ -4899,6 +4997,43 @@ var TreeNode = /*#__PURE__*/ function() {
4899
4997
  return child.codeGen(visitor);
4900
4998
  }
4901
4999
  };
5000
+ /** Run the cross-arm shadowing probe for a MACRO_CALL site. No-op when the
5001
+ * probe isn't meaningful: no macro name, name already resolved as a real
5002
+ * reference, or name is a builtin (builtins can't be shadowed by a
5003
+ * sibling-arm decl). */ VariableIdentifier._probeCrossArmShadowing = function _probeCrossArmShadowing(sa, child, needFindNames, symbols, referenceGlobalSymbolNames) {
5004
+ var macroName = child.macroName;
5005
+ if (!macroName) return;
5006
+ if (needFindNames.indexOf(macroName) !== -1) return; // already looked up as a real reference
5007
+ if (BuiltinFunction.isExist(macroName) || BuiltinVariable.getVar(macroName)) return; // builtins can't be shadowed
5008
+ VariableIdentifier._lookupAndMarkGlobalReference(sa, macroName, symbols, referenceGlobalSymbolNames, null);
5009
+ };
5010
+ /** Look up `name` in the symbol stack and, if a global var/fn declaration
5011
+ * exists, push it into `referenceGlobalSymbolNames`. Returns `true` iff
5012
+ * the lookup hit (caller can then derive type info). When `missWarnLoc`
5013
+ * is non-null, a miss reports a "declared before used" warning; pass
5014
+ * `null` for silent probes (e.g. FXAA-style cross-arm shadowing).
5015
+ *
5016
+ * Mutation contract: `symbols` is used as scratch storage — `lookupAll`
5017
+ * clears and refills it. On hit, the caller may read `symbols[0]` for
5018
+ * type info before the next call overwrites the contents. */ VariableIdentifier._lookupAndMarkGlobalReference = function _lookupAndMarkGlobalReference(sa, name, symbols, referenceGlobalSymbolNames, missWarnLoc) {
5019
+ var lookupSymbol = SemanticAnalyzer._lookupSymbol;
5020
+ lookupSymbol.set(name, ESymbolType.Any);
5021
+ sa.symbolTableStack.lookupAll(lookupSymbol, true, symbols);
5022
+ if (!symbols.length) {
5023
+ if (missWarnLoc) {
5024
+ sa.reportWarning(missWarnLoc, 'Please sure the identifier "' + name + '" will be declared before used.');
5025
+ }
5026
+ return false;
5027
+ }
5028
+ var currentScopeSymbol = sa.symbolTableStack.scope.getSymbol(lookupSymbol, true);
5029
+ var isGlobal = currentScopeSymbol ? _instanceof(currentScopeSymbol, FnSymbol) || currentScopeSymbol.isGlobalVariable : symbols.some(function(s) {
5030
+ return _instanceof(s, FnSymbol) || s.isGlobalVariable;
5031
+ });
5032
+ if (isGlobal && referenceGlobalSymbolNames.indexOf(name) === -1) {
5033
+ referenceGlobalSymbolNames.push(name);
5034
+ }
5035
+ return true;
5036
+ };
4902
5037
  return VariableIdentifier;
4903
5038
  }(TreeNode);
4904
5039
  ASTNode.VariableIdentifier = VariableIdentifier;
@@ -5155,18 +5290,16 @@ var TreeNode = /*#__PURE__*/ function() {
5155
5290
  MacroBranch = __decorate([
5156
5291
  ASTNodeDecorator(NoneTerminal.macro_branch)
5157
5292
  ], MacroBranch);
5158
- var MacroCallSymbol = /*#__PURE__*/ function(TreeNode) {
5159
- _inherits(MacroCallSymbol, TreeNode);
5293
+ var MacroCallSymbol = /*#__PURE__*/ function(TreeNode1) {
5294
+ _inherits(MacroCallSymbol, TreeNode1);
5160
5295
  function MacroCallSymbol() {
5161
5296
  var _this;
5162
- _this = TreeNode.apply(this, arguments) || this, _this.referenceSymbolNames = [], /** True iff every `MacroDefineInfo` visible from this call site's branch
5297
+ _this = TreeNode1.apply(this, arguments) || this, _this.referenceSymbolNames = [], /** True iff every `MacroDefineInfo` visible from this call site's branch
5163
5298
  * has a `valueAst` (i.e. was parsed via the `macro_define` CFG rule).
5164
5299
  * Call sites use this to skip naive type inference: an AST-form macro
5165
5300
  * drives the call site's type through its own value subtree, not through
5166
5301
  * a root of `referenceSymbolNames`. Mixed forms across branches → false,
5167
- * fall back to legacy inference. */ _this.hasAstValue = false, /** True when the macro is defined as function-like (`#define NAME(params) …`).
5168
- * Used by `MacroCallFunction` codegen to pick between the two call shapes
5169
- * — object-like-macro-as-function-name vs true function-like macro. */ _this.isFunctionLikeMacro = false;
5302
+ * 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;
5170
5303
  return _this;
5171
5304
  }
5172
5305
  var _proto = MacroCallSymbol.prototype;
@@ -5174,6 +5307,7 @@ var TreeNode = /*#__PURE__*/ function() {
5174
5307
  this.referenceSymbolNames.length = 0;
5175
5308
  this.hasAstValue = false;
5176
5309
  this.isFunctionLikeMacro = false;
5310
+ this.aliasesNonBuiltinIdent = false;
5177
5311
  };
5178
5312
  _proto.semanticAnalyze = function semanticAnalyze(sa) {
5179
5313
  var nameToken = this.children[0];
@@ -5191,6 +5325,7 @@ var TreeNode = /*#__PURE__*/ function() {
5191
5325
  var visibleCount = 0;
5192
5326
  var allAst = true;
5193
5327
  var isFn = false;
5328
+ var allAliasNonBuiltinIdent = true;
5194
5329
  if (defList) {
5195
5330
  for(var i = 0, n = defList.length; i < n; i++){
5196
5331
  var info = defList[i];
@@ -5198,8 +5333,30 @@ var TreeNode = /*#__PURE__*/ function() {
5198
5333
  visibleCount++;
5199
5334
  if (info.valueAst == null) allAst = false;
5200
5335
  if (info.isFunction) isFn = true;
5201
- var ref = info.referenceName;
5202
- if (ref && info.params.indexOf(ref) === -1 && refs.indexOf(ref) === -1) refs.push(ref);
5336
+ // Harvest references from the value AST. Legacy-form macros (no
5337
+ // `valueAst`) hold non-expression token sequences with no user
5338
+ // identifiers, so nothing to collect.
5339
+ if (info.valueAst) {
5340
+ MacroCallSymbol._collectIdentifierRefs(info.valueAst, info.params, refs);
5341
+ }
5342
+ // aliasesNonBuiltinIdent: macro replacement is a single non-builtin
5343
+ // identifier — best-effort proxy for "this macro call site aliases a
5344
+ // user fn", since uniform/const aliases would surface as a GLSL
5345
+ // compile error later anyway. Keyword replacements (`vec3`, `mat4`)
5346
+ // reach here with `valueAst === undefined` (opaque path), so they
5347
+ // automatically fail without an explicit keyword guard.
5348
+ if (info.isFunction || !info.valueAst) {
5349
+ allAliasNonBuiltinIdent = false;
5350
+ } else {
5351
+ var leadingIdent = ParserUtils.unwrapBareIdentifier(info.valueAst, {
5352
+ allowParens: false
5353
+ });
5354
+ var leadingChild = leadingIdent == null ? void 0 : leadingIdent.children[0];
5355
+ var leadingId = _instanceof(leadingChild, BaseToken) ? leadingChild.lexeme : undefined;
5356
+ if (!leadingId || BuiltinFunction.isExist(leadingId)) {
5357
+ allAliasNonBuiltinIdent = false;
5358
+ }
5359
+ }
5203
5360
  }
5204
5361
  }
5205
5362
  // Require *every* visible entry to be AST-form before taking the AST
@@ -5208,6 +5365,25 @@ var TreeNode = /*#__PURE__*/ function() {
5208
5365
  // instead of polluting the call site with TypeAny.
5209
5366
  this.hasAstValue = visibleCount > 0 && allAst;
5210
5367
  this.isFunctionLikeMacro = isFn;
5368
+ this.aliasesNonBuiltinIdent = visibleCount > 0 && allAliasNonBuiltinIdent;
5369
+ };
5370
+ /** Push every leaf `VariableIdentifier`'s lexeme into `out`, skipping
5371
+ * function-like parameter names (local to the macro, not call-site refs)
5372
+ * and duplicates. */ MacroCallSymbol._collectIdentifierRefs = function _collectIdentifierRefs(node, params, out) {
5373
+ if (_instanceof(node, VariableIdentifier)) {
5374
+ var child = node.children[0];
5375
+ if (_instanceof(child, BaseToken)) {
5376
+ var name = child.lexeme;
5377
+ if (params.indexOf(name) === -1 && out.indexOf(name) === -1) out.push(name);
5378
+ }
5379
+ return;
5380
+ }
5381
+ var children = node.children;
5382
+ if (!children) return;
5383
+ for(var i = 0, n = children.length; i < n; i++){
5384
+ var c = children[i];
5385
+ if (_instanceof(c, TreeNode)) MacroCallSymbol._collectIdentifierRefs(c, params, out);
5386
+ }
5211
5387
  };
5212
5388
  return MacroCallSymbol;
5213
5389
  }(TreeNode);
@@ -5219,7 +5395,7 @@ var TreeNode = /*#__PURE__*/ function() {
5219
5395
  _inherits(MacroCallFunction, TreeNode);
5220
5396
  function MacroCallFunction() {
5221
5397
  var _this;
5222
- _this = TreeNode.apply(this, arguments) || this, _this.referenceSymbolNames = [], _this.macroName = "", _this.hasAstValue = false, _this.isFunctionLikeMacro = false;
5398
+ _this = TreeNode.apply(this, arguments) || this, _this.referenceSymbolNames = [], _this.macroName = "", _this.hasAstValue = false, _this.isFunctionLikeMacro = false, _this.aliasesNonBuiltinIdent = false;
5223
5399
  return _this;
5224
5400
  }
5225
5401
  var _proto = MacroCallFunction.prototype;
@@ -5228,6 +5404,7 @@ var TreeNode = /*#__PURE__*/ function() {
5228
5404
  this.macroName = "";
5229
5405
  this.hasAstValue = false;
5230
5406
  this.isFunctionLikeMacro = false;
5407
+ this.aliasesNonBuiltinIdent = false;
5231
5408
  };
5232
5409
  _proto.semanticAnalyze = function semanticAnalyze(sa) {
5233
5410
  var child = this.children[0];
@@ -5235,6 +5412,7 @@ var TreeNode = /*#__PURE__*/ function() {
5235
5412
  this.macroName = child.macroName;
5236
5413
  this.hasAstValue = child.hasAstValue;
5237
5414
  this.isFunctionLikeMacro = child.isFunctionLikeMacro;
5415
+ this.aliasesNonBuiltinIdent = child.aliasesNonBuiltinIdent;
5238
5416
  };
5239
5417
  _proto.codeGen = function codeGen(visitor) {
5240
5418
  return this.setCache(visitor.visitMacroCallFunction(this));
@@ -5270,7 +5448,10 @@ var TreeNode = /*#__PURE__*/ function() {
5270
5448
  params = ParserUtils.parseMacroParamList(children[2].lexeme);
5271
5449
  valueIdx = 3;
5272
5450
  }
5273
- if (_instanceof(children[valueIdx], AssignmentExpression)) {
5451
+ // Grammar's macro_define value is `expression` (not `assignment_expression`),
5452
+ // so the value child is always an `Expression` wrapper — even for a single
5453
+ // assignment-expression value, which appears as `Expression > AssignmentExpression`.
5454
+ if (_instanceof(children[valueIdx], Expression)) {
5274
5455
  this.valueExpression = children[valueIdx];
5275
5456
  }
5276
5457
  // The Lexer already registered a regex-derived entry for this directive.
@@ -5293,14 +5474,14 @@ var TreeNode = /*#__PURE__*/ function() {
5293
5474
  if (upgradable) {
5294
5475
  upgradable.valueAst = this.valueExpression;
5295
5476
  } else {
5296
- // No matching preprocessor entry (e.g. the lexer was fed the directive
5297
- // directly without a `Preprocessor.parse` pass). Push a fresh entry.
5477
+ // No matching preprocessor entry (lexer fed directly, no
5478
+ // `Preprocessor.parse` pass). Synthetic key based on shape is enough
5479
+ // — this path's only dedup unit is this AST-direct registration.
5298
5480
  var info = {
5299
5481
  isFunction: this.isFunction,
5300
- name: this.macroName,
5301
5482
  params: params,
5302
5483
  valueAst: this.valueExpression,
5303
- referenceName: "",
5484
+ dedupKey: this.macroName + "#ast/" + (this.isFunction ? params.join(",") : ""),
5304
5485
  branch: definingBranch
5305
5486
  };
5306
5487
  if (entries) entries.push(info);
@@ -5308,6 +5489,13 @@ var TreeNode = /*#__PURE__*/ function() {
5308
5489
  info
5309
5490
  ];
5310
5491
  }
5492
+ // Close the form-param scope pushed at `MACRO_DEFINE_PARAMS` shift. By
5493
+ // the time this reduce fires, every `VariableIdentifier` inside the
5494
+ // value expression has already resolved against the params. Object-like
5495
+ // macros never pushed a scope, so nothing to pop.
5496
+ if (this.isFunction) {
5497
+ sa.popScope();
5498
+ }
5311
5499
  };
5312
5500
  _proto.codeGen = function codeGen(visitor) {
5313
5501
  return this.setCache(visitor.visitMacroDefine(this));
@@ -5535,30 +5723,24 @@ var TempArray = /*#__PURE__*/ function() {
5535
5723
  if (_instanceof(paramList, ASTNode.FunctionCallParameterList)) {
5536
5724
  var astNodes = paramList.paramNodes;
5537
5725
  var context = VisitorContext.context;
5538
- // `MacroCallFunction` covers two call shapes sharing the same AST:
5539
- // (a) object-like macro whose value is a function name, used as a call —
5540
- // `#define FN foo` + `FN(varyings, …)`. The driver expands `FN` to
5541
- // `foo`, and `foo` is a shader function whose IO-struct params
5542
- // have been flattened. The call site must drop IO-struct args to
5543
- // match the flattened signature — same rule as `visitFunctionCall`.
5544
- // (b) true function-like macro — `#define MAX3(a,b,c) …` + `MAX3(v.x, …)`.
5545
- // The shader compiler doesn't expand the macro; the driver does, and the
5546
- // `#define` fixes the parameter count. Args must be preserved
5547
- // verbatim — a member-access arg like `v.v_uv` unwraps to a root
5548
- // identifier whose type is an IO struct, but dropping the arg
5549
- // would change the macro's arity.
5550
- //
5551
- // `isFunctionLikeMacro` is set by `MacroCallSymbol.semanticAnalyze` from
5552
- // `macroDefineList[name][*].isFunction` and carries the definition shape.
5553
- var params = node.isFunctionLikeMacro ? astNodes : astNodes.filter(function(arg) {
5554
- if (_instanceof(arg, ASTNode.AssignmentExpression)) {
5555
- var variableParam = ParserUtils.unwrapNodeByType(arg, NoneTerminal.variable_identifier);
5556
- if (variableParam && typeof variableParam.typeInfo === "string" && context.getStructRole(variableParam.typeInfo)) {
5557
- return false;
5726
+ // Drop bare IO-struct args only when the macro aliases a user fn (whose
5727
+ // formal was flattened). All other shapes preserve args verbatim.
5728
+ var params;
5729
+ if (node.isFunctionLikeMacro || !node.aliasesNonBuiltinIdent) {
5730
+ params = astNodes;
5731
+ } else {
5732
+ params = astNodes.filter(function(arg) {
5733
+ if (_instanceof(arg, ASTNode.AssignmentExpression)) {
5734
+ var variableParam = ParserUtils.unwrapBareIdentifier(arg, {
5735
+ allowParens: false
5736
+ });
5737
+ if (variableParam && typeof variableParam.typeInfo === "string" && context.getStructRole(variableParam.typeInfo)) {
5738
+ return false;
5739
+ }
5558
5740
  }
5559
- }
5560
- return true;
5561
- });
5741
+ return true;
5742
+ });
5743
+ }
5562
5744
  var paramsCode = "";
5563
5745
  for(var i = 0, length = params.length; i < length; i++){
5564
5746
  var argNode = params[i];
@@ -6908,9 +7090,14 @@ ShaderInstructionEncoder._FUNC_MACRO_RE = /^(\w+)\(([^)]*)\)\s*(.*)/;
6908
7090
  /** Resolve shift-reduce/reduce-reduce conflict detect */ _proto._addAction = function _addAction(table, terminal, action) {
6909
7091
  var exist = table.get(terminal);
6910
7092
  if (exist && !GrammarUtils.isActionEqual(exist, action)) {
6911
- // Resolve dangling else ambiguity
6912
- if (terminal === Keyword.ELSE && exist.action === EAction.Shift && action.action === EAction.Reduce) {
6913
- return;
7093
+ // Known shift-preferred conflicts (see TargetParser.y `%expect 2`).
7094
+ // Enforce shift regardless of the order `_inferNextState` registers
7095
+ // actions: when `exist` is already Shift and `action` is Reduce, keep
7096
+ // `exist` (early-return); the reverse order (`exist` Reduce, `action`
7097
+ // Shift) falls through to `table.set` below and Shift overwrites
7098
+ // Reduce. Order-independent by construction.
7099
+ if (LALR1._isKnownShiftPreferred(terminal, exist, action)) {
7100
+ if (exist.action === EAction.Shift && action.action === EAction.Reduce) return;
6914
7101
  } else {
6915
7102
  console.warn("conflict detect: <Terminal " + GrammarUtils.toString(terminal) + "> \n", GrammarUtils.printAction(exist), "\n", GrammarUtils.printAction(action));
6916
7103
  }
@@ -6964,6 +7151,22 @@ ShaderInstructionEncoder._FUNC_MACRO_RE = /^(\w+)\(([^)]*)\)\s*(.*)/;
6964
7151
  this._firstSetNTStack.pop();
6965
7152
  return firstSet;
6966
7153
  };
7154
+ // Catalog of expected shift/reduce conflicts. Each entry must correspond to
7155
+ // one of TargetParser.y's `%expect`-ed conflicts; any new conflict not in
7156
+ // this list falls through to the verbose `conflict detect` warning so the
7157
+ // grammar/runtime drift is loud rather than silent.
7158
+ // - ELSE: dangling-else, bind to nearest `if`
7159
+ // - '(' + `type_specifier_nonarray → macro_call_symbol`: macro-as-type-alias
7160
+ // (#2974), prefer the expression-position `macro_call_function` over the
7161
+ // type-position reduce
7162
+ LALR1._isKnownShiftPreferred = function _isKnownShiftPreferred(terminal, exist, action) {
7163
+ if (terminal === Keyword.ELSE) return true;
7164
+ if (terminal !== ETokenType.LEFT_PAREN) return false;
7165
+ var reduce = exist.action === EAction.Reduce ? exist : action.action === EAction.Reduce ? action : null;
7166
+ if (!reduce) return false;
7167
+ var prod = Production.pool.get(reduce.target);
7168
+ return !!prod && prod.goal === NoneTerminal.type_specifier_nonarray && prod.derivation.length === 1 && prod.derivation[0] === NoneTerminal.macro_call_symbol;
7169
+ };
6967
7170
  return LALR1;
6968
7171
  }();
6969
7172
 
@@ -7036,14 +7239,14 @@ var productionAndRules = [].concat(GrammarUtils.createProductionWithOptions(None
7036
7239
  [
7037
7240
  Keyword.MACRO_DEFINE,
7038
7241
  ETokenType.ID,
7039
- NoneTerminal.assignment_expression,
7242
+ NoneTerminal.expression,
7040
7243
  Keyword.MACRO_DEFINE_END
7041
7244
  ],
7042
7245
  [
7043
7246
  Keyword.MACRO_DEFINE,
7044
7247
  ETokenType.ID,
7045
7248
  Keyword.MACRO_DEFINE_PARAMS,
7046
- NoneTerminal.assignment_expression,
7249
+ NoneTerminal.expression,
7047
7250
  Keyword.MACRO_DEFINE_END
7048
7251
  ]
7049
7252
  ], ASTNode.MacroDefine.pool), GrammarUtils.createProductionWithOptions(NoneTerminal.macro_call_symbol, [
@@ -8410,6 +8613,19 @@ var addTranslationRule = function(sa) {
8410
8613
  var actionInfo = this.stateActionTable.get(token.type);
8411
8614
  if ((actionInfo == null ? void 0 : actionInfo.action) === EAction.Shift) {
8412
8615
  traceBackStack.push(token, actionInfo.target);
8616
+ // Function-like `#define` form params live in a scope wrapping the
8617
+ // value AST, mirroring how `function_header` opens a scope for GLSL
8618
+ // function parameters. Push on shift of `MACRO_DEFINE_PARAMS`; the
8619
+ // matching `popScope` runs when `MacroDefine.semanticAnalyze` reduces
8620
+ // the production (only the function-like alternative needs it, and
8621
+ // it knows that from its own children).
8622
+ if (token.type === Keyword.MACRO_DEFINE_PARAMS) {
8623
+ sematicAnalyzer.pushScope();
8624
+ for(var _iterator = _create_for_of_iterator_helper_loose(ParserUtils.parseMacroParamList(token.lexeme)), _step; !(_step = _iterator()).done;){
8625
+ var p = _step.value;
8626
+ sematicAnalyzer.symbolTableStack.insert(new SymbolInfo(p, ESymbolType.VAR));
8627
+ }
8628
+ }
8413
8629
  nextToken = tokens.next();
8414
8630
  } else if ((actionInfo == null ? void 0 : actionInfo.action) === EAction.Accept) {
8415
8631
  sematicAnalyzer.acceptRule == null ? void 0 : sematicAnalyzer.acceptRule.call(sematicAnalyzer, sematicAnalyzer);
@@ -9370,7 +9586,7 @@ ShaderCompiler._shaderPositionPool = ShaderCompilerUtils.createObjectPool(Shader
9370
9586
  ShaderCompiler._shaderRangePool = ShaderCompilerUtils.createObjectPool(ShaderRange);
9371
9587
 
9372
9588
  //@ts-ignore
9373
- var version = "2.0.0-alpha.32";
9589
+ var version = "2.0.0-alpha.33";
9374
9590
  var mode = "Release";
9375
9591
  mode = "Verbose";
9376
9592
  console.log("Galacean Engine Shader Compiler Version: " + version + " | Mode: " + mode);