1 /**
2  * This file is part of DCD, a development tool for the D programming language.
3  * Copyright (C) 2014 Brian Schott
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 module dsymbol.scope_;
20 
21 import dsymbol.symbol;
22 import dsymbol.import_;
23 import dsymbol.builtin.names;
24 import containers.ttree;
25 import containers.unrolledlist;
26 import std.algorithm : canFind, any;
27 import std.experimental.logger;
28 import stdx.allocator.mallocator : Mallocator;
29 
30 /**
31  * Contains symbols and supports lookup of symbols by cursor position.
32  */
33 struct Scope
34 {
35 	@disable this(this);
36 	@disable this();
37 
38 	/**
39 	 * Params:
40 	 *     begin = the beginning byte index
41 	 *     end = the ending byte index
42 	 */
43 	this (uint begin, uint end)
44 	{
45 		this.startLocation = begin;
46 		this.endLocation = end;
47 	}
48 
49 	~this()
50 	{
51 		foreach (child; children[])
52 			typeid(Scope).destroy(child);
53 		foreach (symbol; _symbols)
54 		{
55 			if (symbol.owned)
56 				typeid(DSymbol).destroy(symbol.ptr);
57 		}
58 	}
59 
60 	/**
61 	 * Params:
62 	 *     cursorPosition = the cursor position in bytes
63 	 * Returns:
64 	 *     the innermost scope that contains the given cursor position
65 	 */
66 	Scope* getScopeByCursor(size_t cursorPosition) return pure @nogc
67 	{
68 		if (cursorPosition < startLocation) return null;
69 		if (cursorPosition > endLocation) return null;
70 		foreach (child; children[])
71 		{
72 			auto childScope = child.getScopeByCursor(cursorPosition);
73 			if (childScope !is null)
74 				return childScope;
75 		}
76 		return cast(typeof(return)) &this;
77 	}
78 
79 	/**
80 	 * Params:
81 	 *     cursorPosition = the cursor position in bytes
82 	 * Returns:
83 	 *     all symbols in the scope containing the cursor position, as well as
84 	 *     the symbols in parent scopes of that scope.
85 	 */
86 	DSymbol*[] getSymbolsInCursorScope(size_t cursorPosition)
87 	{
88 		import std.array : array;
89 		import std.algorithm.iteration : map;
90 
91 		auto s = getScopeByCursor(cursorPosition);
92 		if (s is null)
93 			return null;
94 
95 		UnrolledList!(DSymbol*) retVal;
96 		Scope* sc = s;
97 		while (sc !is null)
98 		{
99 			foreach (item; sc._symbols[])
100 			{
101 				if (item.ptr.kind == CompletionKind.withSymbol)
102 				{
103 					if (item.ptr.type !is null)
104 						foreach (i; item.ptr.type.opSlice())
105 							retVal.insert(i);
106 				}
107 				else if (item.ptr.type !is null && item.ptr.kind == CompletionKind.importSymbol)
108 				{
109 					if (item.ptr.qualifier != SymbolQualifier.selectiveImport)
110 					{
111 						foreach (i; item.ptr.type.opSlice())
112 							retVal.insert(i);
113 					}
114 					else
115 						retVal.insert(item.ptr.type);
116 				}
117 				else
118 					retVal.insert(item.ptr);
119 			}
120 			sc = sc.parent;
121 		}
122 		return array(retVal[]);
123 	}
124 
125 	/**
126 	 * Params:
127 	 *     name = the symbol name to search for
128 	 * Returns:
129 	 *     all symbols in this scope or parent scopes with the given name
130 	 */
131 	inout(DSymbol)*[] getSymbolsByName(istring name) inout
132 	{
133 		import std.array : array, appender;
134 		import std.algorithm.iteration : map;
135 
136 		DSymbol s = DSymbol(name);
137 		auto er = _symbols.equalRange(SymbolOwnership(&s));
138 		if (!er.empty)
139 			return cast(typeof(return)) array(er.map!(a => a.ptr));
140 
141 		// Check symbols from "with" statement
142 		DSymbol ir2 = DSymbol(WITH_SYMBOL_NAME);
143 		auto r2 = _symbols.equalRange(SymbolOwnership(&ir2));
144 		if (!r2.empty)
145 		{
146 			auto app = appender!(DSymbol*[])();
147 			foreach (e; r2)
148 			{
149 				if (e.type is null)
150 					continue;
151 				foreach (withSymbol; e.type.getPartsByName(s.name))
152 					app.put(cast(DSymbol*) withSymbol);
153 			}
154 			if (app.data.length > 0)
155 				return cast(typeof(return)) app.data;
156 		}
157 
158 		if (name != CONSTRUCTOR_SYMBOL_NAME &&
159 			name != DESTRUCTOR_SYMBOL_NAME &&
160 			name != UNITTEST_SYMBOL_NAME &&
161 			name != THIS_SYMBOL_NAME)
162 		{
163 			// Check imported symbols
164 			DSymbol ir = DSymbol(IMPORT_SYMBOL_NAME);
165 
166 			auto app = appender!(DSymbol*[])();
167 			foreach (e; _symbols.equalRange(SymbolOwnership(&ir)))
168 			{
169 				if (e.type is null)
170 					continue;
171 				if (e.qualifier == SymbolQualifier.selectiveImport && e.type.name == name)
172 					app.put(cast(DSymbol*) e.type);
173 				else
174 					foreach (importedSymbol; e.type.getPartsByName(s.name))
175 						app.put(cast(DSymbol*) importedSymbol);
176 			}
177 			if (app.data.length > 0)
178 				return cast(typeof(return)) app.data;
179 		}
180 		if (parent is null)
181 			return [];
182 		return parent.getSymbolsByName(name);
183 	}
184 
185 	/**
186 	 * Params:
187 	 *     name = the symbol name to search for
188 	 *     cursorPosition = the cursor position in bytes
189 	 * Returns:
190 	 *     all symbols with the given name in the scope containing the cursor
191 	 *     and its parent scopes
192 	 */
193 	DSymbol*[] getSymbolsByNameAndCursor(istring name, size_t cursorPosition)
194 	{
195 		auto s = getScopeByCursor(cursorPosition);
196 		if (s is null)
197 			return [];
198 		return s.getSymbolsByName(name);
199 	}
200 
201 	DSymbol* getFirstSymbolByNameAndCursor(istring name, size_t cursorPosition)
202 	{
203 		auto s = getSymbolsByNameAndCursor(name, cursorPosition);
204 		return s.length > 0 ? s[0] : null;
205 	}
206 
207 	/**
208 	 * Returns an array of symbols that are present at global scope
209 	 */
210 	inout(DSymbol)*[] getSymbolsAtGlobalScope(istring name) inout
211 	{
212 		if (parent !is null)
213 			return parent.getSymbolsAtGlobalScope(name);
214 		return getSymbolsByName(name);
215 	}
216 
217 	bool hasSymbolRecursive(const(DSymbol)* symbol) const
218 	{
219 		return _symbols[].canFind!(a => a == symbol) || children[].any!(a => a.hasSymbolRecursive(symbol));
220 	}
221 
222 	/// The scope that contains this one
223 	Scope* parent;
224 
225 	/// Child scopes
226 	UnrolledList!(Scope*, Mallocator, false) children;
227 
228 	/// Start location of this scope in bytes
229 	uint startLocation;
230 
231 	/// End location of this scope in bytes
232 	uint endLocation;
233 
234 	auto symbols() @property
235 	{
236 		return _symbols[];
237 	}
238 
239 	/**
240 	 * Adds the given symbol to this scope.
241 	 * Params:
242 	 *     symbol = the symbol to add
243 	 *     owns = if true, the symbol's destructor will be called when this
244 	 *         scope's destructor is called.
245 	 */
246 	void addSymbol(DSymbol* symbol, bool owns)
247 	{
248 		assert(symbol !is null);
249 		_symbols.insert(SymbolOwnership(symbol, owns));
250 	}
251 
252 private:
253 	/// Symbols contained in this scope
254 	TTree!(SymbolOwnership, Mallocator, true, "a.opCmp(b) < 0", false) _symbols;
255 }