1 /// Author: Aziz Köksal
2 /// License: GPL3
3 /// $(Maturity high)
4 module dil.SourceText;
5 
6 import dil.i18n.Messages;
7 import dil.Converter,
8        dil.Diagnostics;
9 import util.Path;
10 import common;
11 
12 import std.file;
13 
14 /// Represents D source code.
15 ///
16 /// The source text may come from a file or from a memory buffer.
17 final class SourceText
18 {
19   /// The file path to the source text. Mainly used for error messages.
20   cstring filePath;
21   cstring data; /// The UTF-8, zero-terminated source text.
22   /// The data member must be terminated with this string.
23   /// Four zeros are used to make certain optimizations possible in the Lexer.
24   static immutable  sentinelString = "\0\0\0\0";
25   /// True when the text has no invalid UTF8 sequences.
26   //bool isValidUTF8; // TODO: could this be useful?
27 
28   /// Constructs a SourceText object.
29   /// Params:
30   ///   filePath = File path to the source file.
31   ///   loadFile = Whether to load the file in the constructor.
32   this(cstring filePath, bool loadFile = false)
33   {
34     this.filePath = filePath;
35     loadFile && load();
36   }
37 
38   /// Constructs a SourceText object.
39   /// Params:
40   ///   filePath = File path for error messages.
41   ///   data = Memory buffer (may be terminated with sentinelString.)
42   this(cstring filePath, cstring data)
43   {
44     this(filePath);
45     addSentinelString(data);
46     this.data = data;
47   }
48 
49   /// Returns a slice to the source text, excluding the sentinel string.
50   cstring text()
51   {
52     return data[0..$-4];
53   }
54 
55   /// Loads the source text from a file.
56   /// Returns: true for success, false on failure.
57   bool load(Diagnostics diag = null)
58   {
59     if (!diag)
60       diag = new Diagnostics();
61     assert(filePath.length);
62 
63     scope(failure)
64     {
65       auto loc = new Location(filePath, 0);
66       auto mid = Path(this.filePath).exists() ?
67         MID.CantReadFile : MID.InexistantFile;
68       diag ~= new LexerError(loc, diag.msg(mid));
69       data = sentinelString.dup;
70       return false;
71     }
72 
73     // Read the file.
74     auto rawdata = cast(ubyte[])filePath.read();
75     // Convert the data.
76     auto converter = Converter(filePath, diag);
77     cstring text = converter.data2UTF8(rawdata);
78     addSentinelString(text);
79     this.data = text;
80     return true;
81   }
82 
83   /// Appends the sentinel string to the text (if not already there.)
84   static void addSentinelString(ref cstring text)
85   {
86     if (text.length < 4 ||
87         // Same as: text[$-4..$] != sentinelString
88         *cast(uint*)(text.ptr+text.length-4) != 0)
89       text ~= sentinelString;
90   }
91 }