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.unmodifiableCollection;
016import static java.util.Collections.unmodifiableSet;
017
018public final class ReferenceTable {
019
020  private ReferenceTable parent;
021  private final Map<String, LocalReference> table = new LinkedHashMap<>();
022
023  public ReferenceTable() {
024    this(null);
025  }
026
027  private ReferenceTable(ReferenceTable parent) {
028    this.parent = parent;
029  }
030
031  public ReferenceTable parent() {
032    return this.parent;
033  }
034
035  public boolean isEmpty() {
036    return this.table.isEmpty();
037  }
038
039  public ReferenceTable add(LocalReference reference) {
040    table.put(reference.getName(), reference);
041    return this;
042  }
043
044  public int size() {
045    return table.size() + (parent != null ? parent.size() : 0);
046  }
047
048  public boolean hasReferenceFor(String name) {
049    return table.containsKey(name) || parent != null && parent.hasReferenceFor(name);
050  }
051
052  public void updateFrom(GoloStatement<?> statement) {
053    if (statement instanceof ReferencesHolder) {
054      for (LocalReference r : ((ReferencesHolder) statement).getDeclaringReferences()) {
055        this.add(r);
056      }
057    }
058  }
059
060  public LocalReference get(String name) {
061    LocalReference reference = table.get(name);
062    if (reference != null) {
063      return reference;
064    }
065    if (parent != null && parent != this) {
066      return parent.get(name);
067    }
068    return null;
069  }
070
071  public Set<String> ownedSymbols() {
072    return unmodifiableSet(table.keySet());
073  }
074
075  public Collection<LocalReference> ownedReferences() {
076    return unmodifiableCollection(table.values());
077  }
078
079  /**
080   * Redefines the parent table for this table.
081   *
082   * <p>If {@code prune} is {@code true}, the owned references already presents in the parent are removed.
083   *
084   * @param parent the new parent table
085   * @param prune remove duplicated owned references
086   */
087  public void relink(ReferenceTable parent, boolean prune) {
088    if (parent == this) { return; }
089    if (prune) {
090      for (LocalReference reference : parent.references()) {
091        if (this.table.containsKey(reference.getName())) {
092          this.remove(reference.getName());
093        }
094      }
095    }
096    this.parent = parent;
097  }
098
099  public void relink(ReferenceTable parent) {
100    relink(parent, true);
101  }
102
103  private boolean isLinkedTo(ReferenceTable other) {
104    return this == other
105      || this.parent == other
106      || (this.parent != null && this.parent.isLinkedTo(other));
107  }
108
109  public void relinkTopLevel(ReferenceTable topLevel) {
110    if (this == topLevel) { return; }
111    if (this.parent == null) {
112      this.parent = topLevel;
113    } else if (!this.isLinkedTo(topLevel) && !topLevel.isLinkedTo(this)) {
114      this.parent.relinkTopLevel(topLevel);
115    }
116  }
117
118  public Set<String> symbols() {
119    LinkedHashSet<String> localSymbols = new LinkedHashSet<>(table.keySet());
120    if (parent != null) {
121      localSymbols.addAll(parent.symbols());
122    }
123    return localSymbols;
124  }
125
126  /**
127   * Return the set of the references known to this table.
128   * <p>
129   * Contains the own references as well as the ones from the parent table.
130   */
131  public Collection<LocalReference> references() {
132    Collection<LocalReference> localReferences = new LinkedHashSet<>(table.values());
133    if (parent != null) {
134      for (LocalReference ref : parent.references()) {
135        if (!table.containsKey(ref.getName())) {
136          localReferences.add(ref);
137        }
138      }
139    }
140    return localReferences;
141  }
142
143  public ReferenceTable fork() {
144    return new ReferenceTable(this);
145  }
146
147  public ReferenceTable flatDeepCopy(boolean turnIntoConstants) {
148    ReferenceTable referenceTable = new ReferenceTable();
149    for (LocalReference reference : references()) {
150      String refName = reference.getName();
151      if (reference.isModuleState()) {
152        referenceTable.add(LocalReference.of(refName)
153            .kind(reference.getKind()));
154        continue;
155      }
156      if (turnIntoConstants && !table.containsKey(refName)) {
157        referenceTable.add(LocalReference.of(refName)
158            .synthetic(reference.isSynthetic()));
159      } else {
160        referenceTable.add(LocalReference.of(refName)
161            .synthetic(reference.isSynthetic())
162            .kind(reference.getKind()));
163      }
164    }
165    return referenceTable;
166  }
167
168  public void remove(String name) {
169    table.remove(name);
170  }
171
172  @Override
173  public String toString() {
174    StringBuilder representation = new StringBuilder("ReferenceTable: {\n");
175    for (Map.Entry<String, LocalReference> elt : table.entrySet()) {
176      representation.append(elt.getKey()).append(": ").append(elt.getValue()).append('\n');
177    }
178    representation.append('}');
179    if (parent != null && parent != this) {
180      representation.append(" => ").append(parent.toString());
181    }
182    return representation.toString();
183  }
184}