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 }