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}