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 }