1structure ReadHMF :> ReadHMF = 2struct 3 4open Holmake_types 5 6datatype cond_position = GrabbingText | NoTrueCondYet | SkippingElses 7val empty_condstate = [] : cond_position list 8 9infix |> 10fun x |> f = f x 11 12fun readline lnum strm = let 13 fun recurse (lnum, acc) latest = 14 case latest of 15 NONE => if null acc then NONE 16 else SOME (lnum + 1, String.concat (List.rev acc)) 17 | SOME "\n" => SOME (lnum + 1, String.concat (List.rev acc)) 18 | SOME s => let 19 val s0 = if String.sub(s, size s - 2) = #"\r" then 20 String.extract(s, 0, SOME (size s - 2)) 21 else String.extract(s, 0, SOME (size s - 1)) 22 in 23 if String.sub(s0, size s0 - 1) = #"\\" then 24 recurse (lnum + 1, 25 " " :: String.extract(s0, 0, SOME (size s0 - 1)) :: acc) 26 (TextIO.inputLine strm) 27 else 28 SOME (lnum + 1, String.concat (List.rev (s0 :: acc))) 29 end 30in 31 recurse (lnum, []) (TextIO.inputLine strm) 32end 33 34datatype buf = B of { lnum : int, 35 strm : TextIO.instream, 36 name : string, 37 curr : (int * string) option } 38 39fun init_buf fname = let 40 val fname = OS.Path.mkAbsolute {path=fname, relativeTo=OS.FileSys.getDir()} 41 val istrm = TextIO.openIn fname 42in 43 B { lnum = 1, strm = istrm, curr = readline 1 istrm, name = fname } 44end 45 46fun close_buf (B r) = TextIO.closeIn (#strm r) 47 48fun currentline (B r) = Option.map #2 (#curr r) 49 50fun advance (b as B r) = 51 case #curr r of 52 NONE => b 53 | SOME (n,s) => B { lnum = n, strm = #strm r, name = #name r, 54 curr = readline n (#strm r) } 55 56fun error (B r) s = 57 raise Fail (#name r ^":"^Int.toString (#lnum r)^": "^s) 58 59fun strip_leading_wspace s = let 60 open Substring 61 val ss = full s 62in 63 string (dropl (fn c => c = #" " orelse c = #"\r") ss) 64end 65 66fun drop_twspace s = let 67 open Substring 68 val ss = full s 69in 70 string (dropr Char.isSpace ss) 71end 72 73 74fun first_special s = let 75 fun recurse i = if i = size s then NONE 76 else if String.sub(s,i) = #"=" then SOME #"=" 77 else if String.sub(s,i) = #":" then SOME #":" 78 else recurse (i + 1) 79in 80 recurse 0 81end 82 83fun strip_trailing_comment s = let 84 fun recurse i = 85 if i >= size s then s 86 else if String.sub(s,i) = #"\\" then recurse (i + 2) 87 else if String.sub(s,i) = #"#" then String.substring(s,0,i) 88 else recurse (i + 1) 89in 90 recurse 0 91end 92 93val ss = Substring.full 94 95fun read_delimited_string b dchar s = let 96 (* assume s begins with dchar *) 97 val s' = String.extract(s,1,NONE) 98 open Substring 99 val (result, rest) = position (str dchar) (ss s') 100 val _ = size rest <> 0 orelse error b ("No matching "^str dchar) 101in 102 (string result, string rest 103 |> (fn s => String.extract(s, 1, NONE)) 104 |> strip_leading_wspace) 105end 106 107fun read_quoted_string b s = let 108 val c = String.sub(s, 0) 109in 110 case c of 111 #"'" => read_delimited_string b c s 112 | #"\"" => read_delimited_string b c s 113 | _ => error b ("Bad argument delimiter: "^str c) 114end 115 116fun split_at_rightmost_rparen ss = let 117 open Substring 118 fun recurse i = 119 if i < 0 then (ss, full "") 120 else if sub(ss,i) = #")" then (slice(ss,0,SOME i), slice(ss,i,NONE)) 121 else recurse (i - 1) 122in 123 recurse (size ss - 1) 124end 125 126fun evaluate_cond b env s = 127 if String.isPrefix "ifdef" s orelse String.isPrefix "ifndef" s then let 128 val (sense, sz, nm) = 129 if String.sub(s,2) = #"n" then (false, 6, "ifndef") 130 else (true, 5, "ifdef") 131 val s' = strip_leading_wspace (String.extract(s, sz, NONE)) 132 val q = extract_normal_quotation (Substring.full s') 133 val s2 = perform_substitution env q 134 in 135 case String.tokens Char.isSpace s2 of 136 [s] => (case lookup env s of 137 [LIT ""] => SOME (not sense) 138 | [] => SOME (not sense) 139 | _ => SOME sense) 140 | _ => error b ("ReadHMF: "^nm^" not followed by a variable name.") 141 end 142 else if String.isPrefix "ifeq" s orelse String.isPrefix "ifneq" s then let 143 val (sense, sz, nm) = 144 if String.sub(s,2) = #"n" then (false, 5, "ifneq") 145 else (true, 4, "ifeq") 146 val s = String.extract(s,sz,NONE) |> strip_leading_wspace |> drop_twspace 147 val (arg1, arg2) = 148 case String.sub(s,0) of 149 #"(" => let 150 open Substring 151 val (arg1s, blob2s) = position "," (full (String.extract(s,1,NONE))) 152 val _ = size blob2s <> 0 orelse 153 error b (nm ^ " with parens requires args separated by \ 154 \commas") 155 val (arg2s, parenblob) = 156 split_at_rightmost_rparen (slice(blob2s,1,NONE)) 157 val _ = size parenblob <> 0 orelse 158 error b ("No right-paren in "^nm^" line") 159 in 160 (arg1s |> string |> drop_twspace |> strip_leading_wspace, 161 arg2s |> string |> drop_twspace |> strip_leading_wspace) 162 end 163 | _ => let 164 val (arg1, s) = read_quoted_string b s 165 val (arg2, s) = read_quoted_string b s 166 val _ = size (drop_twspace s) = 0 orelse 167 error b ("Extraneous junk after complete "^nm^" directive") 168 in 169 (arg1, arg2) 170 end 171 val (q1, q2) = (extract_normal_quotation (ss arg1), 172 extract_normal_quotation (ss arg2)) 173 val (s1, s2) = (perform_substitution env q1, perform_substitution env q2) 174 in 175 SOME ((s1 = s2) = sense) 176 end 177 else NONE 178 179fun getline env (condstate, b) = 180 case (currentline b, condstate) of 181 (NONE, []) => (b, NONE, condstate) 182 | (NONE, _ :: _) => error b "ReadHMF: unterminated conditional" 183 | (SOME s, SkippingElses :: rest) => let 184 val s = strip_leading_wspace s 185 in 186 if String.isPrefix "endif" s then getline env (rest, advance b) 187 else if String.isPrefix "ifdef" s orelse String.isPrefix "ifndef" s orelse 188 String.isPrefix "ifeq" s orelse String.isPrefix "ifneq" s 189 then 190 getline env (SkippingElses::condstate, advance b) 191 else 192 getline env (condstate, advance b) 193 end 194 | (SOME s, NoTrueCondYet::rest) => let 195 val s = strip_leading_wspace s 196 in 197 if String.isPrefix "endif" s then getline env (rest, advance b) 198 else if String.isPrefix "if" s then 199 getline env (SkippingElses :: condstate, advance b) 200 else if String.isPrefix "else" s then let 201 val s = strip_leading_wspace (String.extract(s, 4, NONE)) 202 in 203 if String.isPrefix "if" s then 204 case evaluate_cond b env s of 205 NONE => error b "ReadHMF: bogus string following else" 206 | SOME false => getline env (condstate, advance b) 207 | SOME true => getline env (GrabbingText::rest, advance b) 208 else if s = "" then getline env (GrabbingText::rest, advance b) 209 else error b "ReadHMF: bogus string following else" 210 end 211 else getline env (condstate, advance b) 212 end 213 | (SOME s0, _) => let 214 val s = strip_leading_wspace s0 215 in 216 if String.isPrefix "endif" s then 217 if null condstate then error b "ReadHMF: unpaired endif" 218 else getline env (tl condstate, advance b) 219 else if String.isPrefix "else" s then 220 if null condstate then error b "ReadHMF: unpaired else" 221 else getline env (SkippingElses::tl condstate, advance b) 222 else if String.isPrefix "if" s then 223 case evaluate_cond b env s of 224 NONE => (b, SOME s0, condstate) 225 | SOME false => getline env (NoTrueCondYet::condstate, advance b) 226 | SOME true => getline env (GrabbingText::condstate, advance b) 227 else (b, SOME s0, condstate) 228 end 229 230fun read_commands env (cs,b) head = 231 case getline env (cs, b) of 232 (b, NONE, cs) => ((cs, b), RULE head) 233 | (b, SOME s, cs) => let 234 val s' = strip_leading_wspace s 235 in 236 if s' = "" orelse String.sub(s',0) = #"#" then 237 read_commands env (cs, advance b) head 238 else 239 case String.sub(s',0) of 240 #"\t" => read_commands env (cs, advance b) (head ^ s' ^ "\n") 241 | c => ((cs, b), RULE head) 242 end 243 244 245fun process_line env (condstate, b) = let 246 val (b, line_opt, condstate) = getline env (condstate, b) 247in 248 case line_opt of 249 NONE => ((condstate, b), EOF) 250 | SOME s => let 251 val s' = strip_leading_wspace s 252 in 253 if s' = "" orelse String.sub(s',0) = #"#" then 254 process_line env (condstate, advance b) 255 else let 256 val c1 = String.sub(s',0) 257 in 258 if c1 = #"\t" then error b "TAB starts an unattached command" 259 else 260 case first_special s' of 261 NONE => error b ("Unrecognised character: \""^ 262 String.toString (str c1) ^ "\"") 263 | SOME #"=" => ((condstate, advance b), DEFN (strip_trailing_comment s)) 264 | SOME #":" => read_commands 265 env 266 (condstate, advance b) 267 (strip_trailing_comment s' ^ "\n") 268 | SOME _ => error b "ReadHMF: can't happen" 269 end 270 end 271end 272 273fun readall (acc as (tgt1,env,ruledb,depdb)) csb = 274 case process_line env csb of 275 (csb as (cs, b), EOF) => let 276 val _ = close_buf b 277 fun foldthis (tgt,deps,acc) = 278 case Binarymap.peek(acc,tgt) of 279 NONE => Binarymap.insert(acc,tgt, 280 {dependencies = deps, commands = []}) 281 | SOME {dependencies, commands} => 282 Binarymap.insert(acc,tgt, {dependencies = dependencies @ deps, 283 commands = commands}) 284 in 285 (env,Binarymap.foldl foldthis ruledb depdb,tgt1) 286 end 287 | (csb, x) => let 288 fun warn s = TextIO.output(TextIO.stdErr, s ^ "\n") 289 in 290 case to_token x of 291 HM_defn def => readall (tgt1,env_extend def env, ruledb, depdb) csb 292 | HM_rule rinfo => let 293 val (rdb',depdb',tgts) = extend_ruledb warn env rinfo (ruledb,depdb) 294 val tgt1' = 295 case tgt1 of 296 NONE => List.find (fn s => s <> ".PHONY") tgts 297 | _ => tgt1 298 in 299 readall (tgt1',env,rdb',depdb') csb 300 end 301 end 302 303fun read fname env = 304 readall (NONE, env, empty_ruledb, 305 Binarymap.mkDict String.compare) 306 (empty_condstate, init_buf fname) 307 308fun readlist e vref = 309 map dequote (tokenize (perform_substitution e [VREF vref])) 310 311fun memoise f = 312 let 313 val stash = ref (Binarymap.mkDict String.compare) 314 fun lookup s = 315 case Binarymap.peek(!stash, s) of 316 NONE => 317 let 318 val actual = f s 319 in 320 stash := Binarymap.insert(!stash, s, actual); 321 actual 322 end 323 | SOME r => r 324 in 325 lookup 326 end 327 328fun find_includes0 dirname = 329 let 330 fun warn s = TextIO.output(TextIO.stdErr, s ^ "\n") 331 val hm_fname = OS.Path.concat(dirname, "Holmakefile") 332 in 333 if OS.FileSys.access(hm_fname, [OS.FileSys.A_READ]) then 334 let 335 val (e, _, _) = read hm_fname (base_environment()) 336 val raw_incs = readlist e "INCLUDES" @ readlist e "PRE_INCLUDES" 337 in 338 map (fn p => OS.Path.mkAbsolute {path = p, relativeTo = dirname}) 339 raw_incs 340 end handle e => (warn ("Bogus Holmakefile in " ^ dirname ^ 341 " - ignoring it"); 342 []) 343 344 else [] 345 end 346 347val find_includes = memoise find_includes0 348 349infix ++ 350val op ++ = OS.Path.concat 351fun canonicalise d1 d2 = OS.Path.mkAbsolute{path = d2, relativeTo = d1} 352fun fromList l = Binaryset.addList (Binaryset.empty String.compare, l) 353 354(* returns updated accumulator and list of new places to visit *) 355fun extend_path_with_includes0 (A as (visited,prem,postm)) dir verbosity = 356 if Binaryset.member(visited, dir) then (A,[]) 357 else 358 if OS.FileSys.access (dir ++ "Holmakefile", [OS.FileSys.A_READ]) then 359 let 360 open Holmake_types 361 val _ = if verbosity > 1 then 362 print ("Visiting " ^ dir ^ " for first time\n") 363 else () 364 val extensions = 365 holpathdb.search_for_extensions find_includes [dir] 366 val _ = List.app holpathdb.extend_db extensions 367 val base_env = let 368 fun foldthis ({vname,path}, env) = 369 env_extend (vname, [LIT path]) env 370 in 371 List.foldl foldthis (base_environment()) extensions 372 end 373 val (env, _, _) = read (dir ++ "Holmakefile") base_env 374 fun envlist id = 375 map dequote (tokenize (perform_substitution env [VREF id])) 376 fun diag nm incs = 377 if null incs orelse verbosity < 2 then () 378 else 379 print (nm ^ " = " ^ String.concatWith ", " incs ^ "\n") 380 val pre_incs = map (canonicalise dir) (envlist "PRE_INCLUDES") 381 val _ = diag "PRE_INCLUDES" pre_incs 382 val post_incs = map (canonicalise dir) (envlist "INCLUDES") 383 val _ = diag "INCLUDES" post_incs 384 fun maybeinsert(m,k,v) = 385 if null v then m else Binarymap.insert(m,k,v) 386 in 387 ((Binaryset.add(visited,dir), 388 maybeinsert(prem,dir,pre_incs), 389 maybeinsert(postm,dir,post_incs)), 390 Binaryset.listItems (fromList (pre_incs @ post_incs))) 391 end handle e => (if verbosity > 0 then 392 (TextIO.output(TextIO.stdErr, 393 "[bogus Holmakefile in " ^ dir ^ 394 " - ignoring it]\n"); 395 TextIO.flushOut TextIO.stdErr; 396 (A, []) 397 ) 398 else (A, [])) 399 else (A, []) 400 401fun extend_paths A cfg worklist = 402 case worklist of 403 [] => A 404 | d::ds => 405 let 406 val (A',new) = extend_path_with_includes0 A d cfg 407 in 408 extend_paths A' cfg (ds @ new) 409 end 410 411 412val empty_strset = Binaryset.empty String.compare 413val empty_strmap = Binarymap.mkDict String.compare 414fun extend_path_with_includes (cfg as {lpref,verbosity=v}) = 415 let 416 val wlist = [OS.FileSys.getDir()] 417 val (_, prem, postm) = 418 extend_paths (empty_strset, empty_strmap, empty_strmap) v wlist 419 fun m s = holpathdb.reverse_lookup {path = s} 420 fun foldthis nm (dirname,incs,acc) = ( 421 if v > 0 then 422 print (m dirname ^ "/Holmakefile:" ^ nm ^ " +=\n " ^ 423 String.concatWith "\n " (map m incs) ^ "\n") 424 else (); 425 Binaryset.addList(acc,incs) 426 ) 427 fun acc_range nm = Binarymap.foldl (foldthis nm) empty_strset 428 val all_preincs = Binaryset.listItems (acc_range "PRE_INCLUDES" prem) 429 val all_incs = Binaryset.listItems (acc_range "INCLUDES" postm) 430 in 431 lpref := all_preincs @ !lpref @ all_incs 432 end 433 434end (* struct *) 435