Bottom-up parsing is a strategy used by compilers to analyse source code by building the parse tree from the leaves (terminal symbols) up to the root (start symbol). The most common bottom-up technique is shift-reduce parsing, which uses a stack and a set of production rules. At each step the parser either shifts the next input symbol onto the stack, or reduces the top of the stack by replacing a substring that matches the right-hand side of a production rule with the corresponding left-hand side symbol.
This C++ program implements a simple shift-reduce parser. It reads a set of production rules and an input string from the user, then processes the string step by step, displaying the stack contents, remaining input, and the action taken (Shifted or Reduced) at every stage.
C++ Code
#include <conio.h>
#include <iostream.h>
#include <string.h>
/* Structure to hold one grammar production rule */
struct grammer {
char p[20]; /* LHS: left-hand side non-terminal (e.g. "E") */
char prod[20]; /* RHS: right-hand side string (e.g. "E+E") */
} g[10]; /* up to 10 production rules */
void main() {
int i, stpos, j, k, l, m, o, p, f, r;
int np, tspos, cr;
/* --- Step 1: Read production rules --- */
cout << "\nEnter Number of productions:";
cin >> np;
char sc, ts[10];
cout << "\nEnter productions:\n";
for (i = 0; i < np; i++) {
cin >> ts; /* e.g. "E->E+E" */
strncpy(g[i].p, ts, 1); /* first character is the LHS non-terminal */
strcpy(g[i].prod, &ts[3]); /* skip "X->" prefix to get the RHS */
}
/* --- Step 2: Read the input string to be parsed --- */
char ip[10];
cout << "\nEnter Input:";
cin >> ip;
int lip = strlen(ip); /* length of input string */
char stack[10];
stpos = 0;
i = 0;
/* Shift first input symbol onto the stack to start */
sc = ip[i];
stack[stpos] = sc;
i++; stpos++;
/* --- Step 3: Print the parsing table header --- */
cout << "\n\nStack\tInput\tAction";
/* --- Step 4: Main parse loop --- */
do {
r = 1; /* r=1 means we just shifted; r=2 means we just reduced */
/* Inner loop: keep reducing while a reduction is possible */
while (r != 0) {
cout << "\n";
/* Print current stack contents */
for (p = 0; p < stpos; p++) {
cout << stack[p];
}
cout << "\t";
/* Print remaining input (from current position to end) */
for (p = i; p < lip; p++) {
cout << ip[p];
}
/* Print the action performed in the PREVIOUS iteration */
cout << (r == 2 ? "\tReduced" : "\tShifted");
r = 0;
getch(); /* pause for step-by-step viewing */
/* Try to reduce: check every substring of the stack against all rules */
for (k = 0; k < stpos; k++) {
f = 0;
/* Clear temporary buffer */
for (l = 0; l < 10; l++) ts[l] = '';
/* Extract stack substring starting at position k */
tspos = 0;
for (l = k; l < stpos; l++) {
ts[tspos] = stack[l];
tspos++;
}
/* Compare extracted substring against each production RHS */
for (m = 0; m < np; m++) {
cr = strcmp(ts, g[m].prod);
if (cr == 0) {
/* Match found: reduce by removing the matched symbols
from the stack and pushing the LHS non-terminal */
for (l = k; l < 10; l++) {
stack[l] = '';
stpos--;
}
stpos = k;
strcat(stack, g[m].p); /* push LHS onto stack */
stpos++;
r = 2; /* flag: a reduction was performed */
}
}
}
}
/* No more reductions possible: shift next input symbol */
sc = ip[i];
stack[stpos] = sc;
i++; stpos++;
} while (strlen(stack) != 1 && stpos != lip);
/* --- Step 5: Accept or reject the input --- */
if (strlen(stack) == 1) {
cout << "\n String Accepted";
}
getch();
}
How the Code Works
- Grammar input: Each production is entered in the form
LHS->RHS(e.g.E->E+E). The program splits this at the->separator: the LHS character is stored ing[i].pand the RHS string ing[i].prod. - Initial shift: Before entering the main loop, the first character of the input is pushed onto the stack to ensure the loop has something to work with.
- Reduce phase: The inner
while(r != 0)loop repeatedly tries every possible substring of the stack against all RHS patterns. When a match is found (strcmp == 0), the matched portion is removed from the stack and the LHS symbol is pushed in its place. This continues until no more reductions are possible (rstays 0). - Shift phase: After all reductions are exhausted, the next input character is shifted onto the stack and the outer
do-whileloop repeats. - Accept condition: The parse succeeds if the stack is reduced to a single symbol (the grammar start symbol) after all input has been consumed.
Sample Input / Output
Enter Number of productions:4
Enter productions:
E->E+E
E->E*E
E->(E)
E->a
Enter Input:(a+a)*a
Stack Input Action
( a+a)*a Shifted
(a +a)*a Shifted
(E +a)*a Reduced
(E+ a)*a Shifted
(E+a )*a Shifted
(E+E )*a Reduced
(E )*a Reduced
(E) *a Shifted
E *a Reduced
E* a Shifted
E*a Shifted
E*E Reduced
E Reduced
String Accepted
Output Explanation
(Shifted: The opening parenthesis is pushed onto the stack; remaining input isa+a)*a.(aShifted: The terminalais shifted. Stack is now(a.(EReduced: The top of the stack matches the RHS ofE->a, soais replaced byE.(E+Shifted,(E+aShifted,(E+EReduced:+is shifted, thenais shifted and immediately reduced toEviaE->a.(EReduced: The three-symbol substringE+EmatchesE->E+E, reducing it to a singleE. Stack becomes(E.EReduced: After)is shifted ((E)on the stack), the ruleE->(E)fires, collapsing all three symbols toE.- Multiplication:
*andaare shifted;areduces toE; thenE*Ereduces viaE->E*E. - Acceptance: The stack contains a single
E(the start symbol) and all input is consumed — the string is accepted.
See Also
- Implementing Lexical Analyser in C++
- Implementing Code Generator in C++
- Implementing Macro Processor in C
- Implementing Multi-pass Assembler in C
- Implementing Absolute Loader in C++
Conclusion
This shift-reduce parser illustrates the fundamental mechanism behind LALR(1) parsers — the engine that powers tools like yacc and Bison. The core insight is that every parse is a sequence of shift and reduce decisions driven by the current stack top and the lookahead symbol. In a production parser these decisions are encoded in a pre-computed parse table; here they are computed by brute-force substring matching, which keeps the code readable and easy to trace. Pairing this parser with the lexical analyser gives you a complete front-end that can be connected to the code generator to form a minimal compiler pipeline.
The article lets the users know about the implementation of the programming language with some properties to shift and reduce the commands. Hence the soders will be much helpful to implement the properties.
hi, i’m really newbie at this and i just wanna ask, did you use table parsing to create this program?