001/*
002 * Copyright (c) 2012-2021 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.Arrays;
014import java.util.List;
015import org.eclipse.golo.runtime.InvalidDestructuringException;
016
017/**
018 * Represents a binary operation.
019 *
020 * @see OperatorType
021 */
022public final class BinaryOperation extends ExpressionStatement<BinaryOperation> {
023
024  private final OperatorType type;
025  private ExpressionStatement<?> leftExpression;
026  private ExpressionStatement<?> rightExpression;
027
028  private BinaryOperation(OperatorType type) {
029    super();
030    this.type = type;
031  }
032
033  /**
034   * Create a binary operation.
035   *
036   * @param type a {@link OperatorType} or a {@code String} representing the operator.
037   */
038  public static BinaryOperation of(Object type) {
039    return new BinaryOperation(OperatorType.of(type));
040  }
041
042  /**
043   * Full generic binary operation creation in one call.
044   *
045   * <p>Less readable than the fluent API, but useful when doing meta-generation
046   *
047   * @param type the type of the operation ({@link BinaryOperation#of(Object)})
048   * @param left the left expression.
049   * @param right the right expression.
050   * @return a configured binary operation.
051   */
052  public static BinaryOperation create(Object type, Object left, Object right) {
053    return of(type).left(left).right(right);
054  }
055
056  protected BinaryOperation self() { return this; }
057
058  public OperatorType getType() {
059    return type;
060  }
061
062  public ExpressionStatement<?> left() {
063    return leftExpression;
064  }
065
066  /**
067   * Define the left expression of this operation.
068   *
069   * <p>This is a builder method.
070   *
071   * @param expr the {@link gololang.ir.ExpressionStatement} to use as the left
072   * operand
073   * @return this operation
074   */
075  public BinaryOperation left(Object expr) {
076    this.leftExpression = makeParentOf(ExpressionStatement.of(expr));
077    return this;
078  }
079
080  /**
081   * Define the right expression of this operation.
082   *
083   * <p>This is a builder method.
084   *
085   * @param expr the {@link gololang.ir.ExpressionStatement} to use as the
086   * right operand
087   * @return this operation
088   */
089  public BinaryOperation right(Object expr) {
090    this.rightExpression = makeParentOf(ExpressionStatement.of(expr));
091    if (this.type == OperatorType.ELVIS_METHOD_CALL && this.rightExpression instanceof MethodInvocation) {
092      ((MethodInvocation) this.rightExpression).nullSafe(true);
093    }
094    return this;
095  }
096
097  public ExpressionStatement<?> right() {
098    return rightExpression;
099  }
100
101  @Override
102  public String toString() {
103    return String.format("%s %s %s", leftExpression, type, rightExpression);
104  }
105
106  public boolean isMethodCall() {
107    return this.getType() == OperatorType.METHOD_CALL
108      || this.getType() == OperatorType.ELVIS_METHOD_CALL
109      || this.getType() == OperatorType.ANON_CALL;
110  }
111
112  /**
113   * New style destructuring helper.
114   *
115   * <p>The destructuring must be to exactly two values. No remainer syntax is allowed.
116   * <p>The destructured values are the left and right expressions.
117   *
118   * @param number number of variable that will be affected.
119   * @param substruct whether the destructuring is complete or should contains a sub structure.
120   * @param toSkip a boolean array indicating the elements to skip.
121   * @return an array containing the values to assign.
122   */
123  public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) {
124    if (number == 2 && !substruct) {
125      return new Object[]{leftExpression, rightExpression};
126    }
127    throw new InvalidDestructuringException("A BinaryOperation must destructure to exactly two values");
128  }
129
130  /**
131   * {@inheritDoc}
132   */
133  @Override
134  public void accept(GoloIrVisitor visitor) {
135    visitor.visitBinaryOperation(this);
136  }
137
138  /**
139   * {@inheritDoc}
140   */
141  @Override
142  public List<GoloElement<?>> children() {
143    return Arrays.asList(leftExpression, rightExpression);
144  }
145
146  /**
147   * {@inheritDoc}
148   */
149  @Override
150  protected void replaceElement(GoloElement<?> original, GoloElement<?> newElement) {
151    if (!(newElement instanceof ExpressionStatement)) {
152      throw cantConvert("ExpressionStatement", newElement);
153    }
154    if (leftExpression.equals(original)) {
155      left(newElement);
156    } else if (rightExpression.equals(original)) {
157      right(newElement);
158    } else {
159      throw cantReplace(original, newElement);
160    }
161  }
162}