package org.scribble.visit; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.stream.Collectors; import org.scribble.ast.Do; import org.scribble.ast.NonRoleArgList; import org.scribble.ast.NonRoleArgNode; import org.scribble.ast.NonRoleParamDecl; import org.scribble.ast.NonRoleParamDeclList; import org.scribble.ast.ProtocolDecl; import org.scribble.ast.RoleArgList; import org.scribble.ast.RoleDeclList; import org.scribble.ast.ScopedNode; import org.scribble.ast.ScribNode; import org.scribble.ast.context.ModuleContext; import org.scribble.ast.name.simple.RoleNode; import org.scribble.main.Job; import org.scribble.main.JobContext; import org.scribble.main.ScribbleException; import org.scribble.sesstype.Arg; import org.scribble.sesstype.SubprotocolSig; import org.scribble.sesstype.kind.NonRoleArgKind; import org.scribble.sesstype.kind.NonRoleParamKind; import org.scribble.sesstype.kind.ProtocolKind; import org.scribble.sesstype.name.Name; import org.scribble.sesstype.name.ProtocolName; import org.scribble.sesstype.name.Role; import org.scribble.sesstype.name.Scope; import org.scribble.visit.env.Env; public abstract class SubprotocolVisitor<T extends Env<?>> extends EnvVisitor<T> { protected List<SubprotocolSig> stack = new LinkedList<>(); // name in the current protocoldecl scope -> the original name node in the root protocol decl protected Stack<Map<Role, RoleNode>> rolemaps = new Stack<>(); protected Stack<Map<Arg<? extends NonRoleArgKind>, NonRoleArgNode>> argmaps = new Stack<>(); private Scope scope = null; public SubprotocolVisitor(Job job) { super(job); } // Pushes a subprotocol signature (i.e. on root entry) protected void enterRootProtocolDecl(ProtocolDecl<?> pd) { Map<Role, RoleNode> rolemap = makeRootRoleSubsMap(pd.header.roledecls); Map<Arg<? extends NonRoleArgKind>, NonRoleArgNode> argmap = makeRootNonRoleSubsMap(pd.header.paramdecls); this.rolemaps.push(rolemap); this.argmaps.push(argmap); ModuleContext mcontext = getModuleContext(); ProtocolName<?> fullname = mcontext.getVisibleProtocolDeclFullName(pd.header.getDeclName()); List<Role> roleargs = pd.header.roledecls.getRoles(); List<Arg<? extends NonRoleArgKind>> nonroleargs = pd.header.paramdecls.getDecls().stream().map((param) -> paramDeclToArg(param)).collect(Collectors.toList()); pushSubprotocolSig(fullname, roleargs, nonroleargs); } // Most subclasses will override visitForSubprotocols (e.g. ReachabilityChecker, FsmConstructor), but sometimes still want to change whole visit pattern (e.g. Projector) @Override public ScribNode visit(ScribNode parent, ScribNode child) throws ScribbleException { enter(parent, child); ScribNode visited = visitForSubprotocols(parent, child); return leave(parent, child, visited); } // Subclasses can override this to disable subprotocol visiting protected ScribNode visitForSubprotocols(ScribNode parent, ScribNode child) throws ScribbleException { if (child instanceof Do) { return visitOverrideForDo(parent, (Do<?>) child); // parent is InteractionSequence } else { return child.visitChildren(this); // The base (super) behaviour (could factor it out in ModelVisitor as its own visitor method) } } // The Do node itself is no longer visited -- FIXME: projection needs to visit it -- no: that's in enter/leave, visited means "visit children" protected Do<?> visitOverrideForDo(ScribNode parent, Do<?> doo) throws ScribbleException { if (!isCycle()) { JobContext jc = this.job.getContext(); ModuleContext mc = getModuleContext(); ProtocolDecl<? extends ProtocolKind> pd = doo.getTargetProtocolDecl(jc, mc); // Target is cloned: fresh dels and envs, which will be discarded ScribNode seq = applySubstitutions(pd.def.block.seq.clone()); // Visit the seq? -- or visit the interactions in the seq directly? // Visit seq/interactions under current environment seq.accept(this); // Result from visiting subprotocol body is discarded } return doo; } protected boolean overrideSubstitution() { return false; } protected ScribNode applySubstitutions(ScribNode n) { if (overrideSubstitution()) { return n; } try { return n.accept(new Substitutor(this.job, this.rolemaps.peek(), this.argmaps.peek())); } catch (ScribbleException e) { throw new RuntimeException("Shouldn't get in here: " + n); } } @Override protected final void envEnter(ScribNode parent, ScribNode child) throws ScribbleException { super.envEnter(parent, child); if (child instanceof ProtocolDecl) { setScope(Scope.ROOT_SCOPE); enterRootProtocolDecl((ProtocolDecl<?>) child); } if (child instanceof ScopedNode) { ScopedNode sn = (ScopedNode) child; if(!sn.isEmptyScope()) { setScope(new Scope(getScope(), sn.getScopeElement())); } } if (child instanceof Do) { enterSubprotocol((Do<?>) child); // Scope already pushed } subprotocolEnter(parent, child); } @Override protected final ScribNode envLeave(ScribNode parent, ScribNode child, ScribNode visited) throws ScribbleException { ScribNode n = subprotocolLeave(parent, child, visited); if (child instanceof ProtocolDecl) { envLeaveProtocolDeclOverride(parent, child, visited); } if (child instanceof Do) // child or visited/n? { leaveSubprotocol(); } if (child instanceof ScopedNode && !((ScopedNode) child).isEmptyScope()) { setScope(getScope().getPrefix()); } return super.envLeave(parent, child, n); } // Hack for OffsetSubprotocolVisitor protected void envLeaveProtocolDeclOverride(ScribNode parent, ScribNode child, ScribNode visited) throws ScribbleException { leaveSubprotocol(); } protected void subprotocolEnter(ScribNode parent, ScribNode child) throws ScribbleException { } protected ScribNode subprotocolLeave(ScribNode parent, ScribNode child, ScribNode visited) throws ScribbleException { return visited; } private void enterSubprotocol(Do<?> doo) { ProtocolName<?> fullname = getModuleContext().checkProtocolDeclDependencyFullName(doo.proto.toName()); // namedisamb should already have converted proto to the fullname -- in order for inlining to work pushSubprotocolSig(fullname, doo.roles.getRoles(), doo.args.getArguments()); pushNameMaps(fullname, doo); } // Also used for leaving root protocoldecl, for convenience private void leaveSubprotocol() { this.stack.remove(this.stack.size() - 1); this.rolemaps.pop(); this.argmaps.pop(); } private void pushSubprotocolSig(ProtocolName<?> fullname, List<Role> roleargs, List<Arg<? extends NonRoleArgKind>> nonroleargs) { SubprotocolSig subsig = new SubprotocolSig(fullname, roleargs, nonroleargs); this.stack.add(subsig); } private void pushNameMaps(ProtocolName<?> fullname, Do<?> doo) { ProtocolDecl<?> pd = this.job.getContext().getModule(fullname.getPrefix()).getProtocolDecl(fullname.getSimpleName()); this.rolemaps.push(makeRoleSubsMap(this.rolemaps.get(0), doo.roles, pd.header.roledecls)); this.argmaps.push(makeNonRoleSubsMap(this.argmaps.get(0), doo.args, pd.header.paramdecls)); } public boolean isRootedCycle() { return this.stack.size() > 1 && this.stack.get(0).equals(this.stack.get(this.stack.size() - 1)); } public boolean isCycle() { return getCycleEntryIndex() != -1; } public int getCycleEntryIndex() { int size = this.stack.size(); if (size > 1) { SubprotocolSig last = this.stack.get(size - 1); for (int i = size - 2; i >= 0; i--) { if (this.stack.get(i).equals(last)) // FIXME: doesn't support recursive scoped subprotocols, i.e. cycle detection not general enough? { return i; } } } return -1; } public List<SubprotocolSig> getStack() { return this.stack; } public boolean isStackEmpty() { return this.stack.isEmpty(); } public SubprotocolSig peekStack() { return this.stack.get(this.stack.size() - 1); } public Scope getScope() { return this.scope; } protected void setScope(Scope scope) { this.scope = scope; } protected static Map<Role, RoleNode> makeRootRoleSubsMap(RoleDeclList rdl) { return rdl.getDecls().stream() .collect(Collectors.toMap((r) -> r.getDeclName(), (r) -> (RoleNode) r.name)); } protected static Map<Arg<? extends NonRoleArgKind>, NonRoleArgNode> makeRootNonRoleSubsMap(NonRoleParamDeclList pdl) { return pdl.getDecls().stream() .collect(Collectors.toMap((p) -> (Arg<?>) p.getDeclName(), (p) -> (NonRoleArgNode) p.name)); } protected static Map<Role, RoleNode> makeRoleSubsMap(Map<Role, RoleNode> root, RoleArgList ral, RoleDeclList rdl) { Iterator<Role> roleargs = ral.getRoles().iterator(); return rdl.getRoles().stream().collect(Collectors.toMap((r) -> r, (r) -> root.get(roleargs.next()))); } protected static Map<Arg<? extends NonRoleArgKind>, NonRoleArgNode> makeNonRoleSubsMap(Map<Arg<? extends NonRoleArgKind>, NonRoleArgNode> root, NonRoleArgList nral, NonRoleParamDeclList nrpdl) { Map<Arg<? extends NonRoleArgKind>, NonRoleArgNode> newmap = new HashMap<>(); // Using arg and argnode views of the arglist Iterator<Arg<? extends NonRoleArgKind>> nonroleargs = nral.getArguments().iterator(); Iterator<NonRoleArgNode> nonroleargnodes = nral.getArgumentNodes().iterator(); for (Name<NonRoleParamKind> param : nrpdl.getParameters()) { Arg<?> arg = nonroleargs.next(); NonRoleArgNode argnode; if (root.containsKey(arg)) // A root param propagated through to here as an arg: get the root argnode { argnode = root.get(arg); nonroleargnodes.next(); } else { argnode = nonroleargnodes.next(); // The argnode correspoding to the current arg } newmap.put((Arg<?>) param, argnode); } return newmap; } private static Arg<? extends NonRoleArgKind> paramDeclToArg(NonRoleParamDecl<NonRoleParamKind> pd) { Name<NonRoleParamKind> n = pd.getDeclName(); if (!(n instanceof Arg)) { throw new RuntimeException("Shouldn't get in here: " + n); } @SuppressWarnings("unchecked") Arg<? extends NonRoleArgKind> tmp = (Arg<? extends NonRoleArgKind>) n; return tmp; } }