001/*
002 * Copyright (c) 2012-2017 Institut National des Sciences Appliquées de Lyon (INSA-Lyon)
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 */
009
010package org.eclipse.golo.compiler.ir;
011
012import java.util.LinkedList;
013import java.util.List;
014import java.util.HashSet;
015import java.util.Set;
016import java.util.Collection;
017
018import org.eclipse.golo.compiler.parser.GoloASTNode;
019
020import static java.util.Collections.unmodifiableList;
021import static java.util.Arrays.asList;
022import java.util.Objects;
023import static org.eclipse.golo.compiler.ir.Builders.*;
024import static java.util.Objects.requireNonNull;
025
026public final class GoloFunction extends ExpressionStatement implements Scope {
027
028  private static final SymbolGenerator SYMBOLS = new SymbolGenerator("function");
029
030  private String name;
031  private boolean isLocal = false;
032  private Scope scope = Scope.MODULE;
033
034  private final List<String> parameterNames = new LinkedList<>();
035  private final List<String> syntheticParameterNames = new LinkedList<>();
036  private boolean varargs = false;
037  private Block block;
038  private boolean synthetic = false;
039  private boolean decorator = false;
040  private String syntheticSelfName = null;
041  private String decoratorRef = null;
042  private final LinkedList<Decorator> decorators = new LinkedList<>();
043
044  public static enum Scope {
045    MODULE, AUGMENT, CLOSURE
046  }
047
048  GoloFunction() {
049    super();
050    block = Builders.block();
051    makeParentOf(block);
052  }
053
054  @Override
055  public GoloFunction ofAST(GoloASTNode n) {
056    super.ofAST(n);
057    return this;
058  }
059
060  // name -----------------------------------------------------------------------------------------
061  public GoloFunction name(String n) {
062    this.name = n;
063    return this;
064  }
065
066  public String getName() {
067    return name;
068  }
069
070  public boolean isMain() {
071    return "main".equals(name) && getArity() == 1;
072  }
073
074  public boolean isModuleInit() {
075    return GoloModule.MODULE_INITIALIZER_FUNCTION.equals(this.name);
076  }
077
078  public boolean isAnonymous() {
079    return this.name == null;
080  }
081
082  // synthetic ------------------------------------------------------------------------------------
083  public GoloFunction synthetic() {
084    this.synthetic = true;
085    return this;
086  }
087
088  public boolean isSynthetic() {
089    return synthetic;
090  }
091
092  // decorator ------------------------------------------------------------------------------------
093  public GoloFunction decorator() {
094    return decorator(true);
095  }
096
097  public GoloFunction decorator(boolean d) {
098    this.decorator = d;
099    return this;
100  }
101
102  public boolean isDecorator() {
103    return decorator;
104  }
105
106  // visibility -----------------------------------------------------------------------------------
107  public GoloFunction local() {
108    return local(true);
109  }
110
111  public GoloFunction local(boolean isLocal) {
112    this.isLocal = isLocal;
113    return this;
114  }
115
116  public boolean isLocal() {
117    return this.isLocal;
118  }
119
120  // scope ----------------------------------------------------------------------------------------
121  public GoloFunction inScope(GoloFunction.Scope s) {
122    this.scope = s;
123    return this;
124  }
125
126  public GoloFunction inAugment() {
127    return inAugment(true);
128  }
129
130  public GoloFunction inAugment(boolean isInAugment) {
131    if (isInAugment) {
132      this.scope = Scope.AUGMENT;
133    }
134    return this;
135  }
136
137  public boolean isInAugment() {
138    return Scope.AUGMENT.equals(scope);
139  }
140
141  public GoloFunction asClosure() {
142    this.scope = Scope.CLOSURE;
143    return this;
144  }
145
146  // block ----------------------------------------------------------------------------------------
147  public GoloFunction block(Object... statements) {
148    return this.block(Builders.block(statements));
149  }
150
151  public GoloFunction block(Block block) {
152    this.block = requireNonNull(block);
153    for (String param : parameterNames) {
154      addParameterToBlockReferences(param);
155    }
156    makeParentOf(this.block);
157    return this;
158  }
159
160  public Block getBlock() {
161    return block;
162  }
163
164  public GoloFunction returns(Object expression) {
165    this.block.add(Builders.returns(expression));
166    return this;
167  }
168
169  public void insertMissingReturnStatement() {
170    if (!this.block.hasReturn() && !this.isModuleInit()) {
171      ReturnStatement missingReturnStatement = Builders.returns(constant(null)).synthetic();
172      if (this.isMain()) {
173        missingReturnStatement.returningVoid();
174      }
175      this.block.addStatement(missingReturnStatement);
176    }
177  }
178
179  // parameters and varargs -----------------------------------------------------------------------
180  public GoloFunction varargs(boolean isVarargs) {
181    this.varargs = isVarargs;
182    return this;
183  }
184
185  public GoloFunction varargs() {
186    return this.varargs(true);
187  }
188
189  public boolean isVarargs() {
190    return varargs;
191  }
192
193  public int getArity() {
194    return parameterNames.size() + syntheticParameterNames.size();
195  }
196
197  public GoloFunction withParameters(String... names) {
198    return withParameters(asList(names));
199  }
200
201  public GoloFunction withParameters(Collection<String> names) {
202    for (String name : names) {
203      addParameterToBlockReferences(name);
204      this.parameterNames.add(name);
205    }
206    return this;
207  }
208
209  private void addParameterToBlockReferences(String name) {
210    this.block.getReferenceTable().add(localRef(name));
211  }
212
213  public int getSyntheticParameterCount() {
214    return syntheticParameterNames.size();
215  }
216
217  public List<String> getParameterNames() {
218    LinkedList<String> list = new LinkedList<>(syntheticParameterNames);
219    list.addAll(parameterNames);
220    return unmodifiableList(list);
221  }
222
223  public List<String> getSyntheticParameterNames() {
224    return unmodifiableList(syntheticParameterNames);
225  }
226
227  public void addSyntheticParameters(Set<String> names) {
228    Set<String> existing = new HashSet<>(getParameterNames());
229    for (String name : names) {
230      if (!existing.contains(name) && !name.equals(syntheticSelfName)) {
231        LocalReference ref = block.getReferenceTable().get(name);
232        if (ref == null || !ref.isModuleState()) {
233          this.syntheticParameterNames.add(name);
234        }
235      }
236    }
237  }
238
239  public String getSyntheticSelfName() {
240    return syntheticSelfName;
241  }
242
243  public void setSyntheticSelfName(String name) {
244    if (syntheticParameterNames.contains(name)) {
245      this.syntheticParameterNames.remove(name);
246      this.syntheticSelfName = name;
247    }
248  }
249
250  public void captureClosedReference() {
251    if (synthetic && syntheticSelfName != null) {
252      LocalReference self = block.getReferenceTable().get(syntheticSelfName);
253      ClosureReference closureReference = asClosureReference();
254      closureReference.updateCapturedReferenceNames();
255      block.prependStatement(Builders.define(self).as(closureReference));
256    }
257  }
258
259  // decorators -----------------------------------------------------------------------------------
260  public GoloFunction decoratedWith(Object... decorators) {
261    for (Object deco : decorators) {
262      if (deco instanceof Decorator) {
263        this.addDecorator((Decorator) deco);
264      } else {
265        throw cantConvert("Decorator", deco);
266      }
267    }
268    return this;
269  }
270
271  public boolean isDecorated() {
272    return !decorators.isEmpty();
273  }
274
275  public String getDecoratorRef() {
276    return decoratorRef;
277  }
278
279  public void addDecorator(Decorator decorator) {
280    this.decorators.add(decorator);
281    makeParentOf(decorator);
282  }
283
284  public List<Decorator> getDecorators() {
285    return unmodifiableList(decorators);
286  }
287
288  public boolean hasDecorators() {
289    return !decorators.isEmpty();
290  }
291
292  public GoloFunction createDecorator() {
293    ExpressionStatement expr = refLookup("__$$_original");
294    for (Decorator decorator : this.getDecorators()) {
295      expr = decorator.wrapExpression(expr);
296    }
297    this.decoratorRef = SYMBOLS.next(name + "_decorator");
298    return functionDeclaration(this.decoratorRef)
299      .decorator()
300      .inScope(this.scope)
301      .withParameters("__$$_original")
302      .returns(expr);
303  }
304
305  //-----------------------------------------------------------------------------------------------
306  public ClosureReference asClosureReference() {
307    if (scope != Scope.CLOSURE) {
308      throw new IllegalStateException("Can't get a closure reference of a non-closure function");
309    }
310    return new ClosureReference(this);
311  }
312
313  @Override
314  public void relink(ReferenceTable table) {
315    block.relink(table);
316  }
317
318  @Override
319  public void relinkTopLevel(ReferenceTable table) {
320    block.relinkTopLevel(table);
321  }
322
323  @Override
324  public String toString() {
325    return String.format("Function<name=%s, arity=%d, vararg=%s>",
326        getName(),
327        getArity(),
328        isVarargs());
329  }
330
331  @Override
332  public void accept(GoloIrVisitor visitor) {
333    visitor.visitFunction(this);
334  }
335
336  @Override
337  public void walk(GoloIrVisitor visitor) {
338    for (Decorator deco : decorators) {
339      deco.accept(visitor);
340    }
341    block.accept(visitor);
342  }
343
344  @Override
345  protected void replaceElement(GoloElement original, GoloElement newElement) {
346    throw cantReplace();
347  }
348
349  @Override
350  public int hashCode() {
351    return Objects.hash(this.name, this.getArity(), this.varargs);
352  }
353
354  @Override
355  public boolean equals(Object obj) {
356    if (obj == this) {
357      return true;
358    }
359    if (obj == null) {
360      return false;
361    }
362    if (getClass() != obj.getClass()) {
363      return false;
364    }
365    final GoloFunction other = (GoloFunction) obj;
366    if (!Objects.equals(this.name, other.name)) {
367      return false;
368    }
369    if (this.varargs != other.varargs) {
370      return false;
371    }
372    return this.getArity() == other.getArity();
373  }
374}