1 /**
2  * Cache the contents from files read from disk into memory.
3  *
4  * Copyright:   Copyright (C) 1999-2020 by The D Language Foundation, All Rights Reserved
5  * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
6  * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
7  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/filecache.d, filecache.d)
8  * Documentation:  https://dlang.org/phobos/dmd_filecache.html
9  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/filecache.d
10  */
11 
12 module dmd.filecache;
13 
14 import dmd.root.stringtable;
15 import dmd.root.array;
16 import dmd.root.file;
17 import dmd.root.filename;
18 
19 import core.stdc.stdio;
20 
21 /**
22 A line-by-line representation of a $(REF File, dmd,root,file).
23 */
24 class FileAndLines
25 {
26     FileName* file;
27     FileBuffer* buffer;
28     const(char[])[] lines;
29 
30   nothrow:
31 
32     /**
33     File to read and split into its lines.
34     */
35     this(const(char)[] filename)
36     {
37         file = new FileName(filename);
38         readAndSplit();
39     }
40 
41     // Read a file and split the file buffer linewise
42     private void readAndSplit()
43     {
44         auto readResult = File.read(file.toChars());
45         // FIXME: check success
46         // take ownership of buffer
47         buffer = new FileBuffer(readResult.extractSlice());
48         ubyte* buf = buffer.data.ptr;
49         // slice into lines
50         while (*buf)
51         {
52             auto prevBuf = buf;
53             for (; *buf != '\n' && *buf != '\r'; buf++)
54             {
55                 if (!*buf)
56                     break;
57             }
58             // handle Windows line endings
59             if (*buf == '\r' && *(buf + 1) == '\n')
60                 buf++;
61             lines ~= cast(const(char)[]) prevBuf[0 .. buf - prevBuf];
62             buf++;
63         }
64     }
65 
66     void destroy()
67     {
68         if (file)
69         {
70             file.destroy();
71             file = null;
72             buffer.destroy();
73             buffer = null;
74             lines.destroy();
75             lines = null;
76         }
77     }
78 
79     ~this()
80     {
81         destroy();
82     }
83 }
84 
85 /**
86 A simple file cache that can be used to avoid reading the same file multiple times.
87 It stores its cached files as $(LREF FileAndLines)
88 */
89 struct FileCache
90 {
91     private StringTable!(FileAndLines) files;
92 
93   nothrow:
94 
95     /**
96     Add or get a file from the file cache.
97     If the file isn't part of the cache, it will be read from the filesystem.
98     If the file has been read before, the cached file object will be returned
99 
100     Params:
101         file = file to load in (or get from) the cache
102 
103     Returns: a $(LREF FileAndLines) object containing a line-by-line representation of the requested file
104     */
105     FileAndLines addOrGetFile(const(char)[] file)
106     {
107         if (auto payload = files.lookup(file))
108         {
109             if (payload !is null)
110                 return payload.value;
111         }
112 
113         auto lines = new FileAndLines(file);
114         files.insert(file, lines);
115         return lines;
116     }
117 
118     __gshared fileCache = FileCache();
119 
120     // Initializes the global FileCache singleton
121     static __gshared void _init()
122     {
123         fileCache.initialize();
124     }
125 
126     void initialize()
127     {
128         files._init();
129     }
130 
131     void deinitialize()
132     {
133         foreach (sv; files)
134             sv.destroy();
135         files.reset();
136     }
137 }