/* * JFLAP - Formal Languages and Automata Package * * * Susan H. Rodger * Computer Science Department * Duke University * August 27, 2009 * Copyright (c) 2002-2009 * All rights reserved. * JFLAP is open source software. Please see the LICENSE for terms. * */ package grammar.parse; import grammar.*; import automata.fsa.*; import java.util.*; import java.io.Serializable; import javax.swing.table.AbstractTableModel; /** * The LRParseTable is an LR(1) parse table. It also has the * ability to function as a TableModel for a javax.swing.JTable. *

* * In this table, entries are either of the form "", "s#", "r#", "acc", or "#", * where # is a number. If a user change is not parseable into one of those * forms, then the entry will be unchanged. * * @author Thomas Finley */ public class LRParseTable extends AbstractTableModel implements Serializable, Cloneable { /** * Instantiates a new LR parse table. * * @param grammar * the augmented grammar * @param fsa * the goto graph for the grammar */ public LRParseTable(Grammar grammar, FiniteStateAutomaton fsa) { ArrayList term = new ArrayList(Arrays.asList(grammar.getTerminals())); ArrayList vars = new ArrayList(Arrays.asList(grammar.getVariables())); this.grammar = grammar; Collections.sort(term); Collections.sort(vars); term.add("$"); terminals = (String[]) term.toArray(new String[0]); variables = (String[]) vars.toArray(new String[0]); for (int i = 0; i < terminals.length; i++) symbolsToColumn.put(terminals[i], new Integer(i + 1)); for (int i = 0; i < variables.length; i++) symbolsToColumn.put(variables[i], new Integer(i + 1 + terminals.length)); entries = new String[fsa.getStates().length][terminals.length + variables.length + 1]; for (int i = 0; i < entries.length; i++) for (int j = 0; j < entries[i].length; j++) entries[i][j] = j == 0 ? Integer.toString(i) : ""; } /** * Instantiates a new LR parse table from another LR parse table. * * @param table * the other LR parse table */ public LRParseTable(LRParseTable table) { terminals = table.terminals; variables = table.variables; grammar = table.grammar; entries = new String[table.entries.length][table.entries[0].length]; for (int i = 0; i < entries.length; i++) for (int j = 0; j < entries[i].length; j++) entries[i][j] = table.entries[i][j]; symbolsToColumn = table.symbolsToColumn; } /** * Returns a clone of this object. * * @return a copy of this object */ public Object clone() { return new LRParseTable(this); } /** * This sets the table value for a state ID and a grammar symbol. * * @param value * the value to put in the table * @param id * the state ID * @param symbol * the grammar symbol * @throws IllegalArgumentException * if symbol is not in the grammar */ public void setValueAt(String value, int id, String symbol) { setValueAt(value, id, columnForSymbol(symbol)); } /** * This will return the value for a given state ID and grammar symbol. * * @param id * the state ID * @param symbol * the grammar symbol * @return the table entry for the state and symbol * @throws IllegalArgumentException * if symbol is not in the grammar */ public String getValueAt(int id, String symbol) { return (String) getValueAt(id, columnForSymbol(symbol)); } /** * Returns the set of parse directives at a particular entry. A set of no * items means that that configuration is considered an error (blank entry * in the table). A set with more than one item indicates ambiguity in the * parse table. * * @param id * the state ID * @param symbol * the grammar symbol * @return the set of parse directives at a location * @throws IllegalArgumentException * if symbol is not in the grammar */ public SortedSet getSetAt(int id, String symbol) { return getSetAt(id, columnForSymbol(symbol)); } /** * Appends the parse table directive to existing parse table directives. * * @param directive * the directive to add * @param id * the state ID * @param symbol * the grammar symbol * @throws IllegalArgumentException * if symbol is not in the grammar */ public void appendValueAt(String directive, int id, String symbol) { appendValueAt(directive, id, columnForSymbol(symbol)); } /** * Returns the column for a particular grammar symbol. * * @param symbol * the grammar symbol * @return the column index of the column corresponding to that grammar * symbol * @throws IllegalArgumentException * if symbol is not in the grammar */ public int columnForSymbol(String symbol) { Integer in = (Integer) symbolsToColumn.get(symbol); if (in == null) { throw new IllegalArgumentException(symbol + " is not in the grammar!"); } return in.intValue(); } // ABSTRACT TABLE MODEL METHODS /** * Returns the number of rows, equal to the number of states in the original * DFA. * * @return the number of rows */ public int getRowCount() { return entries.length; } /** * Returns the number of columns, equals to the number of terminals, * nonterminals, plus one column for displaying the numbers of states. * * @return the number of columns */ public int getColumnCount() { return entries[0].length; } /** * Returns the appropriate string for a value, and a column. * * @param value * the value that was input into the table * @param column * the column that we're trying to edit * @return an improved form of the input, or null if, for * this location, the value could not be reasonably determined */ private String parseValue(String value, int column) { if (column < 1) return null; if (value.equals("")) return ""; if (column > terminals.length) { // It's in the variable section. try { int i = Integer.parseInt(value); return Integer.toString(i); } catch (NumberFormatException e) { return null; } } // It's in the terminal section. value = value.toLowerCase(); switch (value.charAt(0)) { case 'a': return "acc"; case 's': case 'r': if (value.length() < 2) return null; int startDigits = 1; while (!Character.isDigit(value.charAt(startDigits))) startDigits++; try { int i = Integer.parseInt(value.substring(startDigits)); return "" + value.charAt(0) + Integer.toString(i); } catch (NumberFormatException e) { return null; } default: return null; } } /** * Given an entry in the parse table, returns the strings for that entry. If * any entry is not a good entry, it is ignored. * * @param input * the input in the table */ private String[] parseValues(String input, int column) { StringTokenizer st = new StringTokenizer(input); SortedSet values = new TreeSet(); while (st.hasMoreTokens()) { String token = st.nextToken(); token = parseValue(token, column); if (token == null) continue; values.add(token); } return (String[]) values.toArray(new String[0]); } /** * Returns the name of a particular column. * * @param column * the column index * @return the name of the column */ public String getColumnName(int column) { if (column == 0) return " "; if (column > terminals.length) return variables[column - 1 - terminals.length]; return terminals[column - 1]; } /** * Sets the value at a particular row and column. * * @param value * the new value * @param row * the row index * @param column * the column index */ public void setValueAt(Object value, int row, int column) { if (column == 0) { return; } String[] values = parseValues((String) value, column); StringBuffer sb = new StringBuffer(); for (int i = 0; i < values.length; i++) { if (i != 0) sb.append(' '); sb.append(values[i]); } entries[row][column] = sb.toString(); fireTableCellUpdated(row, column); } /** * Appends the parse table directive to existing parse table directives. * * @param directive * the directive to add * @param row * the row index * @param column * the column index */ public void appendValueAt(String directive, int row, int column) { setValueAt(getValueAt(row, column) + " " + directive, row, column); } /** * Returns the value at a particular index. */ public Object getValueAt(int row, int column) { return entries[row][column]; } /** * Returns the set of parse directives at a particular entry. A set of no * items means that that configuration is considered an error (blank entry * in the table). A set with more than one item indicates ambiguity in the * parse table. * * @param row * the row index * @param column * the column index * @return the set of parse directives at a location */ public SortedSet getSetAt(int row, int column) { StringTokenizer st = new StringTokenizer(entries[row][column]); SortedSet set = new TreeSet(); while (st.hasMoreTokens()) set.add(st.nextToken()); return set; } /** * All cells are editable except the first column. * * @param row * the row index * @param column * the column index * @return if the column is not 0 */ public boolean isCellEditable(int row, int column) { return column != 0; } /** * Returns an desciption of a particular entry in a parse table. * * @param entry * the entry * @return the description of that entry */ private String getContentDescription(String entry) { switch (entry.charAt(0)) { case 'a': return "Accept"; case 's': return "Shift current input and state " + entry.substring(1) + " to stack"; case 'r': Production p[] = grammar.getProductions(); int i = Integer.parseInt(entry.substring(1)); String reduceDesc = "Reduce by production " + i + ", "; try { reduceDesc += p[i]; } catch (ArrayIndexOutOfBoundsException e) { reduceDesc += "which does not exist"; } return reduceDesc; default: return "Goto state " + entry; } } /** * Gets a plaintext user readable description of the contents of a cell. * * @param row * the row index * @param column * the column index * @return the plaintext desciption of the parse table entry at this row and * column */ public String getContentDescription(int row, int column) { StringTokenizer st = new StringTokenizer(entries[row][column]); StringBuffer description = new StringBuffer(); int n = 0; while (st.hasMoreTokens()) { String token = st.nextToken(); if ((n++) != 0) description.append('\n'); description.append(getContentDescription(token)); } // Nothing indicates that the string should be rejected. if (description.length() == 0) return "Reject"; return description.toString(); } /** The terminals of the grammar. */ private String[] variables; /** The nonterminals of the grammar, including $ at the end. */ private String[] terminals; /** The entries of the table. */ private String[][] entries; /** The grammar for the parse table. */ private Grammar grammar; /** The mapping of grammar symbols to an Integer indicating the column. */ private Map symbolsToColumn = new HashMap(); }