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.*;
014
015import static java.util.Collections.unmodifiableSet;
016
017/**
018 * Represents a {@code union} element.
019 *
020 * <p>For instance:
021 * <pre class="listing"><code class="lang-golo" data-lang="golo">
022 * union ConsList = {
023 *   Empty
024 *   Cons = {head, tail}
025 * }
026 * </code></pre>
027 */
028public final class Union extends GoloType<Union> implements ToplevelGoloElement {
029
030  private final Set<UnionValue> values = new LinkedHashSet<>();
031  private final Set<MacroInvocation> macroCalls = new LinkedHashSet<>();
032
033  private Union(String name) {
034    super(name);
035  }
036
037  /**
038   * Creates a union type.
039   *
040   * <p>Typical usage:
041   * <pre class="listing"><code class="lang-java" data-lang="java">
042   * union("ConsList")
043   *   .value("Empty")
044   *   .value("Cons",
045   *      "head",
046   *      "tail")
047   * </code></pre>
048   * creates
049   * <pre class="listing"><code class="lang-golo" data-lang="golo">
050   * union ConsList = {
051   *   Empty
052   *   Cons = {head, tail}
053   * }
054   * </code></pre>
055   *
056   * @param name the name of the union.
057   */
058  public static Union union(String name) {
059    return new Union(name);
060  }
061
062  protected Union self() { return this; }
063
064  /**
065   * Adds a new value to this union.
066   */
067  public boolean addValue(UnionValue value) {
068    makeParentOf(value);
069    return values.add(value);
070  }
071
072  /**
073   * Adds a macro invocation to this union.
074   *
075   * <p>The macro is supposed to return a {@link UnionValue} when expanded.
076   * This allows decorator-like syntax on union values:
077   * <pre class="listing"><code class="lang-golo" data-lang="golo">
078   * union Foo = {
079   *   &#64;someMacro
080   *   Value = {a, b}
081   *
082   *   &#64;otherMacro
083   *   OtherValue
084   * }
085   * </code></pre>
086   */
087  public boolean addMacroInvocation(MacroInvocation macroCall) {
088    return macroCalls.add(makeParentOf(macroCall));
089  }
090
091  /**
092   * Adds a value or a macro invocation according to the given argument.
093   *
094   * @see #addValue(UnionValue)
095   * @see #addMacroInvocation(MacroInvocation)
096   */
097  public boolean addElement(GoloElement<?> elt) {
098    if (elt instanceof UnionValue) {
099      return addValue((UnionValue) elt);
100    }
101    if (elt instanceof MacroInvocation) {
102      return addMacroInvocation((MacroInvocation) elt);
103    }
104    throw cantConvert("UnionValue or MacroInvocation", elt);
105  }
106
107  public Collection<UnionValue> getValues() {
108    return unmodifiableSet(values);
109  }
110
111  /**
112   * Adds a new value to this union.
113   *
114   * <p>Convenient fluent method to add a new value.
115   * <p>This is a builder method.
116   *
117   * @see #addValue(UnionValue)
118   * @see UnionValue#members(Object...)
119   */
120  public Union value(String name, Object... members) {
121    UnionValue value = new UnionValue(name);
122    value.members(members);
123    addValue(value);
124    return this;
125  }
126
127  /**
128   * {@inheritDoc}
129   */
130  @Override
131  public void accept(GoloIrVisitor visitor) {
132    visitor.visitUnion(this);
133  }
134
135  /**
136   * {@inheritDoc}
137   */
138  @Override
139  public List<GoloElement<?>> children() {
140    List<GoloElement<?>> children = new LinkedList<>(values);
141    children.addAll(macroCalls);
142    return children;
143  }
144
145  /**
146   * {@inheritDoc}
147   */
148  @Override
149  protected void replaceElement(GoloElement<?> original, GoloElement<?> newElement) {
150    if (values.contains(original)) {
151      values.remove(original);
152    } else if (macroCalls.contains(original)) {
153      macroCalls.remove(original);
154    } else {
155      throw cantReplace(original, newElement);
156    }
157    addElement(newElement);
158  }
159}