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 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 without using the counter.
108   *
109   * <p>This can be used in macros to provide hygiene by mangling the local variable names. Mangling is escaped for
110   * names beginning by {@code ESCAPE_MANGLE}.
111   *
112   * <p>For instance:
113   * <pre class="listing"><code class="lang-golo" data-lang="golo">
114   * let symb = SymbolGenerator("foo")
115   * symb: getFor("bar")  # __$$_foo_bar
116   * symb: getFor("$bar") # bar
117   * </code></pre>
118   * <p><strong>Warning</strong>: no check is made that the given name will produce a valid language symbol.
119   */
120  public String getFor(String localName) {
121    if (localName.startsWith(ESCAPE_MANGLE)) {
122      return localName.substring(ESCAPE_MANGLE.length());
123    }
124    return name(localName);
125  }
126
127  /**
128   * Generate the name for the current context and given simple name.
129   * <p>
130   * Returns the name generated by the given name and the current scope and counter, without
131   * incrementing it.
132   * <p><strong>Warning</strong>: no check is made that the given name will produce a valid language symbol.
133   *
134   * @param name the simple name to derive the unique name from
135   * @return the corresponding generated name
136   */
137  public String current(String name) {
138    return name(name, counter.get());
139  }
140
141  /**
142   * Generate the name for the current context.
143   * <p>
144   * Returns the name generated by the current scope and counter, without
145   * incrementing it.
146   *
147   * @return the generated name
148   */
149  public String current() {
150    return name(null, counter.get());
151  }
152
153  /**
154   * Exit from a scope.
155   */
156  public SymbolGenerator exit() {
157    if (this.prefixes.size() > 1) {
158      this.prefixes.removeLast();
159    }
160    return this;
161  }
162
163  /**
164   * Enter a hierarchical scope.
165   *
166   * <p><strong>Warning</strong>: no check is made that the given name will produce a valid language symbol.
167   * @param scopeName the name of the scope.
168   */
169  public SymbolGenerator enter(String scopeName) {
170    if (scopeName == null || scopeName.isEmpty()) {
171      return this;
172    }
173    this.prefixes.addLast(scopeName.replace('.', '$'));
174    return this;
175  }
176
177}