1 module vayne.source.source; 2 3 4 import std.file; 5 import std.format; 6 import std.path; 7 8 9 static struct SourceManagerOptions { 10 string[] search; 11 string extension = ".html"; 12 } 13 14 struct SourceManager { 15 @disable this(); 16 17 this(SourceManagerOptions options) { 18 options_ = options; 19 } 20 21 Source add(string name, string buffer, uint parent = uint.max, string fileName = null) { 22 if (auto pindex = name in index_) 23 return sources_[*pindex]; 24 25 auto id = cast(uint)sources_.length; 26 27 sourceNames_ ~= name; 28 fileNames_ ~= fileName.length ? fileName : name; 29 sources_ ~= Source(id, parent, buffer); 30 index_[name] = id; 31 32 return sources_[id]; 33 } 34 35 void set(uint id, string buffer) { 36 sources_[id].buffer = buffer; 37 } 38 39 void dependency(uint id, uint dependency, SourceLoc loc) { 40 if (id == dependency) 41 throw new Exception(format("source '%s' directly depends on itself - triggered at %s", sourceNames_[id], this.loc(loc))); 42 43 auto source = sources_[id]; 44 while (source.parent != uint.max) { 45 if (source.parent == dependency) 46 throw new Exception(format("cyclic dependency with source '%s' triggered at %s", sourceNames_[dependency], this.loc(loc))); 47 source = sources_[source.parent]; 48 } 49 50 foreach (dep; deps_) { 51 if (dep == dependency) 52 return; 53 } 54 55 deps_ ~= dependency; 56 } 57 58 auto get(uint id) { 59 return sources_[id]; 60 } 61 62 auto name(uint id) { 63 return sourceNames_[id]; 64 } 65 66 auto loc(SourceLoc loc) { 67 return format("%s(%d)", name(loc.id), loc.line); 68 } 69 70 auto open(string fileName, bool binary = false, uint parent = uint.max) { 71 if (auto pindex = fileName in index_) 72 return sources_[*pindex]; 73 74 auto name = resolvePath(fileName); 75 if (!name.length && !name.extension.length) 76 name = resolvePath(fileName ~ options_.extension); 77 78 if (!name.length) 79 throw new Exception(format("file not found '%s'", fileName)); 80 81 auto bytes = cast(string)read(name); 82 83 return add(name, binary ? bytes.stripUTFbyteOrderMarker : bytes, parent, name); 84 } 85 86 auto resolvePath(string fileName) { 87 static string checkSearch(string[] search, string fileName) { 88 foreach (path; search) { 89 auto name = buildNormalizedPath(path, fileName); 90 if (exists(name)) 91 return name; 92 } 93 return null; 94 } 95 96 if (exists(fileName)) 97 return fileName; 98 return checkSearch(options_.search, fileName); 99 } 100 101 auto sourceNames() const { 102 return sourceNames_; 103 } 104 105 auto fileNames() const { 106 return fileNames_; 107 } 108 109 auto dependencies() const { 110 return deps_; 111 } 112 113 private: 114 uint[string] index_; 115 string[] sourceNames_; 116 string[] fileNames_; 117 Source[] sources_; 118 uint[] deps_; 119 120 SourceManagerOptions options_; 121 } 122 123 124 struct Source { 125 uint id; 126 uint parent; 127 string buffer; 128 129 @property auto ptr() const { 130 return buffer.ptr; 131 } 132 133 @property auto ptr(T)(T* ptr) { 134 assert(ptr >= buffer.ptr && ptr <= end); 135 buffer = buffer[ptr - buffer.ptr..$]; 136 } 137 138 @property auto end() const { 139 return buffer.ptr + buffer.length; 140 } 141 } 142 143 144 struct SourceLoc { 145 uint id; 146 uint line; 147 uint column; 148 } 149 150 151 bool balancedQuotes(Range)(Range range) { 152 char open = '\0'; 153 char last = '\0'; 154 155 foreach (ch; range) { 156 if (last != '\\') { 157 switch (ch) { 158 case '"': 159 case '\'': 160 case '`': 161 if (open == ch) { 162 open = '\0'; 163 } else if (open == '\0') { 164 open = ch; 165 } 166 break; 167 default: 168 break; 169 } 170 } 171 last = ch; 172 } 173 return open == '\0'; 174 } 175 176 177 size_t countLines(string x) { 178 size_t count; 179 180 foreach(i; 0..x.length) 181 count += (x.ptr[i] == '\n'); 182 return count; 183 } 184 185 186 string stripUTFbyteOrderMarker(string x) { 187 if (x.length >= 3 && (x[0] == 0xef) && (x[1] == 0xbb) && (x[2] == 0xbf)) 188 return x[3..$]; 189 return x; 190 }