1 /// Author: Aziz Köksal
2 /// License: GPL3
3 /// $(Maturity high)
4 module dil.semantic.Module;
5 
6 import dil.ast.Node,
7        dil.ast.Declarations;
8 import dil.parser.Parser;
9 import dil.lexer.Lexer,
10        dil.lexer.IdTable;
11 import dil.semantic.Symbol,
12        dil.semantic.Symbols;
13 import dil.i18n.Messages;
14 import dil.Compilation,
15        dil.Location,
16        dil.Diagnostics,
17        dil.SourceText,
18        dil.String;
19 import util.Path;
20 import common;
21 
22 import std.file;
23 
24 /// Represents a semantic D module and a source file.
25 class Module : ModuleSymbol
26 {
27   SourceText sourceText; /// The source file of this module.
28   cstring moduleFQN; /// Fully qualified name of the module. E.g.: dil.ast.Node
29   cstring packageName; /// E.g.: dil.ast
30   cstring moduleName; /// E.g.: Node
31   size_t ID; /// A unique 1-based ID. Useful for graph traversing.
32 
33   CompoundDecl root; /// The root of the parse tree.
34   ImportDecl[] imports; /// ImportDeclarations found in this file.
35   ModuleDecl moduleDecl; /// The optional ModuleDecl in this file.
36   Parser parser; /// The parser used to parse this file.
37 
38   /// Indicates which passes have been run on this module.
39   ///
40   /// 0 = No pass.$(BR)
41   /// 1 = Semantic pass 1.$(BR)
42   /// 2 = Semantic pass 2.
43   uint semanticPass;
44   Module[] modules; /// The imported modules.
45 
46   bool failedLoading; /// True if loading the source file failed.
47 
48   CompilationContext cc; /// The compilation context.
49 
50   /// Set when the Lexer should load the tokens from a file.
51   string dlxFilePath;
52 
53   this()
54   {
55     super();
56   }
57 
58   /// Constructs a Module object.
59   /// Params:
60   ///   filePath = File path to the source text; loaded in the constructor.
61   this(cstring filePath, CompilationContext cc)
62   {
63     this();
64     this.cc = cc;
65     this.sourceText = new SourceText(filePath);
66     this.failedLoading = !this.sourceText.load(cc.diag);
67   }
68 
69   /// Constructs from a ready-to-use SourceText instance.
70   this(SourceText src, CompilationContext cc)
71   {
72     this();
73     this.cc = cc;
74     this.sourceText = src;
75   }
76 
77   /// Returns the file path of the source text.
78   cstring filePath()
79   {
80     return sourceText.filePath;
81   }
82 
83   /// Returns filePath and escapes '/' or '\' with '_'.
84   cstring filePathEsc()
85   {
86     return filePath().replace(dirSep, '_');
87   }
88 
89   /// Returns the file extension: "d" or "di".
90   cstring fileExtension()
91   {
92     auto i = String(filePath).findr('.');
93     return i != -1 ? filePath[i..$] : "";
94   }
95 
96   /// Sets the parser to be used for parsing the source text.
97   void setParser(Parser parser)
98   {
99     this.parser = parser;
100   }
101 
102   /// Parses the module.
103   void parse()
104   {
105     if (this.parser is null)
106       this.parser = new Parser(sourceText, cc.tables.lxtables, cc.diag);
107 
108     if (this.dlxFilePath.length)
109       this.parser.lexer.fromDLXFile(cast(ubyte[])dlxFilePath.read());
110 
111     this.root = parser.start();
112     this.imports = parser.imports;
113 
114     // Set the fully qualified name of this module.
115     if (this.root.children.length)
116     { // moduleDecl will be null if first node isn't a ModuleDecl.
117       this.moduleDecl = this.root.children[0].Is!(ModuleDecl);
118       if (this.moduleDecl)
119         this.setFQN(moduleDecl.getFQN()); // E.g.: dil.ast.Node
120     }
121 
122     auto idtable = cc.tables.idents;
123 
124     if (!this.moduleFQN.length)
125     { // Take the base name of the file as the module name.
126       auto str = Path(filePath).name(); // E.g.: Node
127       if (!idtable.isValidUnreservedIdentifier(str))
128       {
129         auto location = this.firstToken().getErrorLocation(filePath());
130         auto msg = cc.diag.formatMsg(MID.InvalidModuleName, str);
131         cc.diag ~= new LexerError(location, msg);
132         str = idtable.genModuleID().str;
133       }
134       this.moduleFQN = this.moduleName = str;
135     }
136     assert(this.moduleFQN.length);
137 
138     // Set the symbol name.
139     this.name = idtable.lookup(this.moduleName);
140     // Set the symbol node.
141     this.loc.n = this.root;
142     this.loc.t = this.moduleDecl ? this.moduleDecl.begin : this.firstToken();
143   }
144 
145   /// Returns the first token of the module's source text.
146   Token* firstToken()
147   {
148     return parser.lexer.firstToken();
149   }
150 
151   /// Returns true if there are errors in the source file.
152   bool hasErrors()
153   {
154     assert(parser && parser.lexer);
155     return parser.errors.length || parser.lexer.errors.length || failedLoading;
156   }
157 
158   /// Returns a list of import paths.
159   /// E.g.: ["dil/ast/Node", "dil/semantic/Module"]
160   cstring[] getImportPaths()
161   {
162     cstring[] result;
163     foreach (import_; imports)
164       result ~= import_.getModuleFQNs(dirSep);
165     return result;
166   }
167 
168   /// Returns the fully qualified name of this module.
169   /// E.g.: dil.ast.Node
170   override cstring getFQN()
171   {
172     return moduleFQN;
173   }
174 
175   /// Sets the module's FQN.
176   void setFQN(cstring moduleFQN)
177   {
178     this.moduleFQN = moduleFQN;
179     auto i = String(moduleFQN).findr('.');
180     if (i != -1)
181       this.packageName = moduleFQN[0..i];
182     this.moduleName = moduleFQN[i+1..$];
183   }
184 
185   /// Returns the module's FQN with slashes instead of dots.
186   /// E.g.: dil.ast.Node -> dil/ast/Node
187   cstring getFQNPath()
188   {
189     return moduleFQN.replace('.', dirSep);
190   }
191 }