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}