1structure Holmake_types :> Holmake_types =
2struct
3
4open internal_functions
5
6datatype pretoken = DEFN of string | RULE of string | EOF
7
8datatype frag = LIT of string | VREF of string
9type quotation = frag list
10type env = (string, quotation)Binarymap.dict
11type rule_info = {dependencies : string list, commands : string list}
12type raw_rule_info = { targets : quotation, dependencies : quotation,
13                       commands : quotation list }
14type ruledb =
15     (string, {dependencies: string list, commands: quotation list}) Binarymap.dict
16datatype token = HM_defn of string * quotation
17               | HM_rule of raw_rule_info
18
19fun normquote acc [] = List.rev acc
20  | normquote acc [x] = List.rev (x::acc)
21  | normquote acc (LIT s1 :: LIT s2 :: t) = normquote acc (LIT (s1 ^ s2) :: t)
22  | normquote acc (h :: t) = normquote (h::acc) t
23
24(* for strings that are not commands *)
25fun is_special c = c = #"#" orelse c = #"$" orelse c = #"\\"
26
27fun ok_symbolvars c = c = #"<" orelse c = #"@"
28
29fun check_for_vref (startc, endc) acc ss k = let
30  open Substring
31  (* scan forward for balancing endc *)
32  fun recurse (count, depth, ss) =
33      case getc ss of
34        NONE => raise Fail ("Unclosed variable reference, beginning: $"^
35                            str startc ^
36                            string (slice(ss, 0, SOME(Int.min(size ss, 10)))))
37      | SOME(c, ss') => if c = endc then
38                          if depth = 0 then (count, slice(ss, 1, NONE))
39                          else recurse (count + 1, depth - 1, ss')
40                        else if c = startc then
41                          recurse (count + 1, depth + 1, ss')
42                        else recurse (count + 1, depth, ss')
43  val (varlength, rest) = recurse(0, 0, ss)
44in
45  k (VREF (string (slice(ss, 0, SOME varlength))) :: acc) rest
46end
47
48fun quotable c =
49    case c of
50      #" " => true
51    | #"\\" => true
52    | #":" => true
53    | #"#" => true
54    | _ => false
55
56fun extract_quotation0 cmd acc ss = let
57  open Substring
58  val (normal, rest) = splitl (not o is_special) ss
59  val acc = if size normal > 0 then LIT (string normal) :: acc
60            else acc
61  val extract = extract_quotation0 cmd
62in
63  case Substring.getc rest of
64    NONE => List.rev acc
65  | SOME(c, rest) => let
66      fun chew_newline acc rest = let
67        val rest = dropl Char.isSpace rest
68      in
69        if size rest = 0 then List.rev acc
70        else extract (LIT " " :: acc) rest
71      end
72    in
73      case c of
74        #"#" => if not cmd then
75                  (* rest of line is comment; drop it *) List.rev acc
76                else extract (LIT "#" :: acc) rest
77      | #"\\" =>
78        if cmd then
79          if size rest > 0 andalso sub(rest,0) = #"\n" andalso
80             not Systeml.isUnix
81          then
82            chew_newline acc rest
83          else extract (LIT "\\" :: acc) rest
84        else let
85          (* need to look at what comes next *)
86          in
87            case Substring.getc rest of
88              NONE => List.rev (LIT "\\" :: acc)
89            | SOME (c, rest') => let
90              in
91                case c of
92                  #"\n" => chew_newline acc rest
93                | #"#" => extract (LIT "#" :: acc) rest'
94                | _ => extract (LIT ("\\" ^ str c) :: acc) rest'
95              end
96          end
97      | #"$" => (* check for well-formed variable *) let
98        in
99          case Substring.getc rest of
100            NONE => (* gnu make silently drops it ; weird *) List.rev acc
101          | SOME (c, rest) => let
102            in
103              case c of
104                #"$" => (* becomes a dollar-sign *)
105                extract (LIT "$" :: acc) rest
106              | #"(" => check_for_vref (c, #")") acc rest extract
107              | #"{" => check_for_vref (c, #"}") acc rest extract
108              | _ =>
109                if Char.isAlphaNum c orelse c = #"_" orelse
110                   ok_symbolvars c
111                then
112                  extract (VREF (str c) :: acc) rest
113                else
114                  raise Fail ("Bad variable name: "^str c)
115            end
116        end
117      | _ => raise Fail "Can't happen"
118    end
119end
120
121
122val extract_normal_quotation = normquote [] o extract_quotation0 false []
123val extract_cmd_quotation = normquote [] o extract_quotation0 true []
124
125
126fun mem e [] = false
127  | mem e (h::t) = e = h orelse mem e t
128
129fun strip_trailing_ws ss = let
130  (* can't just use dropr Char.isSpace, because the first space
131     might be protected with a backslash *)
132  open Substring
133  val (ok, spaces) = splitr Char.isSpace ss
134  val (ok0, bslashes) = splitr (fn c => c = #"\\") ok
135in
136  if size bslashes mod 2 = 0 then ok
137  else if size spaces > 0 then
138    slice(ss, 0, SOME (size ok + 1))
139  else ok
140end
141
142fun convert_newlines ss0 = let
143  (* replace \r\n with \n to normalise against windows convention *)
144  open Substring
145  fun recurse acc ss0 = let
146    val (ss1, ss2) = position "\r\n" ss0
147  in
148    if size ss2 = 0 then concat (List.rev (ss1::acc))
149    else recurse (ss1::acc) (Substring.slice(ss2, 1, NONE))
150  end
151in
152  Substring.full (recurse [] ss0)
153end
154
155fun to_token pt =
156    case pt of
157      DEFN s => let
158        open Substring
159        val ss = convert_newlines (full s)
160        fun endp c = c <> #"=" andalso not (Char.isSpace c)
161        val (varname, rest) = splitl endp ss
162        val rest = dropl Char.isSpace rest
163        val rest = #2 (valOf (getc rest)) (* drops = sign *)
164        val rest = dropl Char.isSpace rest
165      in
166        HM_defn(string varname, extract_normal_quotation rest)
167      end
168    | RULE s => let
169        open Substring
170        val ss = convert_newlines (full s)
171        val idx = valOf (find_unescaped [#":"] ss)
172        val (tgts, rest) = splitAt(ss, idx)
173        val tgts = strip_trailing_ws tgts
174
175        val rest = #2 (valOf (getc rest)) (* drop the colon *)
176        val (deps, rest) =
177            splitAt(rest, valOf (find_unescaped [#"\n"] rest))
178            handle Option => (* happens if the dependencies are terminated
179                                by an eof character *)
180                   splitAt(rest, size rest - 1)
181        val rest = #2 (valOf (getc rest)) (* drop the newline/eof *)
182        val deps =  (* cut any comment on this line *)
183            case find_unescaped [#"#"] deps of
184              NONE => deps
185            | SOME i => #1 (splitAt(deps, i))
186        val deps = dropl Char.isSpace (strip_trailing_ws deps)
187
188        fun docmds acc ss =
189            if size ss = 0 then List.rev acc
190            else
191              case find_unescaped [#"\n"] ss of
192                NONE => (* cut out initial TAB, and final EOF character *)
193                List.rev (extract_cmd_quotation
194                            (slice(ss,1,SOME(size ss - 2))) :: acc)
195              | SOME i => let
196                  val (cmd, rest) = splitAt(ss, i)
197                  val rest = slice(rest, 1, NONE) (* drop newline *)
198                  val cmd = slice(cmd, 1, NONE)  (* drop TAB *)
199                in
200                  docmds (extract_cmd_quotation cmd :: acc) rest
201                end
202      in
203        HM_rule {commands = docmds [] rest,
204                 dependencies = extract_normal_quotation deps,
205                 targets = extract_normal_quotation tgts}
206      end
207    | EOF => raise Fail "No EOF-equivalent"
208
209val commafy = String.concatWith ", "
210
211fun argtokenize ss = let
212  open Substring
213  val sz = size ss
214  fun recurse pdepth start i acc =
215      if i = sz then
216        if pdepth = 0 then List.rev (slice(ss,start,NONE) :: acc)
217        else raise Fail "argtokenize: too few right parens"
218      else let
219          val c = sub(ss,i)
220        in
221          if c = #"(" then recurse (pdepth + 1) start (i + 1) acc
222          else if c = #")" then
223            if pdepth = 0 then raise Fail "argtokenize: too many right parens"
224            else recurse (pdepth - 1) start (i + 1) acc
225          else if c = #"," then
226            if pdepth = 0 then recurse pdepth (i + 1) (i + 1)
227                                       (slice(ss,start,SOME (i-start)) :: acc)
228            else recurse pdepth start (i + 1) acc
229          else
230            recurse pdepth start (i + 1) acc
231        end
232in
233  recurse 0 0 0 []
234end
235
236fun perform_substitution env q = let
237  open Substring
238  fun envfn s =
239      case Binarymap.peek(env, s) of
240        NONE => (case OS.Process.getEnv s of
241                   NONE => [LIT ""]
242                 | SOME s => [LIT s])
243      | SOME q => q
244  fun finisher q =
245      case normquote [] q of
246        [LIT s] => s
247      | [] => ""
248      | _ => raise Fail "Can't happen"
249  fun recurse visited fraglist =
250      case fraglist of
251        [] => []
252      | (LIT s :: rest) => LIT s :: recurse visited rest
253      | VREF s :: rest => let
254          val ss = full s
255          val (fnpart, spc_rest) = position " " ss
256          val eval = finisher o recurse visited o extract_normal_quotation
257          val result =
258              if size spc_rest > 0 then let
259                  (* have a function call to evaluate *)
260                  val fnname = eval fnpart
261                  val args = argtokenize
262                                 (dropl Char.isSpace
263                                        (dropr Char.isSpace spc_rest))
264                in
265                  [LIT (function_call (fnname, args, eval))]
266                end
267              else let
268                  val varname = eval ss
269                  val _ = not (mem varname visited) orelse
270                          raise Fail ("Variable loop through: "^
271                                      commafy visited)
272                  val s_expanded0 = envfn varname
273                in
274                  recurse (s :: visited) s_expanded0
275                end
276        in
277          result @ recurse visited rest
278        end
279in
280  finisher (recurse [] q)
281end
282
283fun dequote s = let
284  open Substring
285  val ss = full s
286  fun recurse acc ss = let
287    val (normal, rest) = splitl (fn c => c <> #"\\") ss
288    val acc = string normal :: acc
289  in
290    case getc rest of
291      NONE => String.concat (List.rev acc)
292    | SOME (_, rest) => let
293      in
294        case getc rest of
295          NONE => String.concat (List.rev ("\\" :: acc))
296        | SOME (c, rest) =>
297          if quotable c then recurse (str c :: acc) rest
298          else recurse (str c :: "\\" :: acc) rest
299      end
300  end
301in
302  recurse [] ss
303end
304
305fun is_pseudo_target s = s = ".PHONY"
306
307val empty_ruledb = Binarymap.mkDict String.compare
308type depdb = (string,string list) Binarymap.dict
309
310fun app_insert (ddb, s, slist) =
311    case Binarymap.peek(ddb, s) of
312      NONE => Binarymap.insert(ddb, s, slist)
313    | SOME existing => Binarymap.insert(ddb, s, existing @ slist)
314
315fun extend_ruledb warn env {targets,dependencies,commands} (rdb,ddb) = let
316  val tgts = map dequote (tokenize (perform_substitution env targets))
317  val deps = map dequote (tokenize (perform_substitution env dependencies))
318in
319  if null commands then
320    (rdb,
321     List.foldl (fn (tgt, ddb) => app_insert(ddb, tgt, deps)) ddb tgts, tgts)
322  else let
323      val info = {dependencies = deps, commands = commands}
324      fun foldthis (t, dict) =
325          case Binarymap.peek(dict, t) of
326            NONE => Binarymap.insert(dict, t, info)
327          | SOME _ => let
328            in
329              warn ("Later rule for `"^t^
330                    "' takes precedence over earlier one.");
331              Binarymap.insert(dict, t, info)
332            end
333    in
334      (List.foldl foldthis rdb tgts, ddb, tgts)
335    end
336end
337
338fun ins (k,v) env = Binarymap.insert(env,k,v)
339infix |>
340fun x |> f = f x
341
342fun get_rule_info rdb env tgt =
343    case Binarymap.peek(rdb, tgt) of
344      NONE => NONE
345    | SOME {dependencies, commands} => let
346        val dep1 = [LIT (hd dependencies)] handle Empty => [LIT ""]
347        val env = env |> ins("<", dep1) |> ins("@", [LIT tgt])
348      in
349        SOME {dependencies = dependencies,
350              commands = map (perform_substitution env) commands}
351      end
352
353
354val base_environment0 = let
355  open Systeml
356  infix ++
357  fun p1 ++ p2 = OS.Path.concat(p1,p2)
358  val alist =
359      [("CC", [LIT CC]),
360       ("CP", if OS = "winNT" then [LIT "copy /b"] else [LIT "/bin/cp"]),
361       ("DEFAULT_TARGETS",
362        [VREF ("patsubst %.sml,%.uo,$(patsubst %Theory.sml,,"^
363               "$(patsubst %Script.sml,%Theory.uo,$(wildcard *.sml)))")]),
364       ("HOLDIR", [LIT HOLDIR]),
365       ("MLLEX", [VREF "protect $(HOLDIR)/tools/mllex/mllex.exe"]),
366       ("MLYACC", [VREF "protect $(HOLDIR)/tools/mlyacc/src/mlyacc.exe"]),
367       ("ML_SYSNAME", [LIT ML_SYSNAME]),
368       ("MV", if OS = "winNT" then [LIT "move", LIT "/y"] else [LIT "/bin/mv"]),
369       ("OS", [LIT OS]),
370       ("SIGOBJ", [VREF "HOLDIR", LIT "/sigobj"]),
371       ("UNQUOTE", [VREF ("protect $(HOLDIR)/" ^ xable_string "/bin/unquote")])] @
372      (if Systeml.ML_SYSNAME = "poly" then
373         [("POLY", [LIT (Systeml.protect Systeml.POLY)]),
374          ("POLYC", [LIT (Systeml.protect Systeml.POLYC)]),
375          ("POLY_VERSION", [LIT (Int.toString Systeml.POLY_VERSION)]),
376          ("POLYMLLIBDIR", [LIT (Systeml.protect Systeml.POLYMLLIBDIR)])]
377       else [])
378in
379  List.foldl (fn ((k,v), a) => Binarymap.insert(a, k, v))
380             (Binarymap.mkDict String.compare)
381             alist
382end
383
384fun base_environment () = let
385  val kernelid =
386      let
387        val strm = TextIO.openIn Holmake_tools.kernelid_fname
388        val s =
389            case TextIO.inputLine strm of
390                NONE => ""
391              | SOME s => hd (String.tokens Char.isSpace s) handle Empty => ""
392
393      in
394        s before TextIO.closeIn strm
395      end handle IO.Io _ => ""
396in
397  Binarymap.insert(base_environment0, "KERNELID", [LIT kernelid])
398end
399
400fun lookup e k =
401    case Binarymap.peek(e, k) of
402      NONE => (case OS.Process.getEnv k of
403                   NONE => [LIT ""]
404                 | SOME s => [LIT s])
405    | SOME q => normquote [] q
406
407
408fun env_extend (k, v) e = Binarymap.insert(e,k,v)
409
410end (* struct *)
411