1 /*
2 Copyright (c) 2011-2014 Timur Gafarov 
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28 
29 module dmodule;
30 
31 import std.stdio;
32 import std.file;
33 import std.ascii;
34 import std.conv;
35 import std.array;
36 import std.path;
37 import std.datetime;
38 
39 import lexer;
40 
41 struct Pattern
42 {
43     string[] symbols;
44     uint position;
45 
46     bool satisfies(string token)
47     {
48         if (symbols[position] == "*")
49         {
50             if (isIdentifier(token))
51             {
52                 position++;
53                 if (position == symbols.length)
54                     position = 0;
55                 return true;
56             }
57             else
58             {
59                 position = 0;
60                 return false;
61             }
62         }
63         else if (symbols[position] == "*,")
64         {
65             if (isIdentifier(token))
66             {
67                 return true;
68             }
69             else if (token == ",")
70             {
71                 return true;
72             }
73             else
74             {
75                 position++;
76                 if (position == symbols.length)
77                 {
78                     position = 0;
79                     return false;
80                 }
81                 else
82                 {
83                     return satisfies(token);
84                 }
85             }
86         }
87         else
88         {
89             string sym = symbols[position];
90             if (token == sym)
91             {
92                 position++;
93                 if (position == symbols.length)
94                     position = 0;
95                 return true;
96             }
97             else
98             {
99                 position = 0;
100                 return false;
101             }
102         }
103     }
104 
105     string toString()
106     {
107         return symbols.to!string;
108     }
109 }
110 
111 struct FilterLevel
112 {
113     Pattern[] patterns;
114     bool positive = true;
115 
116     void addPattern(string[] symbols)
117     {
118         patterns ~= Pattern(symbols, 0);
119     }
120 
121     bool satisfies(string token)
122     {
123         bool res = false;
124         foreach(ref p; patterns)
125             if (positive)
126                 res = res | p.satisfies(token);
127             else
128                 res = res | (!p.satisfies(token));
129         return res;
130     }
131 }
132 
133 class Filter
134 {
135     Lexer lex;
136     FilterLevel[] levels;
137     string current;
138 
139     this(string text)
140     {
141         lex = new Lexer(text);
142         lex.addDelimiters();
143     }
144 
145     FilterLevel* addLevel(bool positive = true)
146     {
147         FilterLevel flevel;
148         flevel.positive = positive;
149         levels ~= flevel;
150         return &levels[$-1];
151     }
152 
153     string getNext()
154     {
155         do
156         {
157             lex.readNext();
158             string token = lex.current;
159             if (token == "")
160                 return token;
161             else 
162             {
163                 bool res = false;
164                 foreach(ref level; levels)
165                 {
166                     if (!level.satisfies(token))
167                     {
168                         res = false;
169                         break;
170                     }
171                     else
172                         res = true;
173                 }
174                 if (res)
175                     return token;
176             }
177         }
178         while(true);
179     }
180 
181     void readNext()
182     {
183         current = getNext();
184     }
185 }
186 
187 bool isIdentifier(string s)
188 {
189     return (s.length && (isAlpha(s[0]) || s[0] == '_'));
190 }
191 
192 string moduleToPath(string modulename, string ext)
193 {
194     string fname = modulename;
195     fname = replace(fname, ".", "/");
196     fname = fname ~ ext;
197     return fname;	
198 }
199 
200 string pathToModule(string path)
201 {
202     string mdl = path;
203     mdl = replace(mdl, "/", ".");
204     return mdl;	
205 }
206 
207 final class DModule
208 {
209     private:
210 
211     Filter filter;
212     string[string] imports;
213 
214     public:
215 
216     string filename;
217     string ext;
218     string packageName;
219     SysTime lastModified;
220     DModule[string] backdeps;
221     int[string] versionIds;
222     int[string] debugIds;
223     bool forceRebuild = false;
224     bool globalFile = false;
225 
226     // TODO: simple debug condition
227 
228     this(string filename, string ext)
229     {
230         this.filename = filename;
231         this.ext = ext;
232 
233         auto text = readText(filename);
234         filter = new Filter(text);
235         auto l1 = filter.addLevel();
236         l1.addPattern(["{"]);
237         l1.addPattern(["}"]);
238         l1.addPattern([";"]);
239         l1.addPattern(["else"]);
240         l1.addPattern(["version", "(", "*", ")"]);
241         l1.addPattern(["version", "=", "*"]);
242         l1.addPattern(["debug", "(", "*", ")"]);
243         l1.addPattern(["debug", "=", "*"]);
244         l1.addPattern(["import", "*,", ";"]);
245         l1.addPattern(["import", "*", ":"]);
246         l1.addPattern(["import", "*", "=", "*"]);
247     }
248 
249     override string toString() 
250     {
251         string output = lastModified.toISOExtString() ~ " " ~ to!string(cast(int)globalFile) ~ " ";
252         foreach(i,v; imports) 
253             output ~= v ~ " ";
254         return output;
255     }
256 
257     string[] importedModules()
258     {
259         return imports.keys;
260     }
261 
262     string[] importedFiles()
263     {
264         return imports.values;
265     }
266 
267     void addImportFile(string filename)
268     {
269         imports[pathToModule(stripExtension(filename))] = filename;
270     }
271 
272     void addImport(string moduleName)
273     {
274         imports[moduleName] = moduleToPath(moduleName, ext);
275     }
276 
277     bool buildDependencyList()
278     {
279         try
280         {
281             parseImports();
282         }
283         catch(Exception)
284         {
285             writefln("%s: syntax error", filename);
286             return false;
287         }
288         return true;
289     }
290 
291     private:
292 
293     void parseImports()
294     {
295         string token = "";
296         do 
297         {
298             filter.readNext();
299             token = filter.current;
300             if (token.length)
301             {
302                 parseStatement(false);
303             }
304         }
305         while (token.length > 0);
306     }
307 
308     void parseStatement(bool skip)
309     {
310         if (filter.current == "version" || filter.current == "debug")
311         {
312             if (!skip)
313                 parseVersionStatement();
314         }
315         else if (filter.current == "import")
316         {
317             if (!skip)
318                 parseImportStatement();
319         }
320     }
321 
322     void parseVersionStatement()
323     {
324         string stat = filter.current;
325         filter.readNext(); // pop version/debug
326 
327         if (filter.current == "(")
328         {
329             filter.readNext(); // pop (
330             string id = filter.current;
331 
332             filter.readNext(); // pop id
333             if (filter.current != ")")
334                 throw new Exception("Syntax error");
335             filter.readNext(); // pop )
336             bool skip;
337 
338             if (stat == "version")
339                 skip = (id in versionIds) is null;
340             else if (stat == "debug")
341                 skip = (id in debugIds) is null;
342 
343             parseBlock(skip);
344 
345             if (filter.current == "else")
346             {
347                 parseBlock(!skip);
348             }
349         }
350         else if (filter.current == "=")
351         {
352             filter.readNext(); // pop =
353             string id = filter.current;
354 
355             if (stat == "version")
356                 versionIds[id] = 1;
357             else if (stat == "debug")
358                 debugIds[id] = 1;
359         }
360     }
361 
362     void parseImportStatement()
363     {
364         filter.readNext(); // pop import
365         string impName = filter.current;
366         filter.readNext(); // pop impName
367 
368         // End of import list
369         if (filter.current == ";" || filter.current == ":")
370         {
371             addImport(impName);
372             return;
373         }
374         // Import list
375         else if (filter.current == ",")
376         {
377             addImport(impName);
378             parseImportStatement();
379         }
380         // Renamed import
381         else if (filter.current == "=")
382         {
383             filter.readNext(); // pop =
384             addImport(filter.current);
385         }
386     }
387 
388     void parseBlock(bool skip)
389     {
390         if (filter.current == "{")
391         {
392             while(filter.current != "}")
393             {
394                 filter.readNext();
395                 parseStatement(skip);
396                 if (filter.current == "")
397                     throw new Exception("Syntax error");
398             }
399             filter.readNext(); // pop }
400         }
401         else
402         {
403             parseStatement(skip);
404         }
405     }
406 }
407