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