1 module armos.graphics.shader.source;
2 
3 /++
4 +/
5 class Source {
6     public{
7         ///
8         this(in string rawText, in string name, in string path = ""){
9             this.rawText = rawText;
10             this.name = name;
11             if(path == ""){
12                 this.path = name;
13             }else{
14                 this.path = path;
15             }
16         }
17 
18         static Source load(in string path){
19             import armos.utils.file;
20             import std.path;
21             import std.file;
22             string absolutePath;
23             if(isAbsolute(path)){
24                 absolutePath = path;
25             }else{
26                 absolutePath = buildPath(thisExePath.dirName, path);
27             }
28 
29             assert(absolutePath.exists, "File not found: " ~ absolutePath);
30             string content = readText(absolutePath);
31             Source source = new Source(content, path.baseName, path);
32             return source;
33         }
34 
35         ///
36         Source expand(in string[] paths = [], Source[] searchableList = []){
37             import armos.utils.file;
38             import std.path;
39             import std.file;
40             string absolutePath;
41             if(isAbsolute(path)){
42                 absolutePath = path;
43             }else{
44                 absolutePath = buildPath(thisExePath.dirName, path);
45             }
46 
47             string glslifyPath = "glslify";
48 
49             import std.process;
50             auto command = "echo " ~ "\"" ~ rawText ~"\"" ~ " | " ~ glslifyPath;
51             auto result = executeShell(command, null, Config.none, size_t.max, absolutePath.dirName);
52             assert(!result.status, command ~ "\n" ~ result.output);
53             _expanded = result.output;
54             return this;
55         }
56 
57         ///
58         string expanded()const{
59             return _expanded;
60         }
61 
62         ///
63         size_t numLines()const{
64             import std..string;
65             return _expanded.split("\n").length;
66         }
67     }//public
68 
69     string rawText;
70     string name;
71     string path;
72     Line[] lines;
73     string _expanded;
74 
75     /// WARNING: duplicatable
76     Source[] dependencies;
77 
78     /// WARNING: duplicatable
79     Source[] allDependencies(){
80         import std.algorithm;
81         import std.array;
82         return dependencies.map!(src => src.allDependencies).join.array ~ dependencies;
83     }
84 
85     private{
86         size_t includeSource(in size_t currentLineIndex, in string[] paths, Source[] searchableList){
87             const line = lines[currentLineIndex];
88             string includingTargetPath = elementNameFromMacroStatement(line.content);
89             size_t lineLength;
90             import std.path;
91             import std.file;
92             string absIncludingTargetPath;
93             if(includingTargetPath.isAbsolute){
94                 absIncludingTargetPath = includingTargetPath;
95             }else{
96                 // First, attempt to search the file from local dir.
97                 // Next, attempt to search from searchable paths.
98                 foreach (p; this.path.dirName ~ paths) {
99                     absIncludingTargetPath = buildPath(p, includingTargetPath);
100                     if(absIncludingTargetPath.exists){
101                         break;
102                     }
103                 }
104                 assert(absIncludingTargetPath, "File not found: " ~ absIncludingTargetPath);
105             }
106             assert(absIncludingTargetPath.exists, "File not found: " ~ absIncludingTargetPath);
107 
108             Source includingTargetSource = Source.load(absIncludingTargetPath);
109             includingTargetSource.expand(paths, searchableList);
110             dependencies ~= includingTargetSource;
111 
112             Line LineSpecificationBegin = {origin: includingTargetSource,
113                                            content: "#line 1 " ~ absIncludingTargetPath};
114             import std.conv;
115             Line LineSpecificationEnd   = {origin: this,
116                                            content: "#line " ~ (currentLineIndex+1).to!string ~ " " ~ path};
117 
118             this.lines = this.lines[0..currentLineIndex] ~ 
119                          LineSpecificationBegin ~ 
120                          includingTargetSource.lines ~ 
121                          LineSpecificationEnd ~ 
122                          this.lines[currentLineIndex+1..$];
123 
124             lineLength = includingTargetSource.lines.length + 2;
125             return lineLength;
126         }
127     }//private
128 }//class Source
129 
130 version(unittest){
131     mixin template SourceA() {
132         auto sourceA = new Source(q{#include "sourceB.frag"
133                                void main(){}
134                 }, "sourceA", "data/sourceA.frag");
135     }//template SourceA
136 
137     mixin template SourceB() {
138         auto sourceB = new Source(q{#include "sourceC.frag"
139                 vec4 funcB(){return vec4(1, 2, 3, 4);}
140                 }, "sourceB", "data/sourceB.frag");
141     }//template SourceB
142 
143     mixin template SourceC() {
144         auto sourceC = new Source(q{
145                 vec4 funcC(){return vec4(1, 2, 3, 4);}
146                 }, "sourceC", "data/sourceC.frag");
147     }//template SourceC
148 }
149 
150 unittest{
151     //TODO
152     // import std.path;
153     // import std.file;
154     // string dir = buildNormalizedPath(__FILE_FULL_PATH__.dirName,
155     //                                  "..", "..", "..", "..",
156     //                                  "test", "graphics", "shader", "precompiler", "include");
157     // assert(dir.exists);
158     //
159     // assert(Source.load(buildPath(dir, "source_a.frag")).expand.lines.length == 11);
160 }
161 
162 /++
163 +/
164 struct Line {
165     public{
166         Source origin;
167         string content;
168         auto expand(in Source[] searchable){
169         }
170     }//public
171 
172     private{
173     }//private
174 }//struct Line
175 
176 auto  elementNameFromMacroStatement(in string macroStatement){
177     import std..string;
178     immutable stripped = macroStatement.strip;
179     immutable first = stripped.indexOf('"');
180     immutable last = stripped.lastIndexOf('"');
181     return stripped[first+1..last];
182 }
183 
184 unittest{
185     assert(elementNameFromMacroStatement(`#include "hoge"`) == "hoge");
186 }
187 
188 bool isMacroStatement(in string line){
189     import std..string;
190     immutable stripped = line.strip;
191     return stripped.length > 0 && stripped[0] == '#';
192 }
193 
194 unittest{
195     assert(isMacroStatement(`#include "hoge"`));
196     assert(isMacroStatement(`    #include "hoge"`));
197     assert(!isMacroStatement(`void main{}`));
198 }
199 
200 string macroNameFrom(in string macroStatement){
201     import std..string;
202     immutable stripped = macroStatement.strip;
203     immutable last = stripped.lastIndexOf(' ');
204     return stripped[1..last];
205 }
206 
207 unittest{
208     assert(macroNameFrom(`#include "hoge"`) == "include");
209     assert(macroNameFrom(`    #include "hoge"`) == "include");
210 }