/* * 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 java.util.*; import javax.swing.table.AbstractTableModel; import java.io.Serializable; /** * The LLParseTable is a parse table for LL grammars. It also has * the ability to function as a TableModel for a javax.swing.JTable. * Variables are in the rows, lookahead terminals in the columns. In its * capacity as a TableModel the first row is taken up with the * names of the variables. * * @author Thomas Finley */ public class LLParseTable extends AbstractTableModel implements Serializable, Cloneable { /** * Instantiates a new LLParseTable for a given grammar. * * @param grammar * the grammar to create the table for */ public LLParseTable(Grammar grammar) { variables = grammar.getVariables(); Arrays.sort(variables); terminals = grammar.getTerminals(); Arrays.sort(terminals); entries = new SortedSet[variables.length][terminals.length + 1]; for (int i = 0; i < entries.length; i++) for (int j = 0; j < entries[i].length; j++) entries[i][j] = new TreeSet(); } /** * Copy constructor for a LL parse table. * * @param table * the table to copy */ public LLParseTable(LLParseTable table) { variables = table.variables; terminals = table.terminals; entries = new SortedSet[variables.length][terminals.length + 1]; for (int i = 0; i < entries.length; i++) for (int j = 0; j < entries[i].length; j++) entries[i][j] = new TreeSet(table.entries[i][j]); } /** * Clone method for the LL parse table. * * @return a copy of this table */ public Object clone() { return new LLParseTable(this); } /** * Returns true if this object equals another. * * @param object * the object to compare against */ public boolean equals(Object object) { try { LLParseTable other = (LLParseTable) object; if (!Arrays.equals(variables, other.variables)) return false; if (!Arrays.equals(terminals, other.terminals)) return false; for (int i = 0; i < variables.length; i++) if (!Arrays.equals(entries[i], other.entries[i])) return false; return true; } catch (ClassCastException e) { return false; } } /** * Returns a hashcode for this table. * * @return the hashcode for this parse table */ public int hashCode() { // Lazy, stupid, and dangerous, but unlike me has the virtue // of working... return variables.length ^ terminals.length; } /** * Returns a 2 integer array of the row and column of a variable and * lookahead, in that order. * * @param variable * the variable to look for * @param lookahead * the lookahead terminal * @throws IllegalArgumentException * if either variable or lookahead is not a variable or terminal * (or $) respectively in the grammar */ private int[] getLocation(String variable, String lookahead) { int[] r = new int[2]; r[0] = getRow(variable); r[1] = getColumn(lookahead) - 1; return r; } /** * Returns the row location in the table model of the variable. * * @param variable * the variable to find the row for * @return the row where this variable is * @throws IllegalArgumentException * if the variable is not a variable in the grammar */ public int getRow(String variable) { int row = Arrays.binarySearch(variables, variable); if (row < 0) throw new IllegalArgumentException(variable + " is not a variable!"); return row; } /** * Returns the column location in the table model of the lookahead terminal. * * @throws IllegalArgumentException * if either variable or lookahead is not a variable or terminal * (or $) respectively in the grammar */ public int getColumn(String lookahead) { int column = terminals.length; if (!lookahead.equals("$")) column = Arrays.binarySearch(terminals, lookahead); if (column < 0) throw new IllegalArgumentException(lookahead + " is not a terminal!"); return column + 1; } /** * Given another parse table with the same variable and lookahead, return a * listing of those pairs of variables and terminals where there are * differences. * * @param table * the other parse table * @return an array, each element of which is a 2-array of the variable and * lookahead, in that order, of those entries in the table which do * not match * @throws IllegalArgumentException * if the other parse table does not have the same variables and * lookahead terminals */ public String[][] getDifferences(LLParseTable table) { if (!Arrays.equals(variables, table.variables) || !Arrays.equals(terminals, table.terminals)) throw new IllegalArgumentException( "Tables differ in variables or terminals."); ArrayList differences = new ArrayList(); for (int v = 0; v < entries.length; v++) for (int t = 0; t < entries[v].length; t++) if (!entries[v][t].equals(table.entries[v][t])) { if (t == terminals.length) differences.add(new String[] { variables[v], "$" }); else differences.add(new String[] { variables[v], terminals[t] }); } return (String[][]) differences.toArray(new String[0][0]); } /** * Adds a new entry to the LL parse table * * @param variable * the stack variable * @param lookahead * the lookahead terminal * @param expansion * what to expand the stack variable on when the lookahead * terminal is lookahead * @return the number of expansion entries for this variable and lookahead * after this value was added * @throws IllegalArgumentException * if either variable or lookahead is not a variable or terminal * (or $) respectively in the grammar */ public int addEntry(String variable, String lookahead, String expansion) { int[] r = getLocation(variable, lookahead); entries[r[0]][r[1]].add(expansion); fireTableCellUpdated(r[0], r[1] + 1); return entries[r[0]][r[1]].size(); } /** * Removes an entry from the LL parse table * * @param variable * the stack variable * @param lookahead * the lookahead terminal * @param expansion * what to expand the stack variable on when the lookahead * terminal is lookahead * @return if the expansion was even in this entry to start with * @throws IllegalArgumentException * if either variable or lookahead is not a variable or terminal * (or $) respectively in the grammar */ public boolean removeEntry(String variable, String lookahead, String expansion) { int[] r = getLocation(variable, lookahead); boolean removed = entries[r[0]][r[1]].remove(expansion); fireTableCellUpdated(r[0], r[1] + 1); return removed; } /** * This will clear all entries in the table. */ public void clear() { for (int i = 0; i < entries.length; i++) for (int j = 0; j < entries[i].length; j++) entries[i][j].clear(); fireTableDataChanged(); } /** * This will clear the entry in the table for the given variable and * lookahead. * * @param variable * the stack variable * @param lookahead * the lookahead terminal * @throws IllegalArgumentException * if either variable or lookahead is not a variable or terminal * (or $) respectively in the grammar */ public void clear(String variable, String lookahead) { int[] r = getLocation(variable, lookahead); entries[r[0]][r[1]].clear(); fireTableCellUpdated(r[0], r[1] + 1); } /** * Returns the set of mapping for a variable and lookahead. * * @param variable * the stack variable * @param lookahead * the lookahead terminal * @throws IllegalArgumentException * if either variable or lookahead is not a variable or terminal * (or $) respectively in the grammar */ public SortedSet get(String variable, String lookahead) { int[] r = getLocation(variable, lookahead); return Collections.unmodifiableSortedSet(entries[r[0]][r[1]]); } /** * Defines the set of mapping for a variable and lookahead. * * @param productions * the new set for the entry * @param variable * the stack variable * @param lookahead * the lookahead terminal * @throws IllegalArgumentException * if either variable or lookahead is not a variable or terminal * (or $) respectively in the grammar */ public void set(Set productions, String variable, String lookahead) { int[] r = getLocation(variable, lookahead); entries[r[0]][r[1]].clear(); entries[r[0]][r[1]].addAll(productions); } // ABSTRACT TABLE MODEL METHODS BELOW /** * Returns the number of rows, equal to the number of variables. * * @return the number of rows */ public int getRowCount() { return variables.length; } /** * Returns the number of columns, equal to the number of terminals plus 1 * for the $ symbol plus 1 for the column devoted to variables. */ public int getColumnCount() { return terminals.length + 2; } /** * Returns the name of a column. * * @param column * the index for a column */ public String getColumnName(int column) { if (column == 0) return " "; if (column == terminals.length + 1) return "$"; return terminals[column - 1]; } /** * Takes a set, and returns a string with the entries of that set space * delimited. * * @param set * the set to put in a space delimited string */ private String spaceSet(Set set) { Iterator it = set.iterator(); boolean first = true; StringBuffer sb = new StringBuffer(); while (it.hasNext()) { if (!first) sb.append(" "); String s = (String) it.next(); sb.append(s.equals("") ? "!" : s); first = false; } return sb.toString(); } /** * Given a whitespace delimited string and a set, this clears the set and * adds all distinct tokens from the string to the set. * * @param string * the string to process * @param set * the set to add to * @return the number of elements processed */ private int despaceSet(String string, Set set) { set.clear(); StringTokenizer st = new StringTokenizer(string); while (st.hasMoreTokens()) { String s = st.nextToken(); if (s.equals("!")) s = ""; set.add(s); } return set.size(); } /** * Returns the value for the table. * * @param row * the row to get data for * @param column * the column to get data for * @return M[ variables[row], terminals[column-1] ] if * column != 0, or if column == 0 * returns the variable for the row. */ public Object getValueAt(int row, int column) { if (column == 0) return variables[row]; return spaceSet(entries[row][column - 1]); } /** * Sets the value at a particular entry of the table. * * @param value * the new value for an entry */ public void setValueAt(Object value, int row, int column) { despaceSet((String) value, entries[row][column - 1]); fireTableCellUpdated(row, column); } /** * All cells are editable except those of the first column. * * @param row * the row index * @param column * the column index */ public boolean isCellEditable(int row, int column) { if (frozen) return false; return column != 0; } /** * Sets whether any entries in this table should be editable. * * @param editable * true if entries in this table should be * editable, false if the entries should be frozen */ public void setEditable(boolean editable) { frozen = !editable; } /** The list of terminals, each corresponding to a column. */ private String[] terminals; /** The variables in the grammar, each corresponding to a row. */ private String[] variables; /** The entries in the parse table. */ private SortedSet[][] entries; /** Is the table noneditable? */ private boolean frozen = false; }