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

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