1 // This file is part of Visual D
2 //
3 // Visual D integrates the D programming language into Visual Studio
4 // Copyright (c) 2010-2011 by Rainer Schuetze, All Rights Reserved
5 //
6 // Distributed under the Boost Software License, Version 1.0.
7 // See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt
8 
9 module vdc.semantic;
10 
11 import vdc.util;
12 import vdc.ast.mod;
13 import vdc.ast.node;
14 import vdc.ast.type;
15 import vdc.ast.aggr;
16 import vdc.ast.decl;
17 import vdc.ast.expr;
18 import vdc.ast.tmpl;
19 import vdc.ast.writer;
20 import vdc.parser.engine;
21 import vdc.logger;
22 import vdc.interpret;
23 import vdc.versions;
24 
25 import stdext.util;
26 import stdext.array;
27 import stdext.path;
28 
29 import std.exception;
30 import std.stdio;
31 import std.string;
32 import std.array;
33 import std.conv;
34 import std.datetime;
35 
36 int semanticErrors;
37 
38 alias object.AssociativeArray!(Node, const(bool)) _wa1; // fully instantiate type info for bool[Node]
39 alias object.AssociativeArray!(string, const(VersionInfo)) _wa2; // fully instantiate type info for VersionInfo[string]
40 alias object.AssociativeArray!(string, const(bool)) _wa3; // fully instantiate type info for bool[string]
41 
42 class SemanticException : Exception
43 {
44     this(string msg)
45     {
46         super(msg);
47     }
48 }
49 
50 class InterpretException : Exception
51 {
52     this()
53     {
54         super("cannot interpret");
55     }
56 }
57 
58 enum MessageType
59 {
60     Warning,
61     Error,
62     Message
63 }
64 
65 void delegate(MessageType,string) fnSemanticWriteError = null;
66 
67 string semanticErrorWriteLoc(string filename, ref const(TextPos) pos)
68 {
69     string txt = filename;
70     if(pos.line > 0)
71         txt ~= text("(", pos.line, ")");
72     txt ~= ": ";
73     semanticErrors++;
74     return txt;
75 }
76 
77 void semanticErrorLoc(T...)(string filename, ref const(TextPos) pos, T args)
78 {
79     foreach(a; args)
80         if(typeid(a) == typeid(ErrorType) || typeid(a) == typeid(ErrorValue))
81             return;
82 
83     string msg = semanticErrorWriteLoc(filename, pos);
84     msg ~= text(args);
85     if(fnSemanticWriteError)
86         fnSemanticWriteError(MessageType.Error, msg);
87     logInfo("%s", msg); // avoid interpreting % in message
88 }
89 
90 void semanticErrorPos(T...)(ref const(TextPos) pos, T args)
91 {
92     string filename;
93     if(Scope.current && Scope.current.mod)
94         filename = Scope.current.mod.filename;
95     else
96         filename = "at global scope";
97     semanticErrorLoc(filename, pos, args);
98 }
99 
100 void semanticError(T...)(T args)
101 {
102     TextPos pos;
103     semanticErrorPos(pos, args);
104 }
105 
106 void semanticErrorFile(T...)(string fname, T args)
107 {
108     TextPos pos;
109     semanticErrorLoc(fname, pos, args);
110 }
111 
112 void semanticMessage(string msg)
113 {
114     if(fnSemanticWriteError)
115         fnSemanticWriteError(MessageType.Message, msg);
116 }
117 
118 ErrorValue semanticErrorValue(T...)(T args)
119 {
120     TextPos pos;
121     semanticErrorPos(pos, args);
122     //throw new InterpretException;
123     return Singleton!(ErrorValue).get();
124 }
125 
126 ErrorType semanticErrorType(T...)(T args)
127 {
128     semanticErrorPos(TextPos(), args);
129     return Singleton!(ErrorType).get();
130 }
131 
132 alias Node Symbol;
133 
134 class Context
135 {
136     Scope scop;
137     Value[Node] vars;
138     Context parent;
139 
140     this(Context p)
141     {
142         parent = p;
143     }
144 
145     Value getThis()
146     {
147         if(parent)
148             return parent.getThis();
149         return null;
150     }
151 
152     void setThis(Value v)
153     {
154         setValue(null, v);
155     }
156 
157     Value getValue(Node n)
158     {
159         if(auto pn = n in vars)
160             return *pn;
161         if(parent)
162             return parent.getValue(n);
163         return null;
164     }
165 
166     void setValue(Node n, Value v)
167     {
168         vars[n] = v;
169     }
170 }
171 
172 class AggrContext : Context
173 {
174     Value instance;
175     bool virtualCall;
176 
177     this(Context p, Value inst)
178     {
179         super(p);
180         instance = inst;
181         virtualCall = true;
182     }
183 
184     override Value getThis()
185     {
186         if(auto t = cast(Class)instance.getType())
187             return new ClassValue(t, static_cast!ClassInstanceValue (instance));
188         return instance;
189     }
190 
191     override Value getValue(Node n)
192     {
193         if(auto pn = n in vars)
194             return *pn;
195         if(auto decl = cast(Declarator) n)
196             //if(Value v = instance._interpretProperty(this, decl.ident))
197             if(Value v = instance.getType().getProperty(instance, decl, virtualCall))
198                 return v;
199         if(parent)
200             return parent.getValue(n);
201         return null;
202     }
203 }
204 
205 class AssertContext : Context
206 {
207     Value[Node] identVal;
208 
209     this(Context p)
210     {
211         super(p);
212     }
213 }
214 
215 Context nullContext;
216 AggrContext noThisContext;
217 
218 Context globalContext;
219 Context threadContext;
220 Context errorContext;
221 
222 class Scope
223 {
224     Scope parent;
225 
226     Annotation annotations;
227     Attribute attributes;
228     Module mod;
229     Node node;
230     Set!Symbol[string] symbols;
231     Import[] imports;
232 
233 //    Context ctx; // compile time only
234 
235     static Scope current;
236 
237     this()
238     {
239         logInfo("Scope(%s) created, current=%s", cast(void*)this, cast(void*)current);
240     }
241     enum
242     {
243         SearchParentScope = 1,
244         SearchPrivateImport = 2,
245     }
246 
247     Scope pushClone()
248     {
249         Scope sc = new Scope;
250         sc.annotations = annotations;
251         sc.attributes = attributes;
252         sc.mod = mod;
253         sc.parent = this;
254         return current = sc;
255     }
256     Scope push(Scope sc)
257     {
258         if(!sc)
259             return pushClone();
260 
261         assert(this !is sc);
262         sc.parent = this;
263         return current = sc;
264     }
265 
266     Scope pop()
267     {
268         return current = parent;
269     }
270 
271     Type getThisType()
272     {
273         if(!parent)
274             return null;
275         return parent.getThisType();
276     }
277 
278     void addSymbol(string ident, Symbol s)
279     {
280         logInfo("Scope(%s).addSymbol(%s, sym %s=%s)", cast(void*)this, ident, s, cast(void*)s);
281 
282         if(auto sym = ident in symbols)
283             addunique(*sym, s);
284         else
285             symbols[ident] = Set!Symbol([s : true]);
286     }
287 
288     void addImport(Import imp)
289     {
290         imports ~= imp;
291     }
292 
293     struct SearchData { string ident; Scope sc; }
294     static Stack!SearchData searchStack;
295 
296     alias Set!Symbol SearchSet;
297 
298     void searchCollect(string ident, ref SearchSet syms)
299     {
300         string iden = ident[0..$-1];
301         foreach(id, sym; symbols)
302             if(id.startsWith(iden))
303                 addunique(syms, sym);
304     }
305 
306     void searchParents(string ident, bool inParents, bool privateImports, bool publicImports, ref SearchSet syms)
307     {
308         if(inParents && parent)
309         {
310             if(syms.length == 0)
311                 syms = parent.search(ident, true, privateImports, publicImports);
312             else if(collectSymbols(ident))
313                 addunique(syms, parent.search(ident, true, privateImports, publicImports));
314         }
315     }
316 
317     static bool collectSymbols(string ident)
318     {
319         return ident.endsWith("*");
320     }
321 
322     SearchSet search(string ident, bool inParents, bool privateImports, bool publicImports)
323     {
324         // check recursive search
325         SearchData sd = SearchData(ident, this);
326         for(int d = 0; d < searchStack.depth; d++)
327             if(searchStack.stack[d] == sd) // memcmp
328                 return Scope.SearchSet();
329 
330         SearchSet syms;
331         if(collectSymbols(ident))
332             searchCollect(ident, syms);
333         else if(auto pn = ident in symbols)
334             return *pn;
335 
336         searchStack.push(sd);
337         if(publicImports)
338             foreach(imp; imports)
339             {
340                 if(privateImports || (imp.getProtection() & Annotation_Public))
341                     addunique(syms, imp.search(this, ident));
342             }
343         searchParents(ident, inParents, privateImports, publicImports, syms);
344         searchStack.pop();
345         return syms;
346     }
347 
348     Scope.SearchSet matchFunctionArguments(Node id, Scope.SearchSet n)
349     {
350         Scope.SearchSet matches;
351         ArgumentList fnargs = id.getFunctionArguments();
352         int cntFunc = 0;
353         foreach(s, b; n)
354             if(s.getParameterList())
355                 cntFunc++;
356         if(cntFunc != n.length)
357             return matches;
358 
359         Node[] args;
360         if(fnargs)
361             args = fnargs.members;
362         foreach(s, b; n)
363         {
364             auto pl = s.getParameterList();
365             if(args.length > pl.members.length)
366                 continue;
367 
368             if(args.length < pl.members.length)
369                 // parameterlist must have default
370                 if(auto param = pl.getParameter(args.length))
371                     if(!param.getInitializer())
372                         continue;
373 
374             int a;
375             for(a = 0; a < args.length; a++)
376             {
377                 auto atype = args[a].calcType();
378                 auto ptype = pl.members[a].calcType();
379                 if(!ptype.convertableFrom(atype, Type.ConversionFlags.kImpliciteConversion))
380                     break;
381             }
382             if(a < args.length)
383                 continue;
384             matches[s] = true;
385         }
386         return matches;
387     }
388 
389     Node resolveOverload(string ident, Node id, Scope.SearchSet n)
390     {
391         if(n.length == 0)
392         {
393             id.semanticError("unknown identifier " ~ ident);
394             return null;
395         }
396         foreach(s, b; n)
397             s.semanticSearches++;
398 
399         if(n.length > 1)
400         {
401             auto matches = matchFunctionArguments(id, n);
402             if(matches.length == 1)
403                 return matches.first();
404             else if(matches.length > 0)
405                 n = matches; // report only matching funcs
406 
407             id.semanticError("ambiguous identifier " ~ ident);
408             foreach(s, b; n)
409                 s.semanticError("possible candidate");
410 
411             if(!collectSymbols(ident))
412                 return null;
413         }
414         return n.first();
415     }
416 
417     Node resolve(string ident, Node id, bool inParents = true)
418     {
419         auto n = search(ident, inParents, true, true);
420         logInfo("Scope(%s).search(%s) found %s %s", cast(void*)this, ident, n.keys(), n.length > 0 ? cast(void*)n.first() : null);
421 
422         return resolveOverload(ident, id, n);
423     }
424 
425     Node resolveWithTemplate(string ident, Scope sc, Node id, bool inParents = true)
426     {
427         auto n = search(ident, inParents, true, true);
428         logInfo("Scope(%s).search(%s) found %s %s", cast(void*)this, ident, n.keys(), n.length > 0 ? cast(void*)n.first() : null);
429 
430         auto resolved = resolveOverload(ident, id, n);
431         if(resolved && resolved.isTemplate())
432         {
433             TemplateArgumentList args;
434             if(auto tmplid = cast(TemplateInstance)id)
435             {
436                 args = tmplid.getTemplateArgumentList();
437             }
438             else
439             {
440                 args = new TemplateArgumentList; // no args
441             }
442             resolved = resolved.expandTemplate(sc, args);
443         }
444         return resolved;
445     }
446 
447     Project getProject() { return mod ? mod.getProject() : null; }
448 }
449 
450 struct ArgMatch
451 {
452     Value value;
453     string name;
454 
455     string toString() { return "{" ~ value.toStr() ~ "," ~ name ~ "}"; }
456 }
457 
458 class SourceModule
459 {
460     // filename already in Module
461     SysTime lastModified;
462     Options options;
463 
464     string txt;
465     Module parsed;
466     Module analyzed;
467 
468     Parser parser;
469     ParseError[] parseErrors;
470 
471     bool parsing() { return parser !is null; }
472 }
473 
474 version = no_syntaxcopy;
475 version = no_disconnect;
476 //version = free_ast;
477 
478 class Project : Node
479 {
480     Options options;
481     int countErrors;
482     bool saveErrors;
483 
484     Module mObjectModule; // object.d
485     SourceModule[string] mSourcesByModName;
486     SourceModule[string] mSourcesByFileName;
487 
488     this()
489     {
490         TextSpan initspan;
491         super(initspan);
492         options = new Options;
493 
494         version(none)
495         {
496         options.importDirs ~= r"c:\l\dmd-2.055\src\druntime\import\";
497         options.importDirs ~= r"c:\l\dmd-2.055\src\phobos\";
498 
499         options.importDirs ~= r"c:\tmp\d\runnable\";
500         options.importDirs ~= r"c:\tmp\d\runnable\imports\";
501 
502         options.importDirs ~= r"m:\s\d\rainers\dmd\test\";
503         options.importDirs ~= r"m:\s\d\rainers\dmd\test\imports\";
504         }
505 
506         globalContext = new Context(null);
507         threadContext = new Context(null);
508         errorContext = new Context(null);
509     }
510 
511     Module addSource(string fname, Module mod, ParseError[] errors, Node importFrom = null)
512     {
513         mod.filename = fname;
514         mod.imported = importFrom !is null;
515 
516         SourceModule src;
517         string modname = mod.getModuleName();
518         if(auto pm = modname in mSourcesByModName)
519         {
520             if(pm.parsed && pm.parsed.filename != fname)
521             {
522                 semanticErrorFile(fname, "module name " ~ modname ~ " already used by " ~ pm.parsed.filename);
523                 countErrors++;
524                 //return null;
525             }
526             src = *pm;
527         }
528         else if(auto pm = fname in mSourcesByFileName)
529             src = *pm;
530         else
531             src = new SourceModule;
532 
533         if(src.parsed)
534         {
535             version(no_disconnect) {} else
536                 src.parsed.disconnect();
537             version(free_ast)
538                 src.parsed.free();
539         }
540         src.parsed = mod;
541         src.parseErrors = errors;
542 
543         import std.file : exists, timeLastModified;
544 
545         if(exists(fname)) // could be pseudo name
546             src.lastModified = timeLastModified(fname);
547 
548         if(src.analyzed)
549         {
550             removeMember(src.analyzed);
551             version(disconnect) {} else
552                 src.analyzed.disconnect();
553             version(free_ast)
554                 src.analyzed.free();
555         }
556         version(no_syntaxcopy) {} else
557         {
558             src.analyzed = mod.clone();
559             addMember(src.analyzed);
560         }
561         src.options = options;
562         if(importFrom)
563             if(auto m = importFrom.getModule())
564                 src.options = m.getOptions();
565 
566         mSourcesByModName[modname] = src;
567         mSourcesByFileName[fname] = src;
568         version(no_syntaxcopy)
569             return src.parsed;
570         else
571             return src.analyzed;
572     }
573 
574     ////////////////////////////////////////////////////////////
575     Module addText(string fname, string txt, Node importFrom = null)
576     {
577         SourceModule src;
578         if(auto pm = fname in mSourcesByFileName)
579             src = *pm;
580         else
581             src = new SourceModule;
582 
583         logInfo("parsing " ~ fname);
584 
585         Parser p = new Parser;
586         p.saveErrors = saveErrors;
587         src.parser = p;
588         scope(exit) src.parser = null;
589 
590         p.filename = fname;
591         Node n;
592         try
593         {
594             semanticMessage(fname ~ ": parsing...");
595             n = p.parseModule(txt);
596         }
597         catch(Exception e)
598         {
599             if(fnSemanticWriteError)
600                 fnSemanticWriteError(MessageType.Error, e.msg);
601             countErrors += p.countErrors + 1;
602             return null;
603         }
604         countErrors += p.countErrors;
605         if(!n)
606             return null;
607 
608         auto mod = static_cast!(Module)(n);
609         return addSource(fname, mod, p.errors, importFrom);
610     }
611 
612     Module addAndParseFile(string fname, Node importFrom = null)
613     {
614         //debug writeln(fname, ":");
615         string txt = readUtf8(fname);
616         return addText(fname, txt, importFrom);
617     }
618 
619     bool addFile(string fname)
620     {
621         import std.file : exists, timeLastModified;
622         auto src = new SourceModule;
623         if(exists(fname)) // could be pseudo name
624             src.lastModified = timeLastModified(fname);
625 
626         src.txt = readUtf8(fname);
627         mSourcesByFileName[fname] = src;
628         return true;
629     }
630 
631     Module getModule(string modname)
632     {
633         if(auto pm = modname in mSourcesByModName)
634             return pm.analyzed;
635         return null;
636     }
637 
638     SourceModule getModuleByFilename(string filename)
639     {
640         if(auto pm = filename in mSourcesByFileName)
641             return *pm;
642         return null;
643     }
644 
645     Module importModule(string modname, Node importFrom)
646     {
647         if(auto mod = getModule(modname))
648             return mod;
649 
650         string dfile = replace(modname, ".", "/") ~ ".di";
651         string srcfile = searchImportFile(dfile, importFrom);
652         if(srcfile.length == 0)
653         {
654             dfile = replace(modname, ".", "/") ~ ".d";
655             srcfile = searchImportFile(dfile, importFrom);
656         }
657         if(srcfile.length == 0)
658         {
659             if (importFrom)
660                 importFrom.semanticError("cannot find imported module " ~ modname);
661             else
662                 .semanticError("cannot find imported module " ~ modname);
663             return null;
664         }
665         srcfile = normalizePath(srcfile);
666         return addAndParseFile(srcfile, importFrom);
667     }
668 
669     string searchImportFile(string dfile, Node importFrom)
670     {
671         import std.file : exists;
672         if(exists(dfile))
673             return dfile;
674 
675         Options opt = options;
676         if(importFrom)
677             if(auto mod = importFrom.getModule())
678                 opt = mod.getOptions();
679 
680         foreach(dir; opt.importDirs)
681             if(exists(dir ~ dfile))
682                 return dir ~ dfile;
683         return null;
684     }
685 
686     void initScope()
687     {
688         getObjectModule(null);
689         scop = new Scope;
690     }
691 
692     Module getObjectModule(Module importFrom)
693     {
694         if(!mObjectModule)
695             mObjectModule = importModule("object", importFrom);
696         return mObjectModule;
697     }
698 
699     // for error messages
700     override string getModuleFilename()
701     {
702         return "<global scope>";
703     }
704 
705     void semantic()
706     {
707         try
708         {
709             size_t cnt = members.length; // do not fully analyze imported modules
710             initScope();
711 
712             for(size_t m = 0; m < cnt; m++)
713                 members[m].semantic(scop);
714         }
715         catch(InterpretException)
716         {
717             semanticError("unhandled interpret exception, semantic analysis aborted");
718         }
719     }
720     ////////////////////////////////////////////////////////////
721     void update()
722     {
723     }
724 
725     void disconnectAll()
726     {
727         foreach(s; mSourcesByFileName)
728         {
729             if(s.parsed)
730                 s.parsed.disconnect();
731             if(s.analyzed)
732                 s.analyzed.disconnect();
733         }
734     }
735 
736     ////////////////////////////////////////////////////////////
737     void writeCpp(string fname)
738     {
739         string src;
740         CCodeWriter writer = new CCodeWriter(getStringSink(src));
741         writer.writeDeclarations    = true;
742         writer.writeImplementations = false;
743 
744         for(int m = 0; m < members.length; m++)
745         {
746             writer.writeReferencedOnly = getMember!Module(m).imported;
747             writer(members[m]);
748             writer.nl;
749         }
750 
751         writer.writeDeclarations    = false;
752         writer.writeImplementations = true;
753         for(int m = 0; m < members.length; m++)
754         {
755             writer.writeReferencedOnly = getMember!Module(m).imported;
756             writer(members[m]);
757             writer.nl;
758         }
759 
760         Node mainNode;
761         for(int m = 0; m < members.length; m++)
762             if(members[m].scop)
763             {
764                 if(auto pn = "main" in members[m].scop.symbols)
765                 {
766                     if(pn.length > 1 || mainNode)
767                         semanticError("multiple candidates for main function");
768                     else
769                         mainNode = (*pn).first();
770                 }
771             }
772         if(mainNode)
773         {
774             writer("int main(int argc, char**argv)");
775             writer.nl;
776             writer("{");
777             writer.nl;
778             {
779                 CodeIndenter indent = CodeIndenter(writer);
780                 Module mod = mainNode.getModule();
781                 mod.writeNamespace(writer);
782                 writer("main();");
783                 writer.nl;
784                 writer("return 0;");
785                 writer.nl;
786             }
787             writer("}");
788             writer.nl;
789         }
790 
791         import std.file : write;
792         write(fname, src);
793     }
794 
795     override void toD(CodeWriter writer)
796     {
797         throw new SemanticException("Project.toD not implemeted");
798     }
799 
800     int run()
801     {
802         Scope.SearchSet funcs;
803         foreach(m; mSourcesByModName)
804             addunique(funcs, m.analyzed.search("main"));
805         if(funcs.length == 0)
806         {
807             semanticError("no function main");
808             return -1;
809         }
810         if(funcs.length > 1)
811         {
812             semanticError("multiple functions main");
813             return -2;
814         }
815         TupleValue args = new TupleValue;
816         if(auto cn = cast(CallableNode)funcs.first())
817             if(auto pl = cn.getParameterList())
818                 if(pl.members.length > 0)
819                 {
820                     auto tda = new TypeDynamicArray;
821                     tda.setNextType(getTypeString!char());
822                     auto dav = new DynArrayValue(tda);
823                     args.addValue(dav);
824                 }
825 
826         try
827         {
828             Value v = funcs.first().interpret(nullContext).opCall(nullContext, args);
829             if(v is theVoidValue)
830                 return 0;
831             return v.toInt();
832         }
833         catch(InterpretException)
834         {
835             semanticError("cannot run main, interpretation aborted");
836             return -1;
837         }
838     }
839 }
840 
841 struct VersionInfo
842 {
843     TextPos defined;     // line -1 if not defined yet
844     TextPos firstUsage;  // line int.max if not used yet
845 }
846 
847 struct VersionDebug
848 {
849     int level;
850     VersionInfo[string] identifiers;
851 
852     bool reset(int lev, string[] ids)
853     {
854         if(lev == level && ids.length == identifiers.length)
855         {
856             bool different = false;
857             foreach(id; ids)
858                 if(id !in identifiers)
859                     different = true;
860             if(!different)
861                 return false;
862         }
863 
864         level = lev;
865         identifiers = identifiers.init;
866         foreach(id; ids)
867             identifiers[id] = VersionInfo();
868 
869         return true;
870     }
871 
872     bool preDefined(string ident) const
873     {
874         if(auto vi = ident in identifiers)
875             return vi.defined.line >= 0;
876         return false;
877     }
878 
879     bool defined(string ident, TextPos pos)
880     {
881         if(auto vi = ident in identifiers)
882         {
883             if(pos < vi.defined)
884                 semanticErrorPos(pos, "identifier " ~ ident ~ " used before defined");
885 
886             if(pos < vi.firstUsage)
887                 vi.firstUsage = pos;
888 
889             return vi.defined.line >= 0;
890         }
891         VersionInfo vi;
892         vi.defined.line = -1;
893         vi.firstUsage = pos;
894         identifiers[ident] = vi;
895         return false;
896     }
897 
898     void define(string ident, TextPos pos)
899     {
900         if(auto vi = ident in identifiers)
901         {
902             if(pos > vi.firstUsage)
903                 semanticErrorPos(pos, "identifier " ~ ident ~ " defined after usage");
904             if(pos < vi.defined)
905                 vi.defined = pos;
906         }
907 
908         VersionInfo vi;
909         vi.firstUsage.line = int.max;
910         vi.defined = pos;
911         identifiers[ident] = vi;
912     }
913 }
914 
915 class Options
916 {
917     string[] importDirs;
918     string[] stringImportDirs;
919 
920     public /* debug & version handling */ {
921     bool unittestOn;
922     bool x64;
923     bool debugOn;
924     bool coverage;
925     bool doDoc;
926     bool noBoundsCheck;
927     bool gdcCompiler;
928     bool noDeprecated;
929     bool mixinAnalysis;
930     bool UFCSExpansions;
931     VersionDebug debugIds;
932     VersionDebug versionIds;
933 
934     int changeCount;
935 
936     bool setImportDirs(string[] dirs)
937     {
938         if(dirs == importDirs)
939             return false;
940 
941         importDirs = dirs.dup;
942         changeCount++;
943         return true;
944     }
945     bool setStringImportDirs(string[] dirs)
946     {
947         if(dirs == stringImportDirs)
948             return false;
949 
950         stringImportDirs = dirs.dup;
951         changeCount++;
952         return true;
953     }
954     bool setVersionIds(int level, string[] versionids)
955     {
956         if(!versionIds.reset(level, versionids))
957             return false;
958         changeCount++;
959         return true;
960     }
961     bool setDebugIds(int level, string[] debugids)
962     {
963         if(!debugIds.reset(level, debugids))
964             return false;
965         changeCount++;
966         return true;
967     }
968 
969     bool versionEnabled(string ident)
970     {
971         int pre = versionPredefined(ident);
972         if(pre == 0)
973             return versionIds.defined(ident, TextPos());
974 
975         return pre > 0;
976     }
977 
978     bool versionEnabled(int level)
979     {
980         return level <= versionIds.level;
981     }
982 
983     bool debugEnabled(string ident)
984     {
985         return debugIds.defined(ident, TextPos());
986     }
987 
988     bool debugEnabled(int level)
989     {
990         return level <= debugIds.level;
991     }
992 
993     int versionPredefined(string ident)
994     {
995         int* p = ident in sPredefinedVersions;
996         if(!p)
997             return 0;
998         if(*p)
999             return *p;
1000 
1001         switch(ident)
1002         {
1003             case "unittest":
1004                 return unittestOn ? 1 : -1;
1005             case "assert":
1006                 return unittestOn || debugOn ? 1 : -1;
1007             case "D_Coverage":
1008                 return coverage ? 1 : -1;
1009             case "D_Ddoc":
1010                 return doDoc ? 1 : -1;
1011             case "D_NoBoundsChecks":
1012                 return noBoundsCheck ? 1 : -1;
1013             case "CRuntime_DigitalMars":
1014             case "Win32":
1015             case "X86":
1016             case "D_InlineAsm_X86":
1017                 return x64 ? -1 : 1;
1018             case "CRuntime_Microsoft":
1019             case "Win64":
1020             case "X86_64":
1021             case "D_InlineAsm_X86_64":
1022             case "D_LP64":
1023                 return x64 ? 1 : -1;
1024             case "GNU":
1025                 return gdcCompiler ? 1 : -1;
1026             case "DigitalMars":
1027                 return gdcCompiler ? -1 : 1;
1028             default:
1029                 assert(false, "inconsistent predefined versions");
1030         }
1031     }
1032 
1033     }
1034 }