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.Collections;
014import java.util.List;
015
016/**
017 * A function decorator.
018 *
019 * <p>For instance in:
020 * <pre class="listing"><code class="lang-golo" data-lang="golo">
021 * &#64;decorator
022 * function foo = |x| -> x
023 * </code></pre>
024 */
025public final class Decorator extends GoloElement<Decorator> {
026
027  private ExpressionStatement<?> expressionStatement;
028
029  private boolean constant = false;
030
031  private Decorator(ExpressionStatement<?> expressionStatement) {
032    super();
033    setExpressionStatement(expressionStatement);
034  }
035
036  /**
037   * Create a function decorator from the given expression.
038   *
039   * <p>Since this function is implicitly called by {@link GoloFunction#decoratedWith(Object...)}, it should not be
040   * necessary to use it directly. For instance:
041   * <pre class="listing"><code class="lang-java" data-lang="java">
042   * function("foo").returns(constant(42)).decoratedWith(ReferenceLookup.of("deco"))
043   * </code></pre>
044   * creates
045   * <pre class="listing"><code class="lang-golo" data-lang="golo">
046   * &#64;deco
047   * function foo = -> 42
048   * </code></pre>
049   *
050   * <p>Valid expressions are:<ul>
051   * <li>already created decorators ({@link Decorator})
052   * <li>references ({@link ReferenceLookup}) as in <code class="lang-golo">&#64;deco</code>
053   * <li>function invocations ({@link FunctionInvocation}) as in <code class="lang-golo">&#64;deco(42)</code>
054   * <li>closures ({@link ClosureReference}) as in <code class="lang-golo">&#64;(|f| -> |x| -> f(x) + 42)</code>
055   * <li>anonymous calls as in <code class="lang-golo">&#64;deco("answer")(42)</code>
056   * </ul>
057   *
058   * @param expr the expression representing the decorator or any element that can be converted into a valid
059   * {@link ExpressionStatement}
060   *
061   * @see ExpressionStatement#of(Object)
062   */
063  public static Decorator of(Object expr) {
064    if (expr instanceof Decorator) {
065      return (Decorator) expr;
066    }
067    return new Decorator(ExpressionStatement.of(expr));
068  }
069
070  protected Decorator self() { return this; }
071
072  public ExpressionStatement<?> expression() {
073    return expressionStatement;
074  }
075
076  private boolean isValidDecoratorExpressoin(ExpressionStatement<?> expr) {
077    return expr instanceof ReferenceLookup
078          || expr instanceof FunctionInvocation
079          || expr instanceof ClosureReference
080          || (expr instanceof BinaryOperation
081            && OperatorType.ANON_CALL.equals(((BinaryOperation) expr).getType()));
082  }
083
084  private void setExpressionStatement(ExpressionStatement<?> expr) {
085    if (!isValidDecoratorExpressoin(expr)) {
086      throw new IllegalArgumentException("Decorator expression must be a reference or an invocation, got a "
087          + expr.getClass().getSimpleName());
088    }
089    this.expressionStatement = makeParentOf(expr);
090  }
091
092  public boolean isConstant() {
093    return constant;
094  }
095
096  public Decorator constant(boolean constant) {
097    this.constant = constant;
098    return this;
099  }
100
101  public Decorator constant() {
102    return constant(true);
103  }
104
105  private ExpressionStatement<?> wrapLookup(ReferenceLookup reference, ExpressionStatement<?> expression) {
106    return FunctionInvocation.of(reference.getName())
107      .constant(this.isConstant())
108      .withArgs(expression);
109  }
110
111  private ExpressionStatement<?> wrapInvocation(FunctionInvocation invocation, ExpressionStatement<?> expression) {
112    return invocation.call(FunctionInvocation.of(null).constant(this.isConstant()).withArgs(expression));
113  }
114
115  private ExpressionStatement<?> wrapAnonymousCall(ExpressionStatement<?> call, ExpressionStatement<?> expression) {
116    return call.call(FunctionInvocation.of(null).constant(this.isConstant()).withArgs(expression));
117  }
118
119  public ExpressionStatement<?> wrapExpression(ExpressionStatement<?> expression) {
120    if (expressionStatement instanceof ReferenceLookup) {
121      return wrapLookup((ReferenceLookup) expressionStatement, expression);
122    }
123    if (expressionStatement instanceof FunctionInvocation) {
124      return wrapInvocation((FunctionInvocation) expressionStatement, expression);
125    }
126    return wrapAnonymousCall(expressionStatement, expression);
127  }
128
129  /**
130   * {@inheritDoc}
131   */
132  @Override
133  public void accept(GoloIrVisitor visitor) {
134    visitor.visitDecorator(this);
135  }
136
137  /**
138   * {@inheritDoc}
139   */
140  @Override
141  public List<GoloElement<?>> children() {
142    return Collections.singletonList(expressionStatement);
143  }
144
145  /**
146   * {@inheritDoc}
147   */
148  @Override
149  protected void replaceElement(GoloElement<?> original, GoloElement<?> newElement) {
150    if (expressionStatement.equals(original) && newElement instanceof ExpressionStatement) {
151      setExpressionStatement(ExpressionStatement.of(newElement));
152    } else {
153      throw cantReplace(original, newElement);
154    }
155  }
156
157  @Override
158  public String toString() {
159    return "Decorator{" + expressionStatement + "}";
160  }
161}