Commit bdb6c454 authored by Michael Schwarz's avatar Michael Schwarz

If precedence is cyclic, remove just edge that was inserted last

parent dc2eebf3
......@@ -3,12 +3,7 @@ package de.tum.in.www2.cupplugin.views;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
......@@ -69,7 +64,8 @@ import de.tum.in.www2.cupplugin.editors.Jumper;
import de.tum.in.www2.cupplugin.editors.RevisionManager;
import de.tum.in.www2.cupplugin.model.ICupParserLaLrChangeObserver;
import de.tum.in.www2.cupplugin.model.Model;
import de.tum.in.www2.cupplugin.views.CupConflictsView.PrecedenceCyclicException;
import de.tum.in.www2.cupplugin.views.CupConflictsView.ConflictPanel;
import de.tum.in.www2.cupplugin.views.PrecedenceToInsert.PrecedenceCyclicException;
public class CupConflictsView extends FailableView implements ICupEditorPageVisibility,
ICupParserLaLrChangeObserver, IRegisterForControllerChanges {
......@@ -79,7 +75,7 @@ public class CupConflictsView extends FailableView implements ICupEditorPageVisi
private Jumper jumper;
private CupTextEditor editor;
private boolean isVisible;
private List<ConflictPanel> conflictPanels;
List<ConflictPanel> conflictPanels;
private Label countConflictsLabel, connectedResolutionLabel;
private Button connectedResolutionAbort, connectedResolutionApply;
private ScrolledComposite scrolled;
......@@ -87,306 +83,6 @@ public class CupConflictsView extends FailableView implements ICupEditorPageVisi
private HashMap<terminal,Integer> terminalsAffectConflicts;
private PrecedenceToInsert currentPrecs;
class PrecedenceToInsert {
// TODO: The user can insert edges that contradict the thing we wanted in the end => Doesn't crash on cyclic
// Occurs because the first terminal is not inserted
//TODO: Can I do this? Does the terminal class properly implement all the required things?
private HashMap<terminal,Precedence.Type> precs = new HashMap<>();
// Edges are stored in reverse order (i.e. there is a edge from a to b iff b has a higher
// precedence than a)
private HashMap<terminal,Set<terminal>> edges = new HashMap<>();
// These need to be inserted whenever an edge that contains the key is to be inserted
private HashMap<terminal,Set<terminal>> speculativeEdges = new HashMap<>();
private int tarjanIndex;
private HashMap<terminal,TarjanNodeInfo> tarjanNodeInfos;
private Stack<terminal> tarjanStack;
private class TarjanNodeInfo{
public int index;
public boolean onStack;
public int lowlink;
}
private LinkedList<Pair<terminal,Precedence.Type>> result;
public boolean isResolvedInFavorOfShift(ShiftReduceDetails srd){
if(!srd.reduce.equals(srd.shift))
//TODO: Check if precedence set for terminal at all
return hasPrecedence(srd.shift,srd.reduce);
else
return precs.get(srd.shift) == Precedence.Type.Right;
}
public boolean isResolvedInFavorOfReduce(ShiftReduceDetails srd) {
if(!srd.reduce.equals(srd.shift))
//TODO: Check if precedence set for terminal at all
return hasPrecedence(srd.reduce,srd.shift);
else
return precs.get(srd.shift) == Precedence.Type.Left;
}
public boolean isResolvedAsError(ShiftReduceDetails srd) {
if(!srd.reduce.equals(srd.shift))
return false;
else
return precs.get(srd.shift) == Precedence.Type.NonAssoc;
}
public boolean isAffected(ShiftReduceDetails srd){
return precs.containsKey(srd.shift) || precs.containsKey(srd.reduce);
}
// Add with non-set assoc
public void add(terminal t,terminal other){
// We use NoPrec as a placeholder
precs.put(t, Precedence.Type.NoPrec);
edges.put(t, new HashSet<>());
HashSet<terminal> spec = new HashSet<>();
spec.add(t);
speculativeEdges.put(other,spec);
for(ConflictPanel c : conflictPanels){
c.markIfAffected(this);
}
}
public void addPrecedence(terminal higher,terminal lower) throws PrecedenceCyclicException{
// Make sure all terminals are represented within the precs
if(!precs.containsKey(higher)){
precs.put(higher, Precedence.Type.NoPrec);
edges.put(higher, new HashSet<>());
}
if(!precs.containsKey(lower)){
precs.put(lower, Precedence.Type.NoPrec);
edges.put(lower, new HashSet<>());
}
if(speculativeEdges.containsKey(higher)){
// Insert all speculative edges that are outgoing from the current node
// we need those now
edges.get(higher).addAll(speculativeEdges.get(higher));
}
// Insert edge
edges.get(lower).add(higher);
if(!isAcyclic()){
throw new PrecedenceCyclicException();
}
for(ConflictPanel c : conflictPanels){
c.markIfAffected(this);
}
}
private boolean isAcyclic(){
tarjanIndex = 0;
tarjanNodeInfos = new HashMap<>();
tarjanStack = new Stack<>();
for(terminal t : precs.keySet()){
TarjanNodeInfo tni = new TarjanNodeInfo();
tni.onStack = false;
tni.index = -1;
tni.lowlink = -1;
tarjanNodeInfos.put(t, tni);
}
for(Entry<terminal, TarjanNodeInfo> e : tarjanNodeInfos.entrySet()){
if(e.getValue().index == -1){
if(strongConnect(e.getKey())){
return false;
}
}
}
return true;
}
private boolean strongConnect(terminal t){
TarjanNodeInfo node = tarjanNodeInfos.get(t);
node.index = tarjanIndex;
node.lowlink = tarjanIndex;
node.onStack = true;
tarjanIndex++;
tarjanStack.push(t);
for(terminal succ : edges.get(t)){
if(tarjanNodeInfos.get(succ).index == -1){
if(strongConnect(succ)){
return true;
}
node.lowlink = Math.min(node.lowlink,tarjanNodeInfos.get(succ).lowlink);
}
else if(tarjanNodeInfos.get(succ).onStack){
node.lowlink = Math.min(node.lowlink,tarjanNodeInfos.get(succ).index);
}
}
if(node.lowlink == node.index){
terminal w = tarjanStack.pop();
tarjanNodeInfos.get(w).onStack = false;
if(!w.equals(t)){
return true;
}
}
return false;
}
private void topSort(){
result = new LinkedList<>();
tarjanNodeInfos = new HashMap<>();
tarjanStack = new Stack<>();
for(terminal t : precs.keySet()){
TarjanNodeInfo tni = new TarjanNodeInfo();
tni.onStack = false;
tni.index = -1;
tni.lowlink = -1;
tarjanNodeInfos.put(t, tni);
}
for(Entry<terminal, TarjanNodeInfo> e : tarjanNodeInfos.entrySet()){
if(e.getValue().index == -1){
topSortVisit(e.getKey());
}
}
}
private void topSortVisit(terminal t){
if(tarjanNodeInfos.get(t).onStack){
throw new IllegalStateException("Expected result to be acyclic.");
}
if(tarjanNodeInfos.get(t).index == -1){
tarjanNodeInfos.get(t).onStack = true;
for(terminal succ : edges.get(t)){
topSortVisit(succ);
}
tarjanNodeInfos.get(t).index = 1;
tarjanNodeInfos.get(t).onStack = false;
result.addFirst(new Pair<terminal,Precedence.Type>(t,precs.get(t)));
}
}
public String precsToInsert() throws PrecedenceCyclicException{
if(!isAcyclic()){
throw new PrecedenceCyclicException();
}
topSort();
StringBuilder sb = new StringBuilder();
boolean first = true;
for(Pair<terminal,Precedence.Type> p : result){
if(first){
first = false;
}
else {
sb.append("\n");
}
sb.append("precedence ");
if(p.getSecond() == Precedence.Type.NoPrec){
// We insert nonassoc in here. This should never matter
sb.append(Precedence.Type.NonAssoc.toString().toLowerCase());
}
else {
sb.append(p.getSecond().toString().toLowerCase());
}
sb.append(" ");
sb.append(p.getFirst().name());
sb.append("; // Inserted by Eclipse plugin");
}
return sb.toString();
}
private boolean hasPrecedence(terminal t,terminal over){
if(!precs.containsKey(t) && !precs.containsKey(over)){
return false;
}
if(precs.containsKey(t) && !precs.containsKey(over)){
return true;
}
if(!precs.containsKey(t) && precs.containsKey(over)){
return false;
}
// Hereafter we know that both are in there
// This works because we have guarantee that the graph is a DAG
HashSet<terminal> toCheck = new HashSet<>();
if(edges.get(over).size() == 0){
// if the node has no outgoing node, than nothing has precedence over it
return false;
}
toCheck.addAll(edges.get(over));
// We need to consider those as well
if(speculativeEdges.get(over) != null){
toCheck.addAll(speculativeEdges.get(over));
}
// TODO:Sure ?
if(toCheck.size() == 0){
return false;
}
while(toCheck.size() > 0){
if(toCheck.contains(t)){
return true;
}
else {
HashSet<terminal> next = new HashSet<>();
for(terminal x : toCheck){
next.addAll(edges.get(x));
}
toCheck = next;
}
}
return false;
}
public void setAssoc(terminal t,Precedence.Type p){
precs.put(t, p);
for(ConflictPanel c : conflictPanels){
c.markIfAffected(this);
}
}
public boolean isActive(){
return precs.size() != 0;
}
}
class PrecedenceCyclicException extends Exception{
}
class ShiftReduceDetails {
terminal shift,reduce;
......@@ -858,14 +554,14 @@ public class CupConflictsView extends FailableView implements ICupEditorPageVisi
// The user wants to shift, but shifting affects other conflicts
if(srdetails.shiftAffectsOthers && resolutionOptions.getSelectionIndex() == 1){
beginConnectedResolution();
currentPrecs.add(srdetails.shift,srdetails.reduce);
add(srdetails.shift,srdetails.reduce);
return;
}
// The user wants to reduce, but reduce affects other conflicts
if(srdetails.reduceAffectsOthers && resolutionOptions.getSelectionIndex() == 2){
beginConnectedResolution();
currentPrecs.add(srdetails.reduce,srdetails.shift);
add(srdetails.reduce,srdetails.shift);
return;
}
......@@ -881,11 +577,11 @@ public class CupConflictsView extends FailableView implements ICupEditorPageVisi
if(srdetails.shiftAffectsOthers){
if(resolutionOptions.getSelectionIndex() == 1) { //Shift
forceEnableDropdown = true;
currentPrecs.setAssoc(srdetails.shift,Precedence.Type.Right);
setAssoc(srdetails.shift,Precedence.Type.Right);
}
else if (resolutionOptions.getSelectionIndex() == 2) { // Reduce
forceEnableDropdown = true;
currentPrecs.setAssoc(srdetails.shift,Precedence.Type.Left);
setAssoc(srdetails.shift,Precedence.Type.Left);
}
}
else {
......@@ -917,28 +613,23 @@ public class CupConflictsView extends FailableView implements ICupEditorPageVisi
// We need to insert a assoc
if(resolutionOptions.getSelectionIndex() == 1) { //Shift
forceEnableDropdown = true;
currentPrecs.setAssoc(srdetails.shift,Precedence.Type.Right);
setAssoc(srdetails.shift,Precedence.Type.Right);
}
else if (resolutionOptions.getSelectionIndex() == 2) { // Reduce
forceEnableDropdown = true;
currentPrecs.setAssoc(srdetails.shift,Precedence.Type.Left);
setAssoc(srdetails.shift,Precedence.Type.Left);
}
}
else {
if(resolutionOptions.getSelectionIndex() == 1 && currentPrecs.isResolvedInFavorOfShift(srdetails)){
// Shift is current solution, Shift is desired
// Do nothing
return;
}
if(resolutionOptions.getSelectionIndex() == 2 && currentPrecs.isResolvedInFavorOfReduce(srdetails)){
// Reduce is current solution, Reduce is desired
// Do nothing
if((resolutionOptions.getSelectionIndex() == 1 && currentPrecs.isResolvedInFavorOfShift(srdetails))
|| (resolutionOptions.getSelectionIndex() == 2 && currentPrecs.isResolvedInFavorOfReduce(srdetails))) {
//Current action is desired -> Do nothing
return;
}
if(resolutionOptions.getSelectionIndex() == 1){ //Shift
try {
currentPrecs.addPrecedence(srdetails.shift, srdetails.reduce);
addPrecedence(srdetails.shift, srdetails.reduce);
} catch (PrecedenceCyclicException e1) {
Shell shell = new Shell(Display.getCurrent());
MessageBox box = new MessageBox(shell,SWT.ICON_WARNING | SWT.OK);
......@@ -946,14 +637,14 @@ public class CupConflictsView extends FailableView implements ICupEditorPageVisi
box.setMessage("These precedences are inconsistent. (Cyclic). Abort.");
box.open();
abortConnectedResolution();
markIfAffected(currentPrecs);
return;
}
}
if(resolutionOptions.getSelectionIndex() == 2){ // Reduce
try {
currentPrecs.addPrecedence(srdetails.reduce, srdetails.shift);
addPrecedence(srdetails.reduce, srdetails.shift);
} catch (PrecedenceCyclicException e1) {
Shell shell = new Shell(Display.getCurrent());
MessageBox box = new MessageBox(shell,SWT.ICON_WARNING | SWT.OK);
......@@ -961,7 +652,7 @@ public class CupConflictsView extends FailableView implements ICupEditorPageVisi
box.setMessage("These precedences are inconsistent. (Cyclic). Abort..");
box.open();
abortConnectedResolution();
markIfAffected(currentPrecs);
return;
}
}
......@@ -1216,6 +907,28 @@ public class CupConflictsView extends FailableView implements ICupEditorPageVisi
}
}
void markAllAffected(){
for(ConflictPanel c : conflictPanels){
c.markIfAffected(currentPrecs);
}
}
private void add(terminal t,terminal other){
currentPrecs.add(t,other);
markAllAffected();
}
private void addPrecedence(terminal higher,terminal lower) throws PrecedenceCyclicException{
currentPrecs.addPrecedence(higher,lower);
markAllAffected();
}
private void setAssoc(terminal t,Precedence.Type p){
currentPrecs.setAssoc(t, p);
markAllAffected();
}
private void beginConnectedResolution(){
Shell shell = new Shell(Display.getCurrent());
MessageBox box = new MessageBox(shell,SWT.ICON_WARNING | SWT.OK);
......@@ -1283,7 +996,7 @@ public class CupConflictsView extends FailableView implements ICupEditorPageVisi
} catch (BadLocationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (PrecedenceCyclicException e) {
} catch (de.tum.in.www2.cupplugin.views.PrecedenceToInsert.PrecedenceCyclicException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
......
package de.tum.in.www2.cupplugin.views;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.Stack;
import java.util.Map.Entry;
import de.in.tum.www2.cup.ast.Precedence;
import de.in.tum.www2.cup.ast.Precedence.Type;
import de.in.tum.www2.cup.internal.terminal;
import de.tum.in.www2.cupplugin.Pair;
import de.tum.in.www2.cupplugin.views.CupConflictsView.ShiftReduceDetails;
class PrecedenceToInsert {
// Occurs because the first terminal is not inserted
//TODO: Can I do this? Does the terminal class properly implement all the required things?
private HashMap<terminal,Precedence.Type> precs = new HashMap<>();
// Edges are stored in reverse order (i.e. there is a edge from a to b iff b has a explicit
// higher precedence than a
private HashMap<terminal,Set<terminal>> edges = new HashMap<>();
// These need to be inserted whenever an edge that contains the key is to be inserted
private HashMap<terminal,Set<terminal>> speculativeEdges = new HashMap<>();
private int tarjanIndex;
private HashMap<terminal,TarjanNodeInfo> tarjanNodeInfos;
private Stack<terminal> tarjanStack;
private class TarjanNodeInfo{
public int index;
public boolean onStack;
public int lowlink;
}
class PrecedenceCyclicException extends Exception{
/**
* Every Exception is expected to have one of those
*/
private static final long serialVersionUID = -3346362877378712162L;
public PrecedenceCyclicException(String s){
super(s);
}
}
private LinkedList<Pair<terminal,Precedence.Type>> result;
/**
* Returns true iff this conflict would be resolved in favor of Shift by those precs
* @param srd Conflict
* @return true iff conflict is resolved in favor of Shift
*/
public boolean isResolvedInFavorOfShift(ShiftReduceDetails srd){
if(!srd.reduce.equals(srd.shift))
//TODO: Check if precedence set for terminal at all
return hasPrecedence(srd.shift,srd.reduce);
else
return precs.get(srd.shift) == Precedence.Type.Right;
}
/**
* Returns true iff this conflict would be resolved in favor of Reduce by those precs
* @param srd Conflict
* @return true iff conflict is resolved in favor of Reduce
*/
public boolean isResolvedInFavorOfReduce(ShiftReduceDetails srd) {
if(!srd.reduce.equals(srd.shift))
//TODO: Check if precedence set for terminal at all
return hasPrecedence(srd.reduce,srd.shift);
else
return precs.get(srd.shift) == Precedence.Type.Left;
}
/**
* Returns true iff conflict involves the same terminal two times and
* assoc is set to NonAssoc
* @param srd Conflict
* @return true iff conflict is resolved as error
*/
public boolean isResolvedAsError(ShiftReduceDetails srd) {
if(!srd.reduce.equals(srd.shift))
return false;
else
return precs.get(srd.shift) == Precedence.Type.NonAssoc;
}
/**
* Returns true iff the conflict is affected by the precs
* @param srd conflict
* @return true iff conflict is affected
*/
public boolean isAffected(ShiftReduceDetails srd){
return precs.containsKey(srd.shift) || precs.containsKey(srd.reduce);
}
/**
* Insert terminal precedence t over other. other is not inserted into the set of precedences because it might not be necessary to resolve
* a conflict to insert both precs. However, the possible edge is recorded and will be added later on if it becomes necessary
* @param t
* @param other
*/
public void add(terminal t,terminal other){
// We use NoPrec as a placeholder
precs.put(t, Precedence.Type.NoPrec);
edges.put(t, new HashSet<>());
HashSet<terminal> spec = new HashSet<>();
spec.add(t);
speculativeEdges.put(other,spec);
}
/**
* Add precedence higher over lower. If not inserted yet they are inserted into the set of total precedences
* with asssoc set to non-assoc
* @param higher
* @param lower
* @throws PrecedenceCyclicException if this would induce a cyclic precedence. In this case the action is not performed
*/
public void addPrecedence(terminal higher,terminal lower) throws PrecedenceCyclicException{
boolean addedHigher = false, addedLower = false;
// Make sure all terminals are represented within the precs
if(!precs.containsKey(higher)){
precs.put(higher, Precedence.Type.NoPrec);
edges.put(higher, new HashSet<>());
addedHigher = true;
}
if(!precs.containsKey(lower)){
precs.put(lower, Precedence.Type.NoPrec);
edges.put(lower, new HashSet<>());
addedLower = true;
}
Set<terminal> outgoingSpecEdgesHigher = new HashSet<>();
if(speculativeEdges.containsKey(higher)){
// Insert all speculative edges that are outgoing from the node with higher pred
// We don't need to add it it to precs, it is already there
edges.get(higher).addAll(speculativeEdges.get(higher));
outgoingSpecEdgesHigher.addAll(speculativeEdges.get(higher));
speculativeEdges.remove(higher);
}
// Insert edge
edges.get(lower).add(higher);
if(!isAcyclic()){
//Do rollback
edges.get(lower).remove(higher);
if(outgoingSpecEdgesHigher.size() != 0){
// Remove from real edges
edges.get(higher).removeAll(outgoingSpecEdgesHigher);
//Add to spec edges again
speculativeEdges.put(higher, outgoingSpecEdgesHigher);
}
if(addedHigher){
precs.remove(higher);
}
if(addedLower){
precs.remove(lower);
}
throw new PrecedenceCyclicException("");
}
}
private boolean isAcyclic(){
tarjanIndex = 0;
tarjanNodeInfos = new HashMap<>();
tarjanStack = new Stack<>();
for(terminal t : precs.keySet()){
TarjanNodeInfo tni = new TarjanNodeInfo();
tni.onStack = false;
tni.index = -1;
tni.lowlink = -1;
tarjanNodeInfos.put(t, tni);
}
for(Entry<terminal, TarjanNodeInfo> e : tarjanNodeInfos.entrySet()){
if(e.getValue().index == -1){
if(strongConnect(e.getKey())){
return false;
}
}
}
return true;
}
private boolean strongConnect(terminal t){
TarjanNodeInfo node = tarjanNodeInfos.get(t);
node.index = tarjanIndex;
node.lowlink = tarjanIndex;
node.onStack = true;
tarjanIndex++;
tarjanStack.push(t);
for(terminal succ : edges.get(t)){
if(tarjanNodeInfos.get(succ).index == -1){
if(strongConnect(succ)){
return true;
}
node.lowlink = Math.min(node.lowlink,tarjanNodeInfos.get(succ).lowlink);
}
else if(tarjanNodeInfos.get(succ).onStack){
node.lowlink = Math.min(node.lowlink,tarjanNodeInfos.get(succ).index);
}
}
if(node.lowlink == node.index){
terminal w = tarjanStack.pop();
tarjanNodeInfos.get(w).onStack = false;
if(!w.equals(t)){
return true;
}