package scanner; import java.util.HashMap; import java.util.Stack; public class Scanner { HashMap charCategories; HashMap> transitions; HashMap tokenTypes; String s0; /** * Builds the tables needed for the scanner. */ public Scanner(TableReader tableReader) { charCategories = new HashMap<>(); transitions = new HashMap<>(); tokenTypes = new HashMap<>(); // Build catMap, mapping a character to a category. for (TableReader.CharCat cat : tableReader.getClassifier()) charCategories.put(cat.getC(), cat.getCategory()); // Build the transition table. Given a state and a character category, // give a new state. for (TableReader.Transition t : tableReader.getTransitions()) { if (s0 == null) s0 = t.getFromStateName(); if (!transitions.containsKey(t.getFromStateName())) transitions.put(t.getFromStateName(), new HashMap()); transitions.get(t.getFromStateName()) .put(t.getCategory(), t.getToStateName()); } // Build the token types table for (TableReader.TokenType tt : tableReader.getTokens()) tokenTypes.put(tt.getState(), tt.getType()); } /** * Returns the category for c or "not in alphabet" if c has no category. Do * not hardcode this. That is, this function should have nothing more than a * table lookup or two. You should not have any character literals in here * such as 'r' or '3'. */ public String getCategory(Character c) { if (charCategories.containsKey(c)) return charCategories.get(c); return "not in alphabet"; } /** * Returns the new state given a current state and category. This models * the transition table. Returns "error" if there is no transition. * Do not hardcode any state names or categories. You should have only * table lookups here. */ public String getNewState(String state, String category) { if (transitionExists(state, category)) return transitions.get(state).get(category); return "error"; } private boolean transitionExists(String state, String category) { if (transitions.containsKey(state)) { HashMap transitionTable = transitions.get(state); return transitionTable.containsKey(category); } return false; } /** * Returns the type of token corresponding to a given state. If the state * is not accepting then return "error". * Do not hardcode any state names or token types. */ public String getTokenType(String state) { if (tokenTypes.containsKey(state)) return tokenTypes.get(state); return "error"; } /** * Return the next token or null if there's a lexical error. */ public Token nextToken(ScanStream ss) { String state = s0; String lexeme = ""; Stack stack = new Stack<>(); stack.push("bad"); while (!state.equals("error") && !ss.eof()) { char c = ss.next(); lexeme += c; if (tokenTypes.containsKey(state)) stack.clear(); stack.push(state); state = getNewState(state, getCategory(c)); } while (!tokenTypes.containsKey(state) && !state.equals("bad")) { state = stack.pop(); if (lexeme.length() > 0) lexeme = lexeme.substring(0, lexeme.length() - 1); ss.rollback(); } if (tokenTypes.containsKey(state)) return new Token(tokenTypes.get(state), lexeme); return null; } }