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.LinkedList;
014import java.util.List;
015import java.util.HashSet;
016import java.util.Set;
017import java.util.Collection;
018import java.util.Objects;
019
020import org.eclipse.golo.compiler.SymbolGenerator;
021
022import static java.util.Collections.unmodifiableList;
023import static java.util.Arrays.asList;
024
025/**
026 * Represents a function declaration.
027 *
028 * <p>Such as
029 * <pre class="listing"><code class="lang-golo" data-lang="golo">
030 * function foo = |x| -> x + 1
031 * </code></pre>
032 */
033public final class GoloFunction extends ExpressionStatement<GoloFunction> implements BlockContainer<GoloFunction>, ToplevelGoloElement, NamedElement {
034
035  private static final SymbolGenerator SYMBOLS = new SymbolGenerator("golo.ir.function");
036
037  private String name;
038  private boolean isLocal = false;
039  private Scope scope = Scope.MODULE;
040
041  private final List<String> parameterNames = new LinkedList<>();
042  private final List<String> syntheticParameterNames = new LinkedList<>();
043  private boolean varargs = false;
044  private Block block;
045  private boolean synthetic = false;
046  private boolean decorator = false;
047  private String syntheticSelfName = null;
048  private String decoratorRef = null;
049  private final LinkedList<Decorator> decorators = new LinkedList<>();
050
051  public enum Scope {
052    MODULE, AUGMENT, CLOSURE
053  }
054
055  protected GoloFunction self() { return this; }
056
057  private GoloFunction() {
058    super();
059    this.block(Block.empty());
060  }
061
062  /**
063   * Copy constructor.
064   * <p>
065   * make a shallow copy of the given function: the block is shared, parameters and decorators are copied.
066   */
067  private GoloFunction(GoloFunction function) {
068    this.name = function.name;
069    this.isLocal = function.isLocal;
070    this.scope = function.scope;
071    this.varargs = function.varargs;
072    this.synthetic = function.synthetic;
073    this.decorator = function.decorator;
074    this.syntheticSelfName = function.syntheticSelfName;
075    this.decoratorRef = function.decoratorRef;
076    this.parameterNames.addAll(function.parameterNames);
077    this.syntheticParameterNames.addAll(function.syntheticParameterNames);
078    this.documentation(function.documentation());
079    this.decorators.addAll(function.decorators);
080    this.block(function.block);
081    this.positionInSourceCode(function.positionInSourceCode());
082  }
083
084  /**
085   * Creates a function declaration.
086   *
087   * <p>Typical usage:
088   * <pre class="listing"><code class="lang-java" data-lang="java">
089   * function("foo").withParameters("x").returns(plus(ReferenceLookup.of("x"), constant(1)))
090   * </code></pre>
091   * creates
092   * <pre class="listing"><code class="lang-golo" data-lang="golo">
093   * function foo = |x| -> x + 1
094   * </code></pre>
095   */
096  public static GoloFunction function(Object name) {
097    if (name instanceof GoloFunction) {
098      return new GoloFunction((GoloFunction) name);
099    }
100    if (name == null) {
101      return new GoloFunction();
102    }
103    return new GoloFunction().name(name.toString());
104  }
105
106  // name -----------------------------------------------------------------------------------------
107  public GoloFunction name(String n) {
108    this.name = n;
109    return this;
110  }
111
112  @Override
113  public String getName() {
114    return name;
115  }
116
117  public boolean isMain() {
118    return "main".equals(name) && getArity() == 1;
119  }
120
121  public boolean isModuleInit() {
122    return GoloModule.MODULE_INITIALIZER_FUNCTION.equals(this.name);
123  }
124
125  public boolean isAnonymous() {
126    return this.name == null;
127  }
128
129  // synthetic ------------------------------------------------------------------------------------
130  public GoloFunction synthetic(boolean s) {
131    this.synthetic = s;
132    return this;
133  }
134
135  public GoloFunction synthetic() {
136    return synthetic(true);
137  }
138
139  public boolean isSynthetic() {
140    return synthetic;
141  }
142
143  // decorator ------------------------------------------------------------------------------------
144  public GoloFunction decorator() {
145    return decorator(true);
146  }
147
148  public GoloFunction decorator(boolean d) {
149    this.decorator = d;
150    return this;
151  }
152
153  public boolean isDecorator() {
154    return decorator;
155  }
156
157  // visibility -----------------------------------------------------------------------------------
158  public GoloFunction local() {
159    return local(true);
160  }
161
162  public GoloFunction local(boolean isLocal) {
163    this.isLocal = isLocal;
164    return this;
165  }
166
167  public boolean isLocal() {
168    return this.isLocal;
169  }
170
171  // scope ----------------------------------------------------------------------------------------
172  public GoloFunction inScope(Scope s) {
173    this.scope = s;
174    return this;
175  }
176
177  public GoloFunction inAugment() {
178    return inAugment(true);
179  }
180
181  public GoloFunction inAugment(boolean isInAugment) {
182    if (isInAugment) {
183      this.scope = Scope.AUGMENT;
184    }
185    return this;
186  }
187
188  public boolean isInAugment() {
189    return Scope.AUGMENT.equals(scope);
190  }
191
192  public boolean isInModule() {
193    return Scope.MODULE.equals(scope);
194  }
195
196  public GoloFunction asClosure() {
197    this.scope = Scope.CLOSURE;
198    return this;
199  }
200
201  // block ----------------------------------------------------------------------------------------
202  /**
203   * {@inheritDoc}
204   */
205  @Override
206  public GoloFunction block(Object block) {
207    this.block = makeParentOf(Block.of(block));
208    for (String param : parameterNames) {
209      addParameterToBlockReferences(param);
210    }
211    return this;
212  }
213
214  /**
215   * {@inheritDoc}
216   */
217  @Override
218  public Block getBlock() {
219    return block;
220  }
221
222  public GoloFunction returns(Object expression) {
223    this.block.add(ReturnStatement.of(expression));
224    return this;
225  }
226
227  public void insertMissingReturnStatement() {
228    if (!this.block.hasReturn() && !this.isModuleInit()) {
229      if (this.isMain()) {
230        this.block.add(ReturnStatement.empty().synthetic());
231      } else {
232        this.block.add(ReturnStatement.of(null).synthetic());
233      }
234    }
235  }
236
237  // parameters and varargs -----------------------------------------------------------------------
238  public GoloFunction varargs(boolean isVarargs) {
239    this.varargs = isVarargs;
240    return this;
241  }
242
243  public GoloFunction varargs() {
244    return this.varargs(true);
245  }
246
247  public boolean isVarargs() {
248    return varargs;
249  }
250
251  public int getArity() {
252    return parameterNames.size() + syntheticParameterNames.size();
253  }
254
255  public GoloFunction withParameters(Object... names) {
256    return withParameters(asList(names));
257  }
258
259  public GoloFunction withParameters(Collection<?> names) {
260    for (Object name : names) {
261      addParameterToBlockReferences(name.toString());
262      this.parameterNames.add(name.toString());
263    }
264    return this;
265  }
266
267  private void addParameterToBlockReferences(String name) {
268    this.block.getReferenceTable().add(LocalReference.of(name));
269  }
270
271  public int getSyntheticParameterCount() {
272    return syntheticParameterNames.size();
273  }
274
275  public List<String> getParameterNames() {
276    LinkedList<String> list = new LinkedList<>(syntheticParameterNames);
277    list.addAll(parameterNames);
278    return list;
279  }
280
281  public Collection<String> getSyntheticParameterNames() {
282    return unmodifiableList(syntheticParameterNames);
283  }
284
285  public void addSyntheticParameters(Set<String> names) {
286    Set<String> existing = new HashSet<>(getParameterNames());
287    for (String name : names) {
288      if (!existing.contains(name) && !name.equals(syntheticSelfName)) {
289        LocalReference ref = block.getReferenceTable().get(name);
290        if (ref == null || !ref.isModuleState()) {
291          this.syntheticParameterNames.add(name);
292        }
293      }
294    }
295  }
296
297  public String getSyntheticSelfName() {
298    return syntheticSelfName;
299  }
300
301  public LocalReference getSyntheticSelfReference() {
302    return block.getReferenceTable().get(syntheticSelfName);
303  }
304
305  public void setSyntheticSelfName(String name) {
306    if (syntheticParameterNames.contains(name)) {
307      this.syntheticParameterNames.remove(name);
308      this.syntheticSelfName = name;
309    }
310  }
311
312  public void captureClosedReference() {
313    if (synthetic && syntheticSelfName != null) {
314      block.prepend(AssignmentStatement.create(getSyntheticSelfReference(), asClosureReference(), true));
315    }
316  }
317
318  // decorators -----------------------------------------------------------------------------------
319  /**
320   * Adds decorators to this function.
321   *
322   * <p>The objects are converted into a decorator if needed.
323   */
324  public GoloFunction decoratedWith(Object... decorators) {
325    for (Object deco : decorators) {
326      this.addDecorator(Decorator.of(deco));
327    }
328    return this;
329  }
330
331  public boolean isDecorated() {
332    return !decorators.isEmpty();
333  }
334
335  public String getDecoratorRef() {
336    return decoratorRef;
337  }
338
339  public void addDecorator(Decorator decorator) {
340    this.decorators.add(makeParentOf(decorator));
341  }
342
343  public void removeDecorator(Decorator decorator) {
344    this.decorators.remove(decorator);
345  }
346
347  public List<Decorator> getDecorators() {
348    return unmodifiableList(decorators);
349  }
350
351  public GoloFunction createDecorator() {
352    ExpressionStatement<?> expr = ReferenceLookup.of("__$$_original");
353    for (Decorator decorator : this.getDecorators()) {
354      expr = decorator.wrapExpression(expr);
355    }
356    this.decoratorRef = SYMBOLS.next(name + "_decorator");
357    return function(this.decoratorRef)
358      .decorator()
359      .inScope(this.scope)
360      .withParameters("__$$_original")
361      .returns(expr);
362  }
363
364  //-----------------------------------------------------------------------------------------------
365  public ClosureReference asClosureReference() {
366    if (scope != Scope.CLOSURE) {
367      throw new IllegalStateException("Can't get a closure reference of a non-closure function");
368    }
369    return new ClosureReference(this);
370  }
371
372
373  /**
374   * Return a new function with the same signature as this one.
375   * <p>
376   * Can be used to generate a wrapper for a given function for instance.
377   */
378  public GoloFunction sameSignature() {
379    return function(name)
380      .local(isLocal)
381      .inScope(scope)
382      .withParameters(parameterNames)
383      .varargs(varargs)
384      .synthetic(true);
385  }
386
387  /**
388   * Return the parameters of this function wrapped in a {@code ReferenceLookup}.
389   */
390  public Object[] parametersAsRefs() {
391    return parameterNames.stream().map(ReferenceLookup::of).toArray();
392  }
393
394  @Override
395  public String toString() {
396    return String.format("Function{name=%s, arity=%d, vararg=%s, synthetic=%s, self=%s}",
397        getName(),
398        getArity(),
399        isVarargs(),
400        synthetic,
401        syntheticSelfName);
402  }
403
404  @Override
405  public void accept(GoloIrVisitor visitor) {
406    visitor.visitFunction(this);
407  }
408
409  @Override
410  public List<GoloElement<?>> children() {
411    LinkedList<GoloElement<?>> children = new LinkedList<>(decorators);
412    children.add(block);
413    return children;
414  }
415
416  @Override
417  protected void replaceElement(GoloElement<?> original, GoloElement<?> newElement) {
418    throw cantReplace();
419  }
420
421  @Override
422  public int hashCode() {
423    return Objects.hash(this.name, this.getArity(), this.varargs);
424  }
425
426  @Override
427  public boolean equals(Object obj) {
428    if (obj == this) {
429      return true;
430    }
431    if (obj == null) {
432      return false;
433    }
434    if (getClass() != obj.getClass()) {
435      return false;
436    }
437    final GoloFunction other = (GoloFunction) obj;
438    if (!Objects.equals(this.name, other.name)) {
439      return false;
440    }
441    if (this.varargs != other.varargs) {
442      return false;
443    }
444    return this.getArity() == other.getArity();
445  }
446
447  public static String uniqueName() {
448    return SYMBOLS.next();
449  }
450
451  public static String uniqueName(Object base) {
452    return SYMBOLS.next(base.toString());
453  }
454}