import java.util.*; /** * Shift-reduce parser * * @author Nobo Komagata * @version 5/30/03 */ public class ShiftReduceParser { // Grammar rules (productions) based on grammarArea Grammar grammar; // Input words based on inputArea Input input; // For shift-reduce parsing ParseStack stack = new ParseStack(); // For backtracking and alternatives handling private ConfigStack configStack = new ConfigStack(); // Temporary storage for possible reductions private Reductions reductions; // Current private int curReduction; // Status private boolean reductionOnHold = false; /** * Constructor for objects of class ShiftReduceParser */ public ShiftReduceParser(String grammarArea, String inputStr) { grammar = new Grammar(grammarArea); input = new Input(inputStr); } /** * Shift operation * * @param None * @return Message */ public String shift() { if (input.length() < 1) return "Warning: Nothing to shift"; // save the current config for backtracking configStack.pushS(new Configuration((ParseStack) stack.clone(), (Input) input.clone())); String current = input.at(0); stack.pushS(new Category(current, null)); // no semantics at this point input.removeAt(0); return "Info: Shifted = " + current; } /** * Reduce operation * * @param None * @return Message */ public String reduce() { // When "reduce" button is pressed after alternatives have been found // (not applicable to the first time press of "reduce") if (reductionOnHold) { Production prod = grammar.productions.at(reductions.at(curReduction).productionID); // save the current config for backtracking configStack.pushS(new Configuration((ParseStack) stack.clone(), (Input) input.clone())); // syntax-directed translation based on the Production String sem = translate(prod); for (int i = 0; i < reductions.at(curReduction).handleLen; ++i) { stack.popS(); } stack.pushS(new Category(prod.lhs.type, sem)); reductionOnHold = false; return "Info: Reduction = " + prod.toString(); } // Find and keep alternative reductions int[] match = new int[grammar.productions.length()]; reductions = new Reductions(); for (int i = 0; i < grammar.productions.length(); ++i) { // Identify "handle" matches like the following // Stack: ..., X, Y // RHS: X Y match[i] = lengthOfMatchedHandle(stack, grammar.productions.at(i).rhs); if (match[i] > 0) // Reduction cases are recorded with the folloing info: // 1. Production ID // 2. Length of the matched handle reductions.add(new Reduction(i, match[i])); } if (reductions.length() == 0) return "Warning: Nothing to reduce"; // No alternatives; execute the current one if (reductions.length() == 1) { // save the current config for backtracking configStack.pushS(new Configuration((ParseStack) stack.clone(), (Input) input.clone())); Production prod = grammar.productions.at(reductions.at(0).productionID); // syntax-directed translation based on the Production String sem = translate(prod); for (int i = 0; i < reductions.at(0).handleLen; ++i) { stack.popS(); } stack.pushS(new Category(prod.lhs.type, sem)); return "Info: Reduction = " + prod.toString(); } // Alternatives exist; Set the first available as the current reductionOnHold = true; curReduction = 0; Production prod = grammar.productions.at(reductions.at(curReduction).productionID); return "Info: Press REDUCE to apply " + prod.toString() + "or press ALTERNATIVE"; } /** * Find the length of the matched handle * * @param Parse stack, RHS of the current production * @return Length (0 if not matched) */ private int lengthOfMatchedHandle(ParseStack stack, Category[] rhs) { // Work on a copy to keep the original intact ParseStack copy = (ParseStack) stack.clone(); // RHS = [cat1, cat2, ..., catN] // match catN with the stack top, etc. for (int i = rhs.length - 1; i >= 0; --i) { if (copy.emptyS()) return 0; // tmp: type of the last category of the RHS // (the index part "_#" must be removed) StringTokenizer tok = new StringTokenizer(rhs[i].type); String tmp = tok.nextToken("_"); if (!copy.peekS().type.equals(tmp)) return 0; else copy.popS(); } return rhs.length; } /** * Translate the relevant portion of the input based on * the current Production * * Example: * * @param Production * @return String */ private String translate(Production prod) { // Work on a copy to keep the original intact ParseStack copy = (ParseStack) stack.clone(); String sem = new String(prod.lhs.sem); if (!copy.emptyS()) { for (int i = prod.rhs.length - 1; i >=0; --i) { Category cat = copy.popS(); if (cat.sem == null) continue; sem = replaceStr(sem, prod.rhs[i].type, cat.sem); } } return sem; } /** * Replace all the instances of the specified string with another * * @param Text String, "From" String, "To" String * @return String */ private String replaceStr(String str, String from, String to) { int pos = str.indexOf(from); if (pos < 0) return str; if (str.length() > pos + from.length() && Character.isLetter(str.charAt(pos + from.length()))) return str.substring(0, pos) + from + replaceStr(str.substring(pos + from.length()), from, to); else return str.substring(0, pos) + to + replaceStr(str.substring(pos + from.length()), from, to); } /** * Move to the next alternative * * @param None * @return Message */ public String alternative() { if (!reductionOnHold) return "Warning: No alternative available"; ++curReduction; if (curReduction >= reductions.length()) curReduction = 0; Production prod = grammar.productions.at(reductions.at(curReduction).productionID); return "Info: Press REDUCE to apply " + prod.toString() + "or press ALTERNATIVE"; } /** * Backtrack to the previous configuration * * @param None * @return Message */ public String backtrack() { if (configStack.emptyS()) return "Warning: No more backtracking"; Configuration config = configStack.popS(); stack = config.stack; input = config.input; return "Info: Backtracking successful"; } }