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 96 definitions paths 97 result.add_all include_lines 98 paths.add full_path 99 else 100 val name = s.drop 9 101 assert name == "object" 102 result.add_all ShaderLines.list 103 else 104 result.add line 105 if line.starts_with "#define" && line.trim.count { _ == ' ' } == 1 then 106 let name = line.drop 8 107 definitions.add name 108 109 result 110 111 let get_out_path (directory : String 112 path : String 113 suffix : String) = 114 let file_name = if Path.is_absolute path 115 then Path.get_file_name path 116 else path 117 118 let dot_index = file_name.last_index_of '.' 119 if suffix.is_empty 120 then "$directory/$file_name" 121 else "$directory/${file_name.take dot_index}_$suffix${file_name.drop dot_index}" 122 123 let process_dict (forced_paths : List<String> 124 source_directory : String 125 definitions : Map<String, FileDefinitions> 126 dependencies : Map<String, List<String>> 127 out_dependencies : mut Map<String, List<String>>) = 128 for out_path, (path, list) in definitions do 129 let modified = if fs/exists out_path 130 then fs/modified_time out_path 131 else Time.new 132 133 let source_modified = fs/modified_time path 134 if modified >= source_modified 135 && dependencies.contains path 136 && dependencies[path].all { s -> modified >= fs/modified_time s 137 && not forced_paths.contains s } 138 then 139 continue 140 141 let text = fs/read_text path 142 let mut_list = list.to_mut_list 143 let paths = List<String>.new 144 let result = process source_directory text mut_list paths 145 let sb = StringBuilder.new 146 for item in result do 147 sb.append item 148 sb.append '\n' 149 150 fs/write_file out_path sb.as_string 151 out_dependencies.add path paths 152 153 def get_default_definitions (directory : String) (source_directory : String) = 154 let result = Map<String, FileDefinitions>.new 155 let files = fs/list_files source_directory 156 let shader_files = files.filter { x -> x.ends_with ".comp" 157 || x.ends_with ".vert" 158 || x.ends_with ".frag" } 159 for file in shader_files do 160 let in_path = "$source_directory/$file" 161 let out_path = get_out_path directory file "" 162 result.add out_path (FileDefinitions in_path List.new) 163 164 result 165 166 def add_definitions (definitions : mut Map<String, FileDefinitions> 167 directory : String 168 source_directory : String 169 items : Slice<(String, Slice<FileDefinitions>)>) = 170 for path, slice in items do 171 let full_path = if Path.is_absolute path 172 then path 173 else "$source_directory/$path" 174 175 for suffix, list in slice do 176 let out_path = get_out_path directory path suffix 177 definitions.add out_path (FileDefinitions full_path list) 178 179 let save_dependencies (directory : String 180 dependencies : Map<String, List<String>>) = 181 let sb = StringBuilder.new 182 let list = dependencies.to_mut_list 183 list.sort 184 185 for from_path, to_paths in list do 186 sb.append from_path 187 append '\n' 188 189 for path in to_paths do 190 sb.append path 191 append '\n' 192 193 sb.append '\n' 194 195 sb.pop |> ignore 196 let path = "$directory/dependencies.txt" 197 fs/write_file path sb.as_string 198 199 let save_definitions (directory : String 200 definitions : Map<String, FileDefinitions>) = 201 let sb = StringBuilder.new 202 let list = definitions.to_mut_list 203 list.sort 204 205 for data_path, (src_path, items) in list do 206 sb.append data_path 207 append ", " 208 append src_path 209 210 if items.size > 0 then 211 sb.append ',' 212 for item in items do 213 sb.append ' ' 214 append item 215 216 sb.append '\n' 217 218 let path = "$directory/definitions.txt" 219 fs/write_file path sb.as_string 220 221 let load_definitions (directory : String) = 222 let result = Map<String, FileDefinitions>.new 223 let path = "$directory/definitions.txt" 224 if not fs/exists path then 225 return result 226 227 let text = fs/read_text path 228 let lines = text.lines 229 for line in lines do 230 let parts = line.split ", " 231 let data_path = parts[0].trim 232 let src_path = parts[1].trim 233 let list = if parts.size > 2 234 then parts[2].split ' ' |> map { _.trim } 235 else List<String>.new 236 237 result.add data_path (FileDefinitions src_path list) 238 239 result 240 241 let load_dependencies (directory : String) = 242 let path = "$directory/dependencies.txt" 243 let result = Map<String, List<String>>.new 244 if not fs/exists path then 245 return result 246 247 let text = fs/read_text path 248 let lines = text.lines 249 let mut maybe_path : Option<String> = None 250 let mut paths = List<String>.new 251 252 for line in lines do 253 if line.trim.is_empty then 254 result.add maybe_path.unwrap paths 255 paths = List<String>.new 256 maybe_path = None 257 258 else if maybe_path.is_none then 259 maybe_path = Some line 260 else 261 paths.add line 262 263 if maybe_path ? Some p then 264 result.add p paths 265 266 result 267 268 let get_forced_paths (directory : String) (source_directory : String) = 269 let text = ShaderLines.list.join_to_string separator = "\n" 270 let path = "$directory/lines.txt" 271 let exists = fs/exists path 272 let loaded_text = if exists 273 then fs/read_text path 274 else "" 275 276 let paths = List<String>.new 277 if text <> loaded_text || not exists then 278 val s = "$source_directory/default.glsl" 279 paths.add s 280 fs/write_file path text 281 282 paths 283 284 module shader 285 286 def preprocess (directory : String) (source_directory : String) = 287 if not fs/exists directory then 288 fs/create_path directory 289 290 let loaded_definitions = load_definitions directory 291 let definitions = get_default_definitions directory source_directory 292 add_definitions definitions directory source_directory Slice@zero 293 let forced_paths = get_forced_paths directory source_directory 294 295 for path, item in definitions do 296 if loaded_definitions.contains path 297 && (loaded_definitions[path].definitions.size <> item.definitions.size 298 || loaded_definitions[path].definitions.any 299 { not item.definitions.contains _ }) 300 then 301 fs/remove_file path 302 303 let dependencies = load_dependencies directory 304 let out_dependencies = Map<String, List<String>>.new 305 306 process_dict forced_paths source_directory definitions 307 dependencies out_dependencies 308 309 if out_dependencies.size <> 0 then 310 for path, _ in dependencies do 311 if not out_dependencies.contains path then 312 out_dependencies.add path dependencies[path] 313 314 save_dependencies directory out_dependencies 315 save_definitions directory definitions 316