Source

ocaml-pkgbuild / bashexpand.ml

juster a4622fb 
juster 09947d5 


juster ef0a5cc 


juster 09947d5 
juster ef0a5cc 





























































































juster 09947d5 


juster ef0a5cc 



open Bashparam

module Bashexpand =
  struct
    (** Our error stores the index of the character where parsing failed. *)
    exception ExpandError of int
    exception UnbalancedBracket

    (** Search and destroy parameters (ie $FOO, ${BAR}) in a string. **)
    let param_expand strinput params =
      (* Current scanning position in the string. *)
      let pos = ref 0 in
      (* Increment an int ref but return its previous value. *)
      let preincr posref =
        let before = !posref in incr posref; before in
      (* Find the closing, balanced, curly bracket.
       * In the bash manpage, closing brackets do not cound if they are
       * backslashed or inside arithmetic expressions or command expansions.
       * I think if we simply count them we should be alright for the most part.
       *)
      let rec find_curly_close idx diff =
        try if strinput.[idx] = '}' then begin
          if diff = 1 then idx
          else find_curly_close (idx + 1) (diff - 1)
        end else
          let idx, diff = match strinput.[idx], strinput.[idx + 1] with
            | '$', '{' -> idx + 2, diff + 1
            (* Skip escaped closing brackets or open brackets *)
            | '\\', '}' | '\\', '$' -> idx + 2, diff
            | _, _ -> idx + 1, diff in find_curly_close idx diff
        with Invalid_argument _ -> raise UnbalancedBracket in
      (* Expand substitutions and perform necessary actions. *)
(*       let expand_sub pname posref endpos = *)
(*         let pos = !posref in *)
(*           incr posref; *)
(*           match str.[pos], Bashparam.assoc_str with *)
(*             (\* Use Default Values. *\) *)
(*             | '-' -> *)
      (* Pass this the position just past the parameter name. *)
      let expand_braces pname pval posref =
        let curlyclose = find_curly_close !posref 1 in
          match strinput.[!posref] with
            | '}' -> incr posref; pval
            | ':' | '#' | '%' -> failwith "Not implemented yet!"
            | _ -> raise (ExpandError !posref ) in
      (* extract_name adjusts the current position to past the $PARAMETER *)
      let extract_name posref =
        let name_re = Str.regexp "[a-zA-Z_-]+" in
          if Str.string_match name_re strinput !posref then begin
            posref := Str.match_end (); Str.matched_string strinput
          end else raise (ExpandError !posref) in
      (* Point expand_arrval to the index right after [ *)
      let expand_arrval pname posref =
        match strinput.[!posref] with
          (* TODO: create different results for @ or *? *)
          | '@' | '*' ->
              incr posref;
              (* Make sure there is a closing bracket. *)
              if strinput.[!posref] <> ']' then raise (ExpandError !posref)
              else incr posref;
              Bashparam.assoc_arr pname params
          | _ -> let digitre = Str.regexp "\\([0-9]+\\)\\]" in
              if Str.string_match digitre strinput !posref then begin
                posref := Str.match_end ();
                let arridx = int_of_string (Str.matched_group 1 strinput) in
                  Bashparam.assoc_arr_at pname arridx params
              end else raise (ExpandError !posref) in
      (* Point expand_at to the character after a $ *)
      let expand_at posref =
        Printf.printf "*DBG* %s\n%s^\n" strinput (String.make (6 + !posref) ' ');
        if strinput.[!posref] <> '{' then
          (* No curly brackets? This expansion is simple. *)
          let name = extract_name posref in Bashparam.assoc_str name params
        else begin
          incr posref;
          (* Load up the parameter's name and current value. *)
          let pname = extract_name posref in
          (* Within braces, we might be dealing with an array parameter's
           * element. *)
          let pval = (if strinput.[!posref] = '[' then begin
                        incr posref; expand_arrval pname posref
                      end else
                        Bashparam.assoc_str pname params) in
            expand_braces pname pval posref
        end in
      (* Begin our main search and destroy loop for param_expand. *)
      let outbuff = Buffer.create 16 in
        begin try while true do
          (* $earching... *)
          let newpos = ref (1 + (String.index_from strinput !pos '$')) in
            Buffer.add_substring outbuff strinput !pos (!newpos - !pos - 1);
            let expanded = expand_at newpos in
              pos := !newpos;
              Buffer.add_string outbuff expanded
        done
        with Not_found | Invalid_argument(_) ->
          (* There are no more $'s we are done. Or Invalid_argument means
             we tried to read past the end of the string. *)
          let len = String.length strinput in
            Buffer.add_substring outbuff strinput !pos (len - !pos);
        end;
        Buffer.contents outbuff
  end

let _ =
  let paramslist = [("FOO", ParamArray(["BAR"; "BAZ"]));
                    ("BAR", Param("Hello, World!"))] in
    print_endline (Bashexpand.param_expand "$FOO $BAR" paramslist) ;
    print_endline (Bashexpand.param_expand "${FOO}" paramslist) ;
    print_endline (Bashexpand.param_expand "${FOO[0]} ${FOO[@]}" paramslist)