001/*
002 * Copyright (c) 2012-2018 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010
011package gololang.ir;
012
013import java.util.List;
014import java.util.LinkedList;
015
016/**
017 * Represents a conditional branching.
018 *
019 * <p>Typically an {@code if else} node.
020 */
021public final class ConditionalBranching extends GoloStatement<ConditionalBranching> {
022
023  private ExpressionStatement<?> condition;
024  private Block trueBlock;
025  private ConditionalBranching elseConditionalBranching;
026  private Block falseBlock;
027
028  private ConditionalBranching() {
029    super();
030  }
031
032  /**
033   * Creates an empty conditional branch.
034   */
035  public static ConditionalBranching branch() {
036    return new ConditionalBranching();
037  }
038
039  /**
040   * Full branch construction in one call.
041   *
042   * <p>Less readable than the fluent API, but useful when doing meta-generation
043   *
044   * @param condition the test condition
045   * @param trueBlock the block to execute when the condition is true
046   * @param falseBlock the block to execute when the condition is false
047   * @param elseBranch a nested conditional branch
048   */
049  public static ConditionalBranching create(Object condition,
050                                            Object trueBlock,
051                                            Object falseBlock,
052                                            Object elseBranch) {
053    return new ConditionalBranching().condition(condition)
054      .whenTrue(trueBlock)
055      .whenFalse(falseBlock)
056      .elseBranch(elseBranch);
057  }
058
059  protected ConditionalBranching self() { return this; }
060
061  /**
062   * Defines the condition of the branching.
063   *
064   * <p>This is a builder method.
065   *
066   * @param cond the expression defining the condition. If {@code null}, the condition is set to {@code false}.
067   * @return the branching.
068   */
069  public ConditionalBranching condition(Object cond) {
070    if (cond == null) {
071      this.condition = ConstantStatement.of(false);
072    } else {
073      this.condition = ExpressionStatement.of(cond);
074    }
075    makeParentOf(this.condition);
076    return this;
077  }
078
079  /**
080   * Defines the block executed when the condition evaluates to {@code true}.
081   *
082   * <p>This is a builder method.
083   */
084  public ConditionalBranching whenTrue(Object block) {
085    this.trueBlock = makeParentOf(Block.of(block));
086    return this;
087  }
088
089  /**
090   * Defines the block executed when the condition evaluates to {@code false}.
091   *
092   * <p>This is a builder method.
093   *
094   * @see #otherwise(Object)
095   */
096  public ConditionalBranching whenFalse(Object block) {
097    if (block == null) {
098      this.falseBlock = null;
099    } else {
100      this.falseBlock = makeParentOf(Block.of(block));
101    }
102    return this;
103  }
104
105  /**
106   * Defines a nested conditional branch.
107   *
108   * <p>This is a builder method.
109   *
110   * <p>This represents an {@code else if} branch.
111   *
112   * @see #otherwise(Object)
113   */
114  public ConditionalBranching elseBranch(Object elseBranch) {
115    this.elseConditionalBranching = makeParentOf((ConditionalBranching) elseBranch);
116    return this;
117  }
118
119  /**
120   * Defines a block to execute when {@code false} or a nested branch according to the given element.
121   *
122   * <p>This is a builder method; it's the preferred way to define an alternative to the true block.
123   *
124   * @param alternative a {@link Block} to execute when {@code false} or a {@code ConditionalBranching} defining a
125   * nested branch.
126   */
127  public ConditionalBranching otherwise(Object alternative) {
128    if (alternative instanceof ConditionalBranching) {
129      return elseBranch(alternative);
130    }
131    return whenFalse(alternative);
132  }
133
134  public ExpressionStatement<?> getCondition() {
135    return condition;
136  }
137
138  public Block getTrueBlock() {
139    return trueBlock;
140  }
141
142  public Block getFalseBlock() {
143    return falseBlock;
144  }
145
146  public boolean hasFalseBlock() {
147    return falseBlock != null;
148  }
149
150  public ConditionalBranching getElseConditionalBranching() {
151    return elseConditionalBranching;
152  }
153
154  public boolean hasElseConditionalBranching() {
155    return elseConditionalBranching != null;
156  }
157
158  public boolean returnsFromBothBranches() {
159    if (hasFalseBlock()) {
160      return trueBlock.hasReturn() && falseBlock.hasReturn();
161    } else if (hasElseConditionalBranching()) {
162      return trueBlock.hasReturn() && elseConditionalBranching.returnsFromBothBranches();
163    } else {
164      return false;
165    }
166  }
167
168  @Override
169  public String toString() {
170    return String.format("if %s %s%s", condition, trueBlock,
171        hasFalseBlock() ? " else " + falseBlock.toString()
172        : hasElseConditionalBranching() ? " else " + elseConditionalBranching.toString()
173        : "");
174  }
175
176  /**
177   * {@inheritDoc}
178   */
179  @Override
180  public void accept(GoloIrVisitor visitor) {
181    visitor.visitConditionalBranching(this);
182  }
183
184  /**
185   * {@inheritDoc}
186   */
187  @Override
188  public List<GoloElement<?>> children() {
189    LinkedList<GoloElement<?>> children = new LinkedList<>();
190    children.add(condition);
191    children.add(trueBlock);
192    if (falseBlock != null) {
193      children.add(falseBlock);
194    }
195    if (elseConditionalBranching != null) {
196      children.add(elseConditionalBranching);
197    }
198    return children;
199  }
200
201  /**
202   * {@inheritDoc}
203   */
204  @Override
205  protected void replaceElement(GoloElement<?> original, GoloElement<?> newElement) {
206    if (condition == original) {
207      condition(newElement);
208    } else if (trueBlock == original) {
209      whenTrue(newElement);
210    } else if (elseConditionalBranching == original && newElement instanceof ConditionalBranching) {
211      elseBranch(newElement);
212    } else if (elseConditionalBranching == original) {
213      whenFalse(newElement instanceof Noop ? null : newElement);
214      elseBranch(null);
215    } else if (falseBlock == original && newElement instanceof ConditionalBranching) {
216      elseBranch(newElement);
217      whenFalse(null);
218    } else if (falseBlock == original) {
219      whenFalse(newElement instanceof Noop ? null : newElement);
220    } else {
221      throw cantReplace(original, newElement);
222    }
223  }
224
225}