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 gololang.FunctionReference;
014
015import java.util.ArrayList;
016import java.util.Iterator;
017import java.util.LinkedHashSet;
018import java.util.List;
019
020/**
021 * A container of top-level {@code GoloElement}.
022 * <p>
023 * This class is mainly used by top-level macros to return a collection of golo top-level elements,
024 * i.e. functions, structs, augments and so on, since a macro must return a unique GoloElement to
025 * inject in the Ir by replacing the macro call.
026 *
027 * <p>This element must never be present in a tree.
028 */
029public final class ToplevelElements extends GoloElement<ToplevelElements> implements Iterable<GoloElement<?>> {
030
031  private final LinkedHashSet<GoloElement<?>> elements = new LinkedHashSet<>();
032
033  private ToplevelElements(GoloElement<?>... elements) {
034    for (GoloElement<?> e : elements) {
035      this.add(e);
036    }
037  }
038
039  /**
040   * Creates a top-level elements container.
041   *
042   * <p>Mainly used to return several nodes from a top-level macro.
043   * <p>If only a {@code ToplevelElements} instance is given, it is returned unchanged.
044   *
045   * @param elements the Golo Elements to add.
046   * @return a {@code ToplevelElements} containing all the given elements.
047   */
048  public static ToplevelElements of(Object... elements) {
049    if (elements.length == 1 && elements[0] instanceof ToplevelElements) {
050      return (ToplevelElements) elements[0];
051    }
052    if (elements.length == 1 && elements[0] instanceof Iterable) {
053      @SuppressWarnings("unchecked")
054      Iterable<Object> it = (Iterable<Object>) elements[0];
055      return fromIterable(it);
056    }
057    ToplevelElements tl = new ToplevelElements();
058    for (Object e : elements) {
059      tl.add(e);
060    }
061    return tl;
062  }
063
064/**
065   * Creates a top-level elements container.
066   *
067   * <p>Mainly used to return several nodes from a top-level macro.
068   */
069  public static ToplevelElements fromIterable(Iterable<Object> elements) {
070    ToplevelElements tl = new ToplevelElements();
071    for (Object e : elements) {
072      tl.add(e);
073    }
074    return tl;
075  }
076
077  protected ToplevelElements self() { return this; }
078
079  public ToplevelElements add(Object element) {
080    if (element instanceof ToplevelElements) {
081      for (GoloElement<?> e : (ToplevelElements) element) {
082        this.add(e);
083      }
084    } else if (element instanceof ToplevelGoloElement && !(element instanceof Noop)) {
085      GoloElement<?> elt = (GoloElement<?>) element;
086      this.elements.add(makeParentOf(elt));
087    } else {
088      throw new IllegalArgumentException(element.toString());
089    }
090    return this;
091  }
092
093  public boolean isEmpty() {
094    return this.elements.isEmpty();
095  }
096
097  /**
098   * Map a golo function on the contained elements.
099   * <p>
100   * This can be used in top-level macros to apply the macro on a top-level from a previous macro application, for
101   * instance when stacking decorator-like macros returning a {@code ToplevelElements}.
102   */
103  public ToplevelElements map(FunctionReference fun) throws Throwable {
104    ToplevelElements res = new ToplevelElements();
105    for (GoloElement<?> elt : this) {
106      res.add(fun.invoke(elt));
107    }
108    return res;
109  }
110
111  @Override
112  public Iterator<GoloElement<?>> iterator() {
113    return elements.iterator();
114  }
115
116  @Override
117  public List<GoloElement<?>> children() {
118    return new ArrayList<>(elements);
119  }
120
121  @Override
122  public void replaceElement(GoloElement<?> original, GoloElement<?> newElement) {
123    if (elements.contains(original)) {
124      elements.remove(original);
125      add(newElement);
126    } else {
127      throw cantReplace(original, newElement);
128    }
129  }
130
131  @Override
132  public void accept(GoloIrVisitor visitor) {
133    visitor.visitToplevelElements(this);
134  }
135}
136