LR parsing reads tokens from left to right and constructs the reverse of a rightmost derivation via shift and reduce operations. It first accumulates the structures it has seen, then decides which production rule these symbols belong to. Unlike naive recursive descent, deterministic LR can directly handle many left-recursive grammars.
The runtime is typically just a state stack plus a table-lookup loop; the complex parts lie in generating the tables, understanding conflicts, and designing semantic actions. Small grammars can be constructed by hand, but in engineering, they are usually generated by tools.
Shift, Reduce, and the State Stack
Consider the grammar E → E + n | n and the input n + n: first shift n, then reduce it to E; subsequently shift the + and the second n, and finally reduce E + n to E. AST nodes can be constructed simultaneously during reduction.
Action
Does input advance?
Changes to stack and semantic values
shift s
Consumes one token
Pushes its semantic value and state s
reduce A → β
Does not consume lookahead
Pops content corresponding to β, computes A's value, then pushes the new state via GOTO
accept
Stops
Start symbol is complete and meets termination conditions
error
Usually has not consumed current lookahead yet
Reports error or initiates recovery
If the state stack and semantic value stack are maintained separately, reducing a right-hand side of length r pops r items from each; the state stack bottom still retains the start state. If states and grammar symbols are interleaved, the description of the number of popped items differs. An empty production pops 0 items but still requires entering the post-reduction state via GOTO.
Items: A dot indicates how far a production has been recognized
The item E → E · + n indicates that an E has been recognized, and the next expected tokens are + and n; E → E + n · indicates that the right-hand side is complete, so a reduction can be considered.
A state often contains multiple items, representing that the current prefix might be in various syntactic contexts. It records information about viable prefixes, not just a single source code token.
For LR(0) item sets, when the closure encounters A → α · B β, it adds all productions B → · γ for B and recursively expands them. goto(I, X) first moves the items in I where the dot precedes X one position to the right, then takes the closure. Continuously executing goto on new sets constructs the item set automaton.
Edges on terminals form shift actions, while edges on non-terminals form GOTO actions. The key difference among LR variants lies in which lookahead symbols trigger reductions for completed items.
Running n+n through five states: After reduction, return to the old state, then check GOTO
Number the rules for the initial grammar: Rule 1 is E→E+n, Rule 2 is E→n. Use the following state numbers, which are manually specified for this example and do not need to match generator output.
State
n
+
$
GOTO(E)
0
shift 2
error
error
1
1
error
shift 3
accept
—
2
error
reduce 2
reduce 2
—
3
shift 4
error
error
—
4
error
reduce 1
reduce 1
—
The closure of state 0 contains E→·E+n and E→·n; after recognizing E, it goes to state 1, expecting + or end-of-input; after recognizing n, it goes to state 2, where it can reduce E→n. State 3 represents E→E+·n, and state 4 completes E→E+n·. Since FOLLOW(E)={+, $}, the corresponding reductions are filled in for these two lookaheads.
After reading the second n, the stack is [0,1,3,4]. Reducing length 3 pops back to [0], then checks GOTO(0,E)=1. The lookahead is still $, and the accept action happens in the next step. You cannot check GOTO using the pre-reduction stack top 4, nor can you consume $ just because a reduction was executed.
This example has no empty rules, so length in the code is always greater than zero. When extending to ε, you must specifically handle zero length; in Python, del stack[-0:] is equivalent to clearing the entire stack, not "popping nothing." This is a boundary condition easily missed when copying algorithm descriptions directly into slice code.
Why does reduction change the stack without consuming lookahead?
Inspect reduction with both pre-pop and post-pop stacks visible. Lookahead has its own position: shift moves it; reduce removes the RHS and uses the remaining top for GOTO. Select any action or play the trace. Invalid input follows the same table and stops at a missing entry rather than skipping errors for a tidy demonstration.
Preparing the visual
Why does reduction change the stack without consuming lookahead?
Shift consumes a token. Reduction pops the RHS and consults GOTO using the remaining top state, without consuming lookahead.
{"id":"lr-stack-and-lookahead","title":"Why does reduction change the stack without consuming lookahead?","summary":"Shift consumes a token. Reduction pops the RHS and consults GOTO using the remaining top state, without consuming lookahead.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"input\"><button data-mode=\"short\" data-i18n=\"short\"></button><button data-mode=\"long\" data-i18n=\"long\"></button><button data-mode=\"bad\" data-i18n=\"bad\"></button></div><div class=\"control\"><label for=\"cursor\" data-i18n=\"cursor\"></label><output for=\"cursor\" id=\"cursor-value\"></output><input id=\"cursor\" type=\"range\" min=\"0\" max=\"6\" value=\"2\" step=\"1\"></div><div class=\"actions\"><button id=\"play\" data-i18n=\"play\"></button></div><section class=\"panel\"><h3 data-i18n=\"before\"></h3><div id=\"before\"></div></section><section class=\"panel\"><h3 data-i18n=\"after\"></h3><div id=\"after\"></div></section><div class=\"metrics\"><div><span data-i18n=\"look\"></span><strong id=\"look\"></strong></div></div><p id=\"action\" class=\"log\" role=\"status\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.actions{grid-template-columns:1fr}#before,#after{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:4px}.metrics strong{font-size:18px}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}let mode='short',trace=[];const G={0:1},T={0:{n:['shift',2]},1:{'+':['shift',3],'$':['accept']},2:{'+':['reduce',1],'$':['reduce',1]},3:{n:['shift',4]},4:{'+':['reduce',3],'$':['reduce',3]}};function build(){const input={short:['n','+','n','$'],long:['n','+','n','+','n','$'],bad:['n','+','+','n','$']}[mode];let stack=[0],p=0;trace=[];for(let guard=0;guard<30;guard++){const before=[...stack],look=input[p],a=T[stack.at(-1)][look]||['error'];let detail=a[0],old=p;if(a[0]==='shift'){stack.push(a[1]);p++;detail+=' '+a[1]}else if(a[0]==='reduce'){stack.splice(-a[1]);const top=stack.at(-1),go=G[top];stack.push(go);detail+=' E → '+(a[1]===1?'n':'E + n')+'\\npop '+a[1]+' → '+fmt(stack.slice(0,-1))+'\\nGOTO('+top+', E) = '+go}trace.push({before,after:[...stack],look,next:input[p],detail,p:old,newp:p});if(['accept','error'].includes(a[0]))break}$('#cursor').max=trace.length-1;$('#cursor').value=Math.min(+$('#cursor').value,trace.length-1);}function draw(){const i=+$('#cursor').value,r=trace[i];set('cursor-value',(i+1)+' / '+trace.length);for(const id of ['before','after'])cells(id,Array.from({length:6},(_,j)=>({text:r[id][j]??'·',cls:j<r[id].length?'on':'empty'})));set('look',r.look+' → '+r.next);set('action',r.detail);$('#action').dataset.position=r.p;$('#action').dataset.next=r.newp;$('#after').dataset.stack=r.after.join(',');pressed(mode);}const playback=viz.playback({delay:1400,advance(){if(+$('#cursor').value<trace.length-1){$('#cursor').value=+$('#cursor').value+1;draw()}},canAdvance(){return+$('#cursor').value<trace.length-1},onChange(p){set('play',t(p?'pause':'play'))}});$('#play').onclick=()=>playback.toggle();$('#cursor').oninput=()=>{playback.pause();draw()};document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{playback.pause();mode=b.dataset.mode;build();draw()});build();draw();","audio":false,"strings":{"action":"Current action","after":"After: state stack","bad":"n + + n","before":"Before: state stack","cursor":"Action to inspect","heading":"Why does reduction change the stack without consuming lookahead?","input":"Input","intro":"Select any action and compare before/after stacks. Playback only moves the observation point; manual selection is always available.","long":"n + n + n","look":"Lookahead change","pause":"Pause","play":"Play stack changes","reset":"Reset","scope":"Fixed grammar E→E+n / n and a five-state table, no epsilon rules. Execution is table-driven; controls inspect its snapshots. Not a general LR generator.","scopeLabel":"Model scope and assumptions","short":"n + n"}}
How Much Context Do SLR, LR(1), and LALR Preserve?
Method
Source of Reduction Lookahead
Information That May Be Lost
LR(0)
Completed items do not distinguish lookahead terminals
Coarsest, prone to conflicts
SLR(1)
Uses global FOLLOW(A) for reducing A
FOLLOW mixes different occurrences
Canonical LR(1)
Items carry contextual lookahead
Preserves finer context, but may have more states
LALR(1)
Merges LR(1) states with identical LR(0) cores and merges lookaheads
Contexts with identical cores but different origins are mixed
The closure of an LR(1) item [A → α · B β, a] uses terminals from FIRST(βa) as the lookahead for productions of B. a represents what is allowed to follow after A completes; it cannot be simply understood as the character immediately to be read after the dot.
Canonical LR(1) can handle more grammars than LALR(1), but still not all context-free grammars. Ambiguous grammars cannot yield a unique analysis by deterministic LR tables without disambiguation rules.
An Example That Is LR(1) But Not LALR(1)
S → a A d | b A e | a B e | b B d
A → c
B → c
This grammar only generates four strings: acd, bce, ace, bcd, each with a unique derivation. After reading ac, if followed by d, c should be reduced to A; if followed by e, it should be reduced to B; after reading bc, it is exactly the opposite.
This shows that conflicts may arise from the compression of context in table construction; seeing a reduce/reduce conflict does not immediately imply the original grammar is ambiguous. SLR's global FOLLOW also suffers from similar precision issues. The Bison manual specifically discusses conflicts caused by LALR limitations and methods such as IELR or canonical LR to address them. Mysterious Conflicts
Distinguishing "Grammar Conflicts" from "Merge-Induced Conflicts" Using a Generator
Write the above four-string grammar as conflict.y, using single quotes for character terminals:
%start S
%%
S: 'a' A 'd' | 'b' A 'e' | 'a' B 'e' | 'b' B 'd';
A: 'c';
B: 'c';
%%
This step only generates tables and reports; no lexer is needed, and it is not yet a runnable program. LALR should show two reduce/reduce conflicts, while canonical LR can preserve the different contexts after ac and bc without such conflicts. When checking the reports, locate the lookaheads for the completed items A→c· and B→c·; do not just compare the total number of states.
If you continue using the default LALR with conflicts, some strings may take the wrong reduction path; therefore, "the tool still generated a C file" does not mean the language has been correctly implemented. In engineering, you should explicitly choose an appropriate construction method or rewrite the grammar, rather than ignoring warnings. Algorithm Description in Bison Manual Source
Which correspondence is lost when states merge?
Before merging, each origin preserves a prefix/item/lookahead correspondence. After unioning lookaheads, A and B no longer retain which prefix supplied d. Both original rows stay visible to expose the lost information. Each of the four strings still has one derivation in the original grammar; a table conflict is not proof of ambiguity.
Preparing the visual
Which correspondence is lost when states merge?
After ac and bc the LR(0) cores match, but A/B lookaheads are reversed. Unioning lookaheads makes both reductions compete on d and e, losing provenance pairing.
{"id":"lalr-context-merge","title":"Which correspondence is lost when states merge?","summary":"After ac and bc the LR(0) cores match, but A/B lookaheads are reversed. Unioning lookaheads makes both reductions compete on d and e, losing provenance pairing.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"presets\" role=\"group\" data-i18n-label=\"prefix\"><button data-mode=\"ac\" data-i18n=\"ac\"></button><button data-mode=\"bc\" data-i18n=\"bc\"></button></div><label class=\"form-field\"><span data-i18n=\"look\"></span><select id=\"look\"><option>d</option><option>e</option></select></label><label class=\"check\"><input type=\"checkbox\" id=\"merge\"><span data-i18n=\"merge\"></span></label><div id=\"contexts\" class=\"table\"></div><section class=\"panel\"><h3 data-i18n=\"candidates\"></h3><div id=\"candidates\"></div></section><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"ok\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"conflict\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n#candidates{min-height:3.6em;font:16px/1.8 var(--viz-font-ui)}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}let prefix='ac';function draw(){const look=$('#look').value,merged=$('#merge').checked;table('contexts',[['prefix','A → c ·','B → c ·'],[{text:'ac',cls:prefix==='ac'?'on':''},'{d}','{e}'],[{text:'bc',cls:prefix==='bc'?'on':''},'{e}','{d}'],['merge','{d,e}','{d,e}']]);const a=prefix==='ac'?look==='d':look==='e',choices=merged?['A → c','B → c']:[a?'A → c':'B → c'];set('candidates',choices.join('\\n'));$('#candidates').dataset.count=choices.length;set('explanation',t(merged?'conflict':'ok'));pressed(prefix);}$('#look').onchange=draw;$('#merge').onchange=draw;document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{prefix=b.dataset.mode;draw()});draw();","audio":false,"strings":{"ac":"ac","bc":"bc","candidates":"Current reduction candidates","conflict":"Merging creates a reduce/reduce conflict; the original grammar remains unambiguous.","context":"Original LR(1) contexts","heading":"Which correspondence is lost when states merge?","intro":"Select the read prefix and next character; compare LR(1) contexts with merged candidates.","look":"Next character","merge":"Merge states with identical cores","ok":"With provenance preserved, this input has one reduction.","prefix":"Read prefix","reset":"Reset","scope":"Grammar S→aAd / bAe / aBe / bBd, A→c, B→c. Each of four strings has one derivation. Shows the critical completed items, not the entire table.","scopeLabel":"Model scope and assumptions"}}
Distinguishing Two Types of Conflicts and Resolution Basis
A shift/reduce conflict means that under the same state and lookahead, both shifting and reducing are possible. For example, E → E + E | E * E | n does not express precedence, so n + n * n can have different tree structures. You can rewrite it as a layered grammar or use the tool's precedence and associativity declarations to express language rules.
A reduce/reduce conflict means there are multiple candidate reductions at the same position. It could be true ambiguity or the result of context merging mentioned above. You should examine the items, lookaheads, shortest triggering input, and counterexamples, rather than simply suppressing warnings.
Precedence declarations are semantic choices, not proofs of correctness for arbitrary conflicts. Especially when two reductions perform different semantic actions, defaulting to one rule may change the AST or side effects.
Bison and LALRPOP: Names Do Not Equal Algorithms
Bison supports configurations such as LALR, IELR, canonical LR, and GLR. Although LALRPOP's name contains LALR, the default algorithm in its project documentation is LR(1). You cannot uniformly label all tools as "default LALR," nor can you stipulate a fixed ratio for the number of states between canonical LR and LALR. Bison Grammar and Parsing Capabilities, LALRPOP Project Documentation
GLR can preserve multiple analysis branches at conflict points, later pruning or merging them, but ambiguous semantics still need to be defined, and the worst-case cost differs from deterministic LR. Delayed semantic actions, shared tree representations, and error handling all require additional design.
Error Recovery: error is a Special Mechanism
In Bison, you can write such a recovery production:
statement: error ';' { yyerrok; };
error is a special symbol for the parser, not a rule for "any character" returned by the lexer. During recovery, the parser can pop the stack until a state can shift error, then discard input as needed to find a token that allows continuation. Synchronizing to a semicolon is just an exit provided by this rule; not all errors naturally stop at a semicolon.
Resources for discarded symbols must be properly released; semantic values after recovery should also have clear representations to avoid subsequent stages treating error structures as normal nodes. Some recoveries temporarily suppress cascading diagnostics; do not treat "parser return" as proof that the source code is completely correct. Bison Error Recovery
Engineering Judgment
The table-lookup process of deterministic LR is typically linear with respect to input length, provided there is a fixed grammar, no cyclic empty reductions, and controlled semantic actions; the costs of table construction and error recovery are separate. Incremental editing support also depends on tree structure, reuse strategies, and lexical context; you cannot judge solely based on LL/LR labels.
When understanding conflicts, first reconstruct "read prefix, current item, one lookahead, candidate actions," then determine whether it is a language rule, grammar expression, or algorithm precision issue. Continue reading AST Design and Error Recovery.