mirror of
https://github.com/galacean/engine.git
synced 2026-07-01 01:54:20 +08:00
fix(shader-compiler): resolve ( conflict explicitly, not via add order
`_addAction`'s ELSE-only handler left the second shift/reduce conflict introduced by #2974 (`type_specifier_nonarray → macro_call_symbol` on `(` lookahead) to be resolved by whichever action `_inferNextState` happens to register last. Today that's Shift (correct), because the first loop writes Reduce actions and the second loop writes Shift — Shift overwrites. But the resolution is implicit: any future refactor that flips the registration order would silently route every `MacroCall(args)` to a type-position reduce, breaking macro-as-type shaders without warning. Promote the existing ELSE handler into a `_isKnownShiftPreferred` whitelist matching both conflicts: - terminal === Keyword.ELSE (dangling-else) - terminal === LEFT_PAREN AND reduce target is the precise `type_specifier_nonarray → macro_call_symbol` production Resolution is order-independent by construction: - Shift first, Reduce second → early-return keeps Shift - Reduce first, Shift second → falls through to `table.set`, Shift overwrites Any future conflict not in the whitelist still emits the verbose `conflict detect` warning, so grammar/runtime drift stays loud. This pairs with `%expect 2` in TargetParser.y (previous commit): the bison directive asserts the conflict count at grammar level, this commit makes the LALR builder's resolution deterministic at code level.
This commit is contained in:
@@ -2,6 +2,7 @@ import { ETokenType } from "../common";
|
||||
import { Keyword } from "../common/enums/Keyword";
|
||||
import { Grammar } from "../parser/Grammar";
|
||||
import { GrammarSymbol, NoneTerminal, Terminal } from "../parser/GrammarSymbol";
|
||||
import Production from "./Production";
|
||||
import State from "./State";
|
||||
import StateItem from "./StateItem";
|
||||
import { default as GrammarUtils, default as Utils } from "./Utils";
|
||||
@@ -157,9 +158,14 @@ export class LALR1 {
|
||||
private _addAction(table: ActionTable, terminal: Terminal, action: ActionInfo) {
|
||||
const exist = table.get(terminal);
|
||||
if (exist && !Utils.isActionEqual(exist, action)) {
|
||||
// Resolve dangling else ambiguity
|
||||
if (terminal === Keyword.ELSE && exist.action === EAction.Shift && action.action === EAction.Reduce) {
|
||||
return;
|
||||
// Known shift-preferred conflicts (see TargetParser.y `%expect 2`).
|
||||
// Enforce shift regardless of the order `_inferNextState` registers
|
||||
// actions: when `exist` is already Shift and `action` is Reduce, keep
|
||||
// `exist` (early-return); the reverse order (`exist` Reduce, `action`
|
||||
// Shift) falls through to `table.set` below and Shift overwrites
|
||||
// Reduce. Order-independent by construction.
|
||||
if (LALR1._isKnownShiftPreferred(terminal, exist, action)) {
|
||||
if (exist.action === EAction.Shift && action.action === EAction.Reduce) return;
|
||||
} else {
|
||||
// #if _VERBOSE
|
||||
console.warn(
|
||||
@@ -174,6 +180,28 @@ export class LALR1 {
|
||||
table.set(terminal, action);
|
||||
}
|
||||
|
||||
// Catalog of expected shift/reduce conflicts. Each entry must correspond to
|
||||
// one of TargetParser.y's `%expect`-ed conflicts; any new conflict not in
|
||||
// this list falls through to the verbose `conflict detect` warning so the
|
||||
// grammar/runtime drift is loud rather than silent.
|
||||
// - ELSE: dangling-else, bind to nearest `if`
|
||||
// - '(' + `type_specifier_nonarray → macro_call_symbol`: macro-as-type-alias
|
||||
// (#2974), prefer the expression-position `macro_call_function` over the
|
||||
// type-position reduce
|
||||
private static _isKnownShiftPreferred(terminal: Terminal, exist: ActionInfo, action: ActionInfo): boolean {
|
||||
if (terminal === Keyword.ELSE) return true;
|
||||
if (terminal !== ETokenType.LEFT_PAREN) return false;
|
||||
const reduce = exist.action === EAction.Reduce ? exist : action.action === EAction.Reduce ? action : null;
|
||||
if (!reduce) return false;
|
||||
const prod = Production.pool.get(reduce.target!);
|
||||
return (
|
||||
!!prod &&
|
||||
prod.goal === NoneTerminal.type_specifier_nonarray &&
|
||||
prod.derivation.length === 1 &&
|
||||
prod.derivation[0] === NoneTerminal.macro_call_symbol
|
||||
);
|
||||
}
|
||||
|
||||
// https://people.cs.pitt.edu/~jmisurda/teaching/cs1622/handouts/cs1622-first_and_follow.pdf
|
||||
private computeFirstSet() {
|
||||
for (const production of this.grammar.productions.slice(1)) {
|
||||
|
||||
Reference in New Issue
Block a user