1 object ShaderLines = 2 val list = List<String>.new 3 4 def add (s : String) = 5 list.add s 6 7 type FileDefinitions = struct 8 path : String 9 definitions : List<String> 10 11 def compare (other : FileDefinitions) = 12 let path_result = path.compare other.path 13 if path_result <> Ordering/Equal 14 then path_result 15 else definitions.compare other.definitions 16 17 is Compare 18 19 let matches (line : String) (definitions : List<String>) = 20 let s = line.trim 21 assert s.starts_with "#if " || s.starts_with "#elif " 22 23 let index = if s.starts_with "#if" then 4 else 6 24 let blocks = s.drop index |> split " || " 25 26 let f (expr : String) = 27 let is_neg = expr[0] == '!' 28 let name = if is_neg then expr.drop 1 else expr 29 let contains = definitions.any { _ == name } 30 contains <> is_neg 31 32 for block in blocks do 33 if block.split " && " |> all { f _ } then 34 return true 35 36 false 37 38 let process (source_directory : String 39 text : String 40 definitions : mut List<String> 41 paths : mut List<String>) : List<String> = 42 let lines = text.lines 43 let result = List<String>.new 44 let mut add = true 45 let mut level = 0 46 let mut exclude_level = 0 47 let level_invoked = List<bool>.new 48 49 for line in lines do 50 if not add then 51 if line.contains "#if" then 52 level += 1 53 else if line.contains "#else if" then 54 if exclude_level == level && not level_invoked.last then 55 if matches line definitions then 56 add = true 57 let index = level_invoked.size - 1 58 level_invoked[index] = true 59 60 else if line.contains "#else" || line.contains "#endif" then 61 if exclude_level == level 62 && (not level_invoked.last || line.contains "#endif") 63 then 64 add = true 65 66 if line.contains "#endif" then 67 level -= 1 68 69 else if line.contains "#if" then 70 level += 1 71 if not matches line definitions then 72 add = false 73 exclude_level = level 74 level_invoked.add false 75 else 76 level_invoked.add true 77 78 else if line.contains "#else if" then 79 add = false 80 exclude_level = level 81 82 else if line.contains "#else" then 83 add = false 84 exclude_level = level 85 86 else if line.contains "#endif" then 87 level -= 1 88 89 else if line.contains "#include" then 90 let s = line.trim 91 if s.ends_with '"' then 92 let path = s.drop 10 |> drop_last 1 93 let full_path = "$source_directory/$path" 94 let include_text = fs/read_text full_path 95 let include_lines = process source_directory include_text definitions paths 96 result.add_all include_lines 97 paths.add full_path 98 else 99 val name = s.drop 9 100 assert name == "object" 101 result.add_all ShaderLines.list 102 else 103 result.add line 104 if line.starts_with "#define" && line.trim.count { _ == ' ' } == 1 then 105 let name = line.drop 8 106 definitions.add name 107 108 result 109 110 let get_out_path (directory : String 111 path : String 112 suffix : String) = 113 let file_name = if Path.is_absolute path 114 then Path.get_file_name path 115 else path 116 117 let dot_index = file_name.last_index_of '.' 118 if suffix.is_empty 119 then "$directory/$file_name" 120 else "$directory/${file_name.take dot_index}_$suffix${file_name.drop dot_index}" 121 122 let process_dict (forced_paths : List<String> 123 source_directory : String 124 definitions : Map<String, FileDefinitions> 125 dependencies : Map<String, List<String>> 126 out_dependencies : mut Map<String, List<String>>) = 127 for out_path, (path, list) in definitions do 128 let modified = if fs/exists out_path 129 then fs/modified_time out_path 130 else Time.new 131 132 let source_modified = fs/modified_time path 133 if modified >= source_modified 134 && dependencies.contains path 135 && dependencies[path].all { s -> modified >= fs/modified_time s 136 && not forced_paths.contains s } 137 then 138 continue 139 140 let text = fs/read_text path 141 let mut_list = list.to_mut_list 142 let paths = List<String>.new 143 let result = process source_directory text mut_list paths 144 let sb = StringBuilder.new 145 for item in result do 146 sb.append item 147 sb.append '\n' 148 149 fs/write_file out_path sb.as_string 150 out_dependencies.add path paths 151 152 def get_default_definitions (directory : String) (source_directory : String) = 153 let result = Map<String, FileDefinitions>.new 154 let files = fs/list_files source_directory 155 let shader_files = files.filter { x -> x.ends_with ".comp" 156 || x.ends_with ".vert" 157 || x.ends_with ".frag" } 158 for file in shader_files do 159 let in_path = "$source_directory/$file" 160 let out_path = get_out_path directory file "" 161 result.add out_path (FileDefinitions in_path List.new) 162 163 result 164 165 def add_definitions (definitions : mut Map<String, FileDefinitions> 166 directory : String 167 source_directory : String 168 items : Slice<(String, Slice<FileDefinitions>)>) = 169 for path, slice in items do 170 let full_path = if Path.is_absolute path 171 then path 172 else "$source_directory/$path" 173 174 for suffix, list in slice do 175 let out_path = get_out_path directory path suffix 176 definitions.add out_path (FileDefinitions full_path list) 177 178 let save_dependencies (directory : String 179 dependencies : Map<String, List<String>>) = 180 let sb = StringBuilder.new 181 let list = dependencies.to_mut_list 182 list.sort 183 184 for from_path, to_paths in list do 185 sb.append from_path 186 append '\n' 187 188 for path in to_paths do 189 sb.append path 190 append '\n' 191 192 sb.append '\n' 193 194 sb.pop |> ignore 195 let path = "$directory/dependencies.txt" 196 fs/write_file path sb.as_string 197 198 let save_definitions (directory : String 199 definitions : Map<String, FileDefinitions>) = 200 let sb = StringBuilder.new 201 let list = definitions.to_mut_list 202 list.sort 203 204 for data_path, (src_path, items) in list do 205 sb.append data_path 206 append ", " 207 append src_path 208 209 if items.size > 0 then 210 sb.append ',' 211 for item in items do 212 sb.append ' ' 213 append item 214 215 sb.append '\n' 216 217 let path = "$directory/definitions.txt" 218 fs/write_file path sb.as_string 219 220 let load_definitions (directory : String) = 221 let result = Map<String, FileDefinitions>.new 222 let path = "$directory/definitions.txt" 223 if not fs/exists path then 224 return result 225 226 let text = fs/read_text path 227 let lines = text.lines 228 for line in lines do 229 let parts = line.split ", " 230 let data_path = parts[0].trim 231 let src_path = parts[1].trim 232 let list = if parts.size > 2 233 then parts[2].split ' ' |> map { _.trim } 234 else List<String>.new 235 236 result.add data_path (FileDefinitions src_path list) 237 238 result 239 240 let load_dependencies (directory : String) = 241 let path = "$directory/dependencies.txt" 242 let result = Map<String, List<String>>.new 243 if not fs/exists path then 244 return result 245 246 let text = fs/read_text path 247 let lines = text.lines 248 let mut maybe_path : Option<String> = None 249 let mut paths = List<String>.new 250 251 for line in lines do 252 if line.trim.is_empty then 253 result.add maybe_path.unwrap paths 254 paths = List<String>.new 255 maybe_path = None 256 257 else if maybe_path.is_none then 258 maybe_path = Some line 259 else 260 paths.add line 261 262 if maybe_path ? Some p then 263 result.add p paths 264 265 result 266 267 let get_forced_paths (directory : String) (source_directory : String) = 268 let text = ShaderLines.list.join_to_string separator = "\n" 269 let path = "$directory/lines.txt" 270 let exists = fs/exists path 271 let loaded_text = if exists 272 then fs/read_text path 273 else "" 274 275 let paths = List<String>.new 276 if text <> loaded_text || not exists then 277 val s = "$source_directory/default.glsl" 278 paths.add s 279 fs/write_file path text 280 281 paths 282 283 module shader 284 285 def preprocess (directory : String) (source_directory : String) = 286 if not fs/exists directory then 287 fs/create_path directory 288 289 let loaded_definitions = load_definitions directory 290 let definitions = get_default_definitions directory source_directory 291 add_definitions definitions directory source_directory Slice@zero 292 let forced_paths = get_forced_paths directory source_directory 293 294 for path, item in definitions do 295 if loaded_definitions.contains path 296 && (loaded_definitions[path].definitions.size <> item.definitions.size 297 || loaded_definitions[path].definitions.any 298 { not item.definitions.contains _ }) 299 then 300 fs/remove_file path 301 302 let dependencies = load_dependencies directory 303 let out_dependencies = Map<String, List<String>>.new 304 305 process_dict forced_paths source_directory definitions dependencies out_dependencies 306 307 if out_dependencies.size <> 0 then 308 for path, _ in dependencies do 309 if not out_dependencies.contains path then 310 out_dependencies.add path dependencies[path] 311 312 save_dependencies directory out_dependencies 313 save_definitions directory definitions 314