package org.scribble.visit; import java.util.Deque; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Map; import java.util.Set; import org.scribble.ast.Continue; import org.scribble.ast.ProtocolBlock; import org.scribble.ast.Recursion; import org.scribble.ast.ScribNode; import org.scribble.main.Job; import org.scribble.main.ScribbleException; import org.scribble.sesstype.name.RecVar; import org.scribble.visit.env.Env; // FIXME: unfolding/unrolling algorithm would be easier if we would use an "affine rec var" normal form: a single rec can have multiple labels, each label used at most once in a continue // "Lazily unfolds" each recursion once (by reentering the original rec ast) on reaching a continue // FIXME: would be better to only unfold "as needed" (unguarded choice-recs) // N.B. so subclass should manually keep track of when to cut off visiting, as visiting the "unfolding" will eventually reach the same continue (e.g. an unguarded choice-continue) -- currently using pointer equality in e.g. InlinedWFChoice to cut off traversal on reaching the "same" choice again // Cf. InlinedProtocolUnfolder, statically unfolds unguarded recursions and continues "directly under" choices public abstract class UnfoldingVisitor<E extends Env<?>> extends InlinedProtocolVisitor<E> { private Map<RecVar, Deque<ProtocolBlock<?>>> recs = new HashMap<>(); // Stack needed to handle bad reachability cases (e.g. ... continue X; continue Y; -- if rec Y inside unfolding of X, need to push again before popping so Y still in scope for continue) -- since reachability isn't checked until after projection // Also FIXME: recvar shadowing: though this stack should be enough private Set<RecVar> unfolded = new HashSet<>(); public UnfoldingVisitor(Job job) { super(job); } @Override public ScribNode visit(ScribNode parent, ScribNode child) throws ScribbleException { enter(parent, child); ScribNode visited = visitForUnfolding(parent, child); return leave(parent, child, visited); } protected ScribNode visitForUnfolding(ScribNode parent, ScribNode child) throws ScribbleException { if (child instanceof Continue) { Continue<?> cont = (Continue<?>) child; RecVar rv = cont.recvar.toName(); if (!this.unfolded.contains(rv)) { this.unfolded.add(rv); // Visiting the children of the seq of the block so as to visit *under the existing env contexts* (i.e. the top Visitor Env) // N.B. not visiting a clone because subclasses currently using pointer equality to cut off traversal inside the unfolding (e.g. InlinedWFChoiceChecker) // Also visitChildren, not accept (so not doing enter/exit for the seq) // Also not returning the seq, just the original continue (cf. do visiting) //this.recs.get(rv).peek().seq.clone().visitChildren(this); // No: e.g. InlinedWFChoiceChecker uses pointer equality to check if Choice already visited this.recs.get(rv).peek().seq.visitChildren(this); // FIXME: ok to visit the same AST? any problems with dels/envs? -- maybe do proper equals/hashCode for AST classes this.unfolded.remove(rv); return cont; } } return super.visitInlinedProtocol(parent, child); // Not super.visit because that does enter/exit } @Override protected final void inlinedEnter(ScribNode parent, ScribNode child) throws ScribbleException { super.inlinedEnter(parent, child); if (child instanceof Recursion) { Recursion<?> rec = (Recursion<?>) child; RecVar rv = rec.recvar.toName(); if (!this.recs.containsKey(rv)) { this.recs.put(rv, new LinkedList<>()); } Deque<ProtocolBlock<?>> blocks = this.recs.get(rv); blocks.push(rec.block); } unfoldingEnter(parent, child); } @Override protected final ScribNode inlinedLeave(ScribNode parent, ScribNode child, ScribNode visited) throws ScribbleException { ScribNode n = unfoldingLeave(parent, child, visited); if (child instanceof Recursion) { Recursion<?> rec = (Recursion<?>) child; RecVar rv = rec.recvar.toName(); Deque<ProtocolBlock<?>> blocks = this.recs.get(rv); blocks.pop(); /*if (blocks.isEmpty()) // Unnecessary? But tidier? { this.recs.remove(rv); }*/ } return super.inlinedLeave(parent, child, n); } protected void unfoldingEnter(ScribNode parent, ScribNode child) throws ScribbleException { } protected ScribNode unfoldingLeave(ScribNode parent, ScribNode child, ScribNode visited) throws ScribbleException { return visited; } }