1 /// Author: Aziz Köksal
2 /// License: GPL3
3 /// $(Maturity high)
4 module dil.lexer.IdTable;
5 
6 import dil.lexer.TokensEnum,
7        dil.lexer.Funcs;
8 import dil.Unicode,
9        dil.String;
10 import common;
11 
12 public import dil.lexer.Identifier,
13               dil.lexer.IDs,
14               dil.lexer.IDsEnum;
15 
16 
17 
18 
19 /// A table for hoarding and retrieving identifiers.
20 class IdTable
21 {
22   /// A set of common, predefined identifiers for fast lookups.
23   static Identifier*[hash_t] staticTable;
24   /// A table that grows with every newly found, unique identifier.
25   Identifier*[hash_t] growingTable;
26 
27   alias LookupMethod = Identifier* delegate(hash_t, cstring);
28   /// Looks up idString in the growing table.
29   LookupMethod inGrowing;
30 
31   /// Constructs an IdTable object.
32   ///
33   /// Loads keywords and predefined identifiers into the static table.
34   this()
35   {
36     inGrowing = &_inGrowing_unsafe; // Default to unsafe function.
37 
38     if (staticTable is null) // Initialize global static table?
39     {
40       foreach (ref id; IDs.getAllIDs())
41         staticTable[hashOf(id.str)] = &id;
42       staticTable.rehash;
43     }
44   }
45 
46   /// Returns true if str is a valid D identifier.
47   static bool isIdentifierString(cstring str)
48   {
49     if (str.length == 0 || isdigit(str[0]))
50       return false;
51     size_t idx;
52     do
53     {
54       auto c = dil.Unicode.decode(str, idx);
55       if (c == ERROR_CHAR || !(isident(c) || !isascii(c) && isUniAlpha(c)))
56         return false;
57     } while (idx < str.length);
58     return true;
59   }
60 
61   /// Returns true if str is a keyword or
62   /// a special token (__FILE__, __LINE__ etc.)
63   bool isReservedIdentifier(cstring str)
64   {
65     if (str.length == 0)
66       return false;
67     auto id = inStatic(str);
68     // True if id is in the table and if it's not a normal identifier.
69     return id && id.kind != TOK.Identifier;
70   }
71 
72   /// Returns true if this is a valid identifier and if it's not reserved.
73   bool isValidUnreservedIdentifier(cstring str)
74   {
75     return isIdentifierString(str) && !isReservedIdentifier(str);
76   }
77 
78   /// Looks up idString in both tables.
79   Identifier* lookup(cstring idString)
80   {
81     auto idHash = hashOf(idString);
82     auto id = inStatic(idHash);
83     assert(!id || idString == id.str,
84       Format("bad hash function:\n ‘{}’ != ‘{}’; hash={}",
85         idString, id.str, idHash));
86     if (id)
87       return id;
88     return inGrowing(idHash, idString);
89   }
90 
91   /// Looks up the hash of an id in the static table.
92   Identifier* inStatic(hash_t idHash)
93   {
94     auto id = idHash in staticTable;
95     return id ? *id : null;
96   }
97 
98   /// Looks up idString in the static table.
99   Identifier* inStatic(cstring idString)
100   {
101     auto id = hashOf(idString) in staticTable;
102     return id ? *id : null;
103   }
104 
105   /// Sets the thread safety mode of the growing table.
106   void setThreadsafe(bool safe)
107   {
108     inGrowing = safe ? &_inGrowing_safe : &_inGrowing_unsafe;
109   }
110 
111   /// Returns true if access to the growing table is thread-safe.
112   bool isThreadsafe()
113   {
114     return inGrowing is &_inGrowing_safe;
115   }
116 
117   /// Looks up idString in the table.
118   ///
119   /// Adds idString to the table if not found.
120   private Identifier* _inGrowing_unsafe(hash_t idHash, cstring idString)
121   out(id)
122   { assert(id !is null); }
123   body
124   {
125     auto id = idHash in growingTable;
126     assert(!id || idString == (*id).str,
127       Format("bad hash function:\n ‘{}’ != ‘{}’", idString, (*id).str));
128     if (id)
129       return *id;
130     auto newID = new Identifier(idString.idup, TOK.Identifier);
131     growingTable[idHash] = newID;
132     return newID;
133   }
134 
135   /// Looks up idString in the table.
136   ///
137   /// Adds idString to the table if not found.
138   /// Access to the data structure is synchronized.
139   private Identifier* _inGrowing_safe(hash_t idHash, cstring idString)
140   {
141     synchronized
142       return _inGrowing_unsafe(idHash, idString);
143   }
144 
145   /// Counter for anonymous identifiers.
146   static uint anonCount;
147 
148   /// Generates an anonymous identifier.
149   ///
150   /// Concatenates prefix with anonCount.
151   /// The identifier is not inserted into the table.
152   Identifier* genAnonymousID(cstring prefix)
153   {
154     auto num = itoa(++anonCount);
155     return new Identifier((prefix ~ num).idup, TOK.Identifier);
156   }
157 
158   /// Generates an identifier for an anonymous enum.
159   Identifier* genAnonEnumID()
160   {
161     return genAnonymousID("__anonenum");
162   }
163 
164   /// Generates an identifier for an anonymous class.
165   Identifier* genAnonClassID()
166   {
167     return genAnonymousID("__anonclass");
168   }
169 
170   /// Generates an identifier for an anonymous struct.
171   Identifier* genAnonStructID()
172   {
173     return genAnonymousID("__anonstruct");
174   }
175 
176   /// Generates an identifier for an anonymous union.
177   Identifier* genAnonUnionID()
178   {
179     return genAnonymousID("__anonunion");
180   }
181 
182   /// Generates an identifier for a module which has got no valid name.
183   Identifier* genModuleID()
184   {
185     return genAnonymousID("__module");
186   }
187 }
188 
189 void testIdTable()
190 {
191   // TODO: write benchmark.
192   // Single table
193 
194   // Single table. synchronized
195 
196   // Two tables.
197 
198   // Two tables. synchronized
199 }