001/*
002 * Copyright (c) 2012-2018 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 org.eclipse.golo.compiler;
012
013import java.util.Deque;
014import java.util.LinkedList;
015import java.util.concurrent.atomic.AtomicLong;
016
017/**
018 * Name generator for synthetic objects.
019 * <p>
020 * The generated name follows the patter {@code __$$_<name>_<counter>}.
021 * The default name is {@code symbol}.
022 * <p>
023 * The generator maintains a stack of scope names, to generate hierarchical names.
024 * <pre class="listing"><code class="lang-java" data-lang="java">
025 * SymbolGenerator sym = new SymbolGenerator("closure");
026 * sym.current(); // __$$_closure_0
027 * sym.next(); // __$$_closure_1
028 * sym.current(); // __$$_closure_1
029 * sym.enter("scope");
030 * sym.next(); // __$$_closure_scope_2
031 * sym.enter("subscope");
032 * sym.next(); // __$$_closure_scope_subscope_3
033 * sym.exit().exit();
034 * sym.next(); // __$$_closure_4
035 * </code></pre>
036 * <p>
037 * Since the counter maintains uniqueness, the name and scopes only purpose is to give
038 * somewhat readable names to help debugging.
039 * <p>
040 * Be warned that the uniqueness is only preserved in the context of a single generator.
041 * Two independent generators with the same name (and scope) can produce identical names.
042 * <p>
043 * A counter is used instead of e.g. the generation timestamp or a random number to guarantee stability across
044 * compilations to ease debugging.
045 * <p>If a true uniqueness is require, or if the somewhat predictability of the symbol is a concern, one can use
046 * {@link #getFor(String)} or even {@link #next(String)} in conjunction with {@code System.nanoTime()} or
047 * {@code Random.nextLong()} (for instance <code class="lang-java">sym.next(String.valueOf(System.nanoTime()))</code>
048 */
049public final class SymbolGenerator {
050  public static final String PREFIX = "__$$_";
051  public static final String DEFAULT_NAME = "symbol";
052  public static final String ESCAPE_MANGLE = "$";
053  public static final String JOIN = "_";
054  private final AtomicLong counter = new AtomicLong();
055  private final Deque<String> prefixes = new LinkedList<>();
056
057  public SymbolGenerator(String name) {
058    this.prefixes.addLast(name == null ? DEFAULT_NAME : name.replace('.', '$'));
059  }
060
061  public SymbolGenerator() {
062    this.prefixes.addLast(DEFAULT_NAME);
063  }
064
065  private String name(String localName, long idx) {
066    return name(
067        (localName == null || "".equals(localName)
068          ? ""
069          : (localName + JOIN))
070        + idx);
071  }
072
073  private String name(String localName) {
074    String name = PREFIX + String.join(JOIN, prefixes);
075    if (localName != null && !"".equals(localName)) {
076      name += JOIN + localName;
077    }
078    return name;
079  }
080
081  /**
082   * Generates the next name for the current context.
083   * <p>
084   * Increments the counter and returns the name generated by the current scope and counter.
085   *
086   * @return the next name
087   */
088  public String next() {
089    return next(null);
090  }
091
092  /**
093   * Generates the next name for the current context and given simple name.
094   * <p>
095   * Increments the counter and returns the name generated by the given name and the current scope and counter, and increment
096   * it.
097   *
098   * <p><strong>Warning</strong>: no check is made that the given name will produce a valid language symbol.
099   * @param name the simple name to derive the unique name from
100   * @return the corresponding next name
101   */
102  public String next(String name) {
103    return name(name, counter.incrementAndGet());
104  }
105
106  /**
107   * Mangles the given name.
108   *
109   * <p>Mangling is escaped for names beginning by {@code ESCAPE_MANGLE}.
110   *
111   * <p>For instance:
112   * <pre class="listing"><code class="lang-golo" data-lang="golo">
113   * let symb = SymbolGenerator("foo")
114   * symb: getFor("bar")  # __$$_foo_bar
115   * symb: getFor("$bar") # bar
116   * </code></pre>
117   * <p><strong>Warning</strong>: no check is made that the given name will produce a valid language symbol.
118   */
119  public String getFor(String localName) {
120    if (localName.startsWith(ESCAPE_MANGLE)) {
121      return localName.substring(ESCAPE_MANGLE.length());
122    }
123    return name(localName);
124  }
125
126  /**
127   * Generate the name for the current context and given simple name.
128   * <p>
129   * Returns the name generated by the given name and the current scope and counter, without
130   * incrementing it.
131   * <p><strong>Warning</strong>: no check is made that the given name will produce a valid language symbol.
132   *
133   * @param name the simple name to derive the unique name from
134   * @return the corresponding generated name
135   */
136  public String current(String name) {
137    return name(name, counter.get());
138  }
139
140  /**
141   * Generate the name for the current context.
142   * <p>
143   * Returns the name generated by the current scope and counter, without
144   * incrementing it.
145   *
146   * @return the generated name
147   */
148  public String current() {
149    return name(null, counter.get());
150  }
151
152  /**
153   * Exit from a scope.
154   */
155  public SymbolGenerator exit() {
156    if (this.prefixes.size() > 1) {
157      this.prefixes.removeLast();
158    }
159    return this;
160  }
161
162  /**
163   * Enter a hierarchical scope.
164   *
165   * <p><strong>Warning</strong>: no check is made that the given name will produce a valid language symbol.
166   * @param scopeName the name of the scope.
167   */
168  public SymbolGenerator enter(String scopeName) {
169    this.prefixes.addLast(scopeName.replace('.', '$'));
170    return this;
171  }
172
173}