1structure internal_functions :> internal_functions =
2struct
3
4fun member e [] = false
5  | member e (h::t) = e = h orelse member e t
6
7fun equal x y = x = y
8
9fun spacify0 acc [] = List.rev acc
10  | spacify0 acc [x] = List.rev (x::acc)
11  | spacify0 acc (h::t) = spacify0 (" "::h::acc) t
12
13val spacify = String.concat o spacify0 []
14
15fun dropWhile P [] = []
16  | dropWhile P (l as (h::t)) = if P h then dropWhile P t else l
17
18fun find_unescaped cset = let
19  open Substring
20  fun recurse i ss =
21      case getc ss of
22        NONE => NONE
23      | SOME(c', ss') => if member c' cset then SOME i
24                         else if c' = #"\\" then
25                           case getc ss' of
26                             NONE => NONE
27                           | SOME (_, ss'') => recurse (i + 2) ss''
28                         else recurse (i + 1) ss'
29in
30  recurse 0
31end
32
33fun tokenize s = let
34  (* could be a call to tokens, but for escaped spaces getting in the way *)
35  open Substring
36  val ss = dropl Char.isSpace (full s)
37  fun recurse acc ss =
38      (* assumes first character of ss is not isSpace, or size ss = 0  *)
39      if size ss = 0 then List.rev acc
40      else
41        case find_unescaped [#" ", #"\t"] ss of
42          NONE => List.rev (string ss::acc)
43        | SOME i => let
44            val (t1, rest) = splitAt(ss, i)
45          in
46            recurse (string t1::acc) (dropl Char.isSpace rest)
47          end
48in
49  recurse [] ss
50end
51
52fun subst(from,to,on) = let
53  open Substring
54  val (from,to,on) = (full from, full to, full on)
55  val _ = size from > 0 orelse
56          raise Fail "empty from argument to `subst' function"
57  fun recurse acc ss = let
58    val (ok, rest) = position (string from) ss
59  in
60    if size rest > 0 then
61      recurse (to::ok::acc) (slice(rest, size from, NONE))
62    else concat (List.rev (ok::acc))
63  end
64in
65  recurse [] on
66end
67
68
69fun find_percent ss = let
70  open Substring
71  fun recurse acc ss =
72      case getc ss of
73        NONE => (full (String.implode (List.rev acc)), full "")
74      | SOME(c, ss') => let
75        in
76          case c of
77            #"\\" => let
78            in
79              case getc ss' of
80                NONE => (full (String.implode (List.rev (c::acc))), full "")
81              | SOME(c',ss'') =>
82                if c' = #"%" orelse c' = #"\\" then
83                  recurse (c'::acc) ss''
84                else
85                  recurse (c'::c::acc) ss''
86            end
87          | _ => if c = #"%" then (full (String.implode(List.rev acc)), ss)
88                 else recurse (c::acc) ss'
89        end
90in
91  recurse [] ss
92end
93
94fun pattern_match pattern object = let
95  open Substring
96  fun translate_pattern patss = let
97    val (pfx, rest) = find_percent patss
98    val sfx = if size rest > 0 then let
99                  val (sfx, rest') = find_percent (slice(rest, 1, NONE))
100                in
101                  if size rest' > 0 then
102                    raise Fail "Multiple % chars in pattern"
103                  else
104                    SOME sfx
105                end
106              else NONE
107  in
108    (pfx, sfx)
109  end
110  fun fromright (patss, i) (objss, j) =
111      if j = ~1 then NONE
112      else if i = ~1 then SOME (slice(objss, 0, SOME (j + 1)))
113      else let
114          val pc = sub(patss, i)
115          val oc = sub(objss, j)
116        in
117          if pc = oc then fromright(patss, i - 1) (objss, j - 1)
118          else NONE
119        end
120
121  val (patpfx, patsfx) = translate_pattern (full pattern)
122  val objss = full object
123in
124  if isPrefix (string patpfx) objss then let
125      val objrest = slice(objss, size patpfx, NONE)
126    in
127      case patsfx of
128        NONE => if size objrest = 0 then SOME "" else NONE
129      | SOME sfx => Option.map string
130                               (fromright (sfx, size sfx - 1)
131                                          (objrest, size objrest - 1))
132    end
133  else NONE
134end
135
136fun pcsubst (residue, pattern) = let
137  open Substring
138  val patss = full pattern
139  val resss = full residue
140  fun recurse acc ss =
141      case find_unescaped [#"%"] ss of
142        NONE => concat (List.rev (ss::acc))
143      | SOME i => let
144          val (pfx, sfx) = splitAt(ss, i)
145        in
146          recurse (resss::pfx::acc) (slice(sfx, 1, NONE))
147        end
148in
149  recurse [] (full pattern)
150end
151
152fun patsubst (from,to,arglist) = let
153  fun mapthis s =
154      case pattern_match from s of
155        NONE => s
156      | SOME stem => pcsubst(stem,to)
157in
158  spacify (map mapthis (tokenize arglist))
159end
160
161fun split_to_directories (comps : parse_glob.t list) = let
162  open parse_glob
163  fun recurse h acc [] = List.rev (List.rev h::acc)
164    | recurse h acc (RE r :: rest) = recurse (RE r::h) acc rest
165    | recurse h acc (CHAR #"/" :: rest) = recurse [] (List.rev h::acc) rest
166    | recurse h acc (CHAR c :: rest) = recurse (CHAR c :: h) acc rest
167in
168  recurse [] [] comps
169end
170
171fun dirfiles dirname = let
172  val dirstrm = OS.FileSys.openDir dirname
173  fun recurse acc =
174      case OS.FileSys.readDir dirstrm of
175          NONE => "." :: ".." :: acc
176        | SOME fname => recurse (fname :: acc)
177in
178  recurse [] before OS.FileSys.closeDir dirstrm
179end
180
181fun safeIsDir s =
182    OS.FileSys.isDir s handle OS.SysErr _ => false
183
184fun wildcard s =
185    if s = "" then [""]
186    else let
187      open parse_glob
188      val comps = parse_glob_components s
189      val split_comps = split_to_directories comps
190      fun initial_split d l k =
191          case l of
192              h::t => if null h then
193                        initial_split "/" t (fn (d,s,r) => k (d,s ^ "/", r))
194                      else k (d,"", l)
195            | [] => k (d,"", l)
196      val (starting_dir,pfx, rest) =
197          initial_split (OS.FileSys.getDir()) split_comps (fn x => x)
198      fun recurse curpfx curdir complist : string list =
199          case complist of
200              c::cs => (* c must be non-null *)
201              let
202                val dotfiles_ok = case c of CHAR #"." :: _ => true
203                                          | _ => false
204                val re = toRegexp c
205                val files = Listsort.sort String.compare (dirfiles curdir)
206                val m = regexpMatch.match re
207                val require_dir = not (null cs)
208                val (_, _, cs') = initial_split "" cs (fn x => x)
209                val slashes = if require_dir then "/" else ""
210                fun check s =
211                    m s andalso
212                    (dotfiles_ok orelse String.sub(s,0) <> #".") andalso
213                    (not require_dir orelse
214                     safeIsDir (OS.Path.concat(curdir, s)))
215                      handle e => raise Fail (s ^ " - " ^ exnMessage e)
216              in
217                case List.filter check files of
218                    [] => []
219                  | fs =>
220                    let
221                      val newpfxs = map (fn s => curpfx ^ s ^ slashes) fs
222                    in
223                      if null cs' then newpfxs
224                      else let
225                        val newdirs = map (fn d => OS.Path.concat(curdir, d)) fs
226                        val more_results : string list list =
227                            ListPair.map (fn (pfx,dir) => recurse pfx dir cs')
228                                         (newpfxs,newdirs)
229                      in
230                        List.concat more_results
231                      end
232                    end
233              end
234            | [] => raise Fail "wildcard.recurse: should never happen"
235    in
236      case rest of
237          [] => (* happens if input was a series of forward slashes *) [s]
238        | _ => (case recurse pfx starting_dir rest of [] => [s] | x => x)
239    end
240
241fun get_first f [] = NONE
242  | get_first f (h::t) = (case f h of NONE => get_first f t | x => x)
243
244fun which arg =
245  let
246    open OS.FileSys Systeml
247    val sepc = if isUnix then #":" else #";"
248    fun check p =
249      let
250        val fname = OS.Path.concat(p, arg)
251      in
252        if access (fname, [A_READ, A_EXEC]) then SOME fname else NONE
253      end
254    fun smash NONE = "" | smash (SOME s) = s
255  in
256    case OS.Process.getEnv "PATH" of
257        SOME path =>
258        let
259          val paths = (if isUnix then [] else ["."]) @
260                      String.fields (fn c => c = sepc) path
261        in
262          smash (get_first check paths)
263        end
264    | NONE => if isUnix then "" else smash (check ".")
265  end
266
267fun shell arg =
268  let
269    open Unix
270
271    (* TODO This gets rid of all carriage returns; should only replace
272       those paired with a newline *)
273    fun fix_nls s =
274      let
275        val s = String.translate (fn c => if c = #"\r" then "" else String.str c) s
276        val s = if String.isSuffix "\n" s then
277                  String.substring (s, 0, String.size s - 1)
278                else s
279      in
280        String.map (fn c => if c = #"\n" then #" " else c) s
281      end
282
283    val proc = execute ("/bin/sh", ["-c", arg])
284    val ins = textInstreamOf proc
285    val str = fix_nls (TextIO.inputAll ins)
286  in
287    if OS.Process.isSuccess (reap proc) then str else ""
288  end
289  handle OS.SysErr _ => ""
290
291fun function_call (fnname, args, eval) = let
292  open Substring
293in
294  case fnname of
295    "if" =>
296    if length args <> 2 andalso length args <> 3 then
297      raise Fail "Bad number of arguments to `if' function."
298    else let
299        val condition = dropr Char.isSpace (hd args)
300        val condition_evalled = eval condition
301      in
302        if condition_evalled <> "" then eval (List.nth(args, 1))
303        else if length args = 3 then eval (List.nth(args, 2))
304        else ""
305      end
306  | "subst" =>
307    if length args <> 3 then
308      raise Fail "Bad number of arguments to `subst' function."
309    else let
310        val args_evalled = map eval args
311        val tuple = case args_evalled of
312                      [x,y,z] => (x,y,z)
313                    | _ => raise Fail "Can't happen"
314      in
315        subst tuple
316      end
317  | "patsubst" =>
318    if length args <> 3 then
319      raise Fail "Bad number of arguments to `patsubst' function."
320    else let
321        val args_evalled = map eval args
322        val tuple = case args_evalled of
323                      [x,y,z] => (x,y,z)
324                    | _ => raise Fail "Can't happen"
325      in
326        patsubst tuple
327      end
328  | "protect" => if length args <> 1 then
329                   raise Fail "Bad number of arguments to `protect' function."
330                 else
331                   Systeml.protect (eval (hd args))
332  | "dprot" => if length args <> 1 then
333                 raise Fail "Bad number of arguments to 'dprot' function."
334               else subst(" ", "\\ ", eval (hd args))
335  | "findstring" => if length args <> 2 then
336                      raise Fail "Bad number of arguments to 'findstring' \
337                                 \function."
338                    else let
339                        val (findstr, instr) = case map eval args of
340                                                 [x,y] => (x,y)
341                                               | _ => raise Fail "Can't happen"
342                        open Substring
343                        val (pfx,sfx) = position findstr (full instr)
344                      in
345                        if size sfx = 0 then "" else findstr
346                      end
347  | "which" => if length args <> 1 then
348                 raise Fail "Bad number of arguments to 'which' function"
349               else let
350                 val arg_evalled = eval (hd args)
351               in
352                 which arg_evalled
353               end
354  | "wildcard" => if length args <> 1 then
355                    raise Fail "Bad number of arguments to 'wildcard' function"
356                  else let
357                    val arg_evalled = eval (hd args)
358                  in
359                    spacify (wildcard arg_evalled)
360                  end
361  | "shell" => if length args <> 1 then
362                 raise Fail "Bad number of arguments to 'shell' function"
363               else let
364                 val arg_evalled = eval (hd args)
365               in
366                  shell arg_evalled
367               end
368  | _ => raise Fail ("Unknown function name: "^fnname)
369end
370
371
372end (* struct *)
373