package nl.utwente.viskell.ui.components; import java.util.*; import java.util.stream.Collectors; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.control.Label; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; import javafx.scene.layout.VBox; import nl.utwente.viskell.haskell.env.FunctionInfo; import nl.utwente.viskell.haskell.expr.Apply; import nl.utwente.viskell.haskell.expr.Binder; import nl.utwente.viskell.haskell.expr.Expression; import nl.utwente.viskell.haskell.expr.FunVar; import nl.utwente.viskell.haskell.type.Type; import nl.utwente.viskell.ui.ToplevelPane; public class LiftingBlock extends Block { private NestedBlock nested; private List<LiftInputAnchor> inputs; /** Text label for the output type */ private final Label resTypeLabel; /** The result anchor of this function. */ private final OutputAnchor output; public LiftingBlock(ToplevelPane pane, NestedBlock nested) { super(pane); this.nested = nested; nested.setWrapper(this); List<Type> inputTypes = this.nested.getInputTypes(); Type outputType = this.nested.getOutputTypes().get(0); this.inputs = new ArrayList<>(); this.output = new OutputAnchor(this, new Binder("res")); this.resTypeLabel = new Label(outputType.prettyPrint()); this.resTypeLabel.setMinWidth(USE_PREF_SIZE); this.resTypeLabel.getStyleClass().add("resultType"); VBox outputSpace = new VBox(this.resTypeLabel, this.output); outputSpace.setAlignment(Pos.CENTER); outputSpace.setPickOnBounds(false); outputSpace.setTranslateY(9); for (@SuppressWarnings("unused") Type iType : inputTypes) { this.inputs.add(new LiftInputAnchor()); } HBox inputSpace = new HBox(10, this.inputs.toArray(new Node[this.inputs.size()])); if (inputTypes.isEmpty()) { Pane dummySpace = new Pane(); dummySpace.setPrefHeight(10); dummySpace.setVisible(false); inputSpace.getChildren().add(dummySpace); } inputSpace.setPickOnBounds(false); inputSpace.setAlignment(Pos.CENTER); VBox body = new VBox(inputSpace, nested, outputSpace); body.getStyleClass().addAll("block", "lifting"); this.getChildren().add(body); } /** @return a Map of class-specific properties of this Block. */ @Override protected Map<String, Object> toBundleFragment() { return ImmutableMap.of("nestedId", this.nested.hashCode()); } /** return a new instance of this Block type deserializing class-specific properties used in constructor **/ public static LiftingBlock fromBundleFragment(ToplevelPane pane, Map<String,Object> bundleFragment) { int nestedId = ((Double)bundleFragment.get("nestedId")).intValue(); // TODO find the nested block using the ID return new LiftingBlock(pane, null); } public NestedBlock getNested() { return nested; } @Override public List<InputAnchor> getAllInputs() { return this.inputs.stream().map(i -> i.anchor).collect(Collectors.toList()); } @Override public List<OutputAnchor> getAllOutputs() { return ImmutableList.of(this.output); } @Override protected void refreshAnchorTypes() { this.nested.refreshTypes(); List<Type> inputTypes = this.nested.getInputTypes(); Type outputType = this.nested.getOutputTypes().get(0); Type f = this.getToplevel().getEnvInstance().buildType(inputTypes.size() == 1 ? "Functor f => f" : "Applicative f => f"); this.output.setExactRequiredType(Type.app(f, outputType)); for (int i = 0; i < inputTypes.size(); i++) { this.inputs.get(i).anchor.setExactRequiredType(Type.app(f, inputTypes.get(i))); } } @Override public Expression getLocalExpr(Set<OutputAnchor> outsideAnchors) { if (this.inputs.isEmpty()) { FunctionInfo pure = this.getToplevel().getEnvInstance().lookupFun("pure"); return new Apply(new FunVar(pure), this.nested.getExpr()); } FunctionInfo fmap = this.getToplevel().getEnvInstance().lookupFun("fmap"); FunctionInfo ap = this.getToplevel().getEnvInstance().lookupFun("(<*>)"); List<Expression> args = this.inputs.stream().map(input -> input.anchor.getLocalExpr(outsideAnchors)).collect(Collectors.toList()); Expression expr = new Apply(new Apply(new FunVar(fmap), this.nested.getExpr()), args.get(0)); for (int i = 1; i < args.size(); i++) { expr = new Apply(new Apply(new FunVar(ap), expr), args.get(i)); } return expr; } @Override public void invalidateVisualState() { this.resTypeLabel.setText(this.output.getStringType()); for (LiftInputAnchor input : this.inputs) { input.invalidateVisualState(); } this.output.invalidateVisualState(); } @Override public Optional<Block> getNewCopy() { return Optional.empty(); } /** Combined input anchor and type label. */ private class LiftInputAnchor extends VBox implements ConnectionAnchor.Target { /** The connection anchor for this input argument. */ private final InputAnchor anchor; /** The input type label of this anchor. */ private final Label inputType; private LiftInputAnchor() { this.inputType = new Label("....."); this.inputType.setMinWidth(USE_PREF_SIZE); this.inputType.getStyleClass().add("inputType"); this.inputType.setAlignment(Pos.CENTER); this.anchor = new InputAnchor(LiftingBlock.this); this.anchor.setAlignment(Pos.CENTER); this.getChildren().addAll(this.anchor, this.inputType); this.setTranslateY(-9); this.setPickOnBounds(false); this.setAlignment(Pos.CENTER); } @Override public ConnectionAnchor getAssociatedAnchor() { return this.anchor; } /** Refresh visual information such as types */ private void invalidateVisualState() { this.anchor.invalidateVisualState(); boolean validConnection = this.anchor.hasValidConnection(); this.setTranslateY(validConnection ? 0 : -9); this.inputType.setText(validConnection ? "zyxwv" : this.anchor.getStringType()); this.inputType.setVisible(!validConnection); } } }