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 * @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 * @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">@deco</code> 053 * <li>function invocations ({@link FunctionInvocation}) as in <code class="lang-golo">@deco(42)</code> 054 * <li>closures ({@link ClosureReference}) as in <code class="lang-golo">@(|f| -> |x| -> f(x) + 42)</code> 055 * <li>anonymous calls as in <code class="lang-golo">@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}