juster avatar juster committed ef0a5cc

Rewrote Bashexpand, added features to Bashparam when necessary.

Comments (0)

Files changed (3)

 open Bashparam
-open Printf
 
 module Bashexpand =
   struct
+    (** Our error stores the index of the character where parsing failed. *)
+    exception ExpandError of int
+    exception UnbalancedBracket
 
-    (** Our error stores the index of the character where parsing failed. *)
-    exception ExpandError       of int
-    exception UnbalancedBracket of int
-
-    (** Search and destroy parameters ($VARs) in a string. **)
-    let parameter_expand str params =
-
-      (* Recursive helper function for parameter_expand. *)
-      let rec expand_at idx =
-
-        (* Point expand_name to the index after a $. *)
-        let rec expand_name idx =
-
-          let expand_inbraces = function (idx, name) ->
-
-            (* Similiar to expand_at. *)
-            (* Expand everything until closing bracket. *)
-            let rec expand_until_curly idx =
-              try
-                (* Look ahead for the next } and $. *)
-                let brckidx = String.index_from str idx '}' in
-                begin
-                  try
-                    let sigidx = String.index_from str idx '$' in
-                    if sigidx > brckidx then raise Not_found
-                    else
-                      (* XXX: Copy/pasted from expand_at below. Refactor? *)
-                      let donechunk = String.sub str idx (sigidx - idx) in
-                      match expand_name (sigidx + 1)
-                      with (nextidx, expanded) ->
-                        (* Repeat because the bracket could be gone now. *)
-                        match (expand_until_curly nextidx)
-                        with (newidx, fin) ->
-                          (newidx, donechunk ^ expanded ^ fin)
-                  with Not_found ->
-                    (* No $ was found or it comes after the closing bracket. *)
-                    (brckidx + 1, String.sub str idx (brckidx - idx))
-                end
-              (* If no } was found we have a problem. *)
-              with Not_found -> raise (ExpandError idx)
-            in
-
-            let expand_param_subst name idx =
-              (* XXX: What about badly formed input? *)
-              match expand_until_curly (idx + 1) with (newidx, subst) ->
-                let pval = assoc_str name params in
-                let expstr =
-                  match str.[idx] with
-                  | '-' -> if "" = pval then subst else pval
-                  |  _  -> ""
-                in (newidx, expstr)
-            in
-
-            match str.[idx] with
-            | '}' -> (idx+1, assoc_str name params)
-            | ':' -> expand_param_subst name (idx+1)
-            |  _  -> raise Not_found
-          in (* end of expand_inbraces *)
-
-          let extract_name idx =
-            if Str.string_match (Str.regexp "[a-zA-Z_-]+") str idx then
-              (Str.match_end (), Str.matched_string str)
-            else
-              raise (ExpandError idx)
-          in
-
-          printf "*DBG* str=%s idx=%d\n" str idx ;
-          
-          match str.[idx] with
-          | '$' ->
-              begin
-                try match expand_name (idx+1) with (newidx, name) ->
-                  (newidx, assoc_str name params)
-                with Invalid_argument(_) -> raise (ExpandError idx)
-              end
-          | '{' ->
-              begin
-                try expand_inbraces (expand_name (idx+1))
-                with Invalid_argument(_) -> raise (UnbalancedBracket idx)
-              end
-          | _ ->
-              match extract_name idx with (newidx, name) ->
-                (newidx, assoc_str name params)
-        in (* end of expand_name *)
-
-        try
-          let sigidx = String.index_from str idx '$' in
-          begin
-            try
-              let donechunk = String.sub str idx (sigidx - idx) in
-              match expand_name (sigidx+1)
-              with (nextidx, expanded) ->
-                donechunk ^ expanded ^ (expand_at nextidx)
-
-            (* This exception means the $ is at the end of the string. *)
-            with Invalid_argument(_) -> raise (ExpandError sigidx)
-          end
-        with Not_found ->
-          (* There are no more $'s we are done. *)
-          let len = String.length str in String.sub str idx (len - idx)
-      in (* end of expand_at *)
-
-      expand_at 0
-
-    let expand str params =
-      parameter_expand str params
-
+    (** 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", Param("BAR")) ;
-                     ("BAR", Param("Hello, World!")) ] in
-  print_endline (Bashexpand.parameter_expand
-                   "Substitutions${BAZ:- IS WORKING}!" paramslist)
+  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)
         in String.concat " " (indices_list 0 (List.length arr))
 end
 
+let assoc_arr_at name idx plist =
+  match List.assoc name plist with
+    | Param(str) -> if idx = 0 then str else ""
+    | ParamArray(arr) -> try List.nth arr idx
+      with Invalid_argument "index out of bounds" -> ""
+
 (** Return a string of all parameter names that are prefixed with prefix. *)
 let match_names prefix plist =
   let prelen = String.length prefix in
 
 val assoc_str : string -> (string * bash_param) list -> string
 val assoc_arr : string -> (string * bash_param) list -> string
+val assoc_arr_at : string -> int -> (string * bash_param) list -> string
 val assoc_arr_indices : string -> (string * bash_param) list -> string
 
 val match_names : string -> (string * bash_param) list -> string
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.