Source

ocaml-indent / main.ml

Full commit
open Pos
open Machine

open Args

let check_lines l = function
  | None -> `Inside
  | Some (start, end_) ->
      if l < start then `Before
      else if l <= end_ then `Inside
      else `Over

module Printer = struct

  type t = (int * int) option

  let add_debug t lnum s = match check_lines lnum t with
    | `Before | `Over -> ()
    | `Inside -> print_string s

  (** Print a string [s] for the line number [lnum].
      If the printing text exceeds the interested region,
      It stops printing and raises Exit

      Currently it cannot print a string correctly if it has 
      different number of lines from the original. 
  *)
  let add_string t lnum s =
    let add_line t lnum s = match check_lines lnum t with
      | `Before -> ()
      | `Over -> raise Exit
      | `Inside -> print_string s
    in
    let get_line s =
      try
        let pos = String.index s '\n' in
        String.sub s 0 (pos + 1),
        Some (String.sub s (pos + 1) (String.length s - pos - 1))
      with
      | Not_found -> s, None
    in
    let rec iter lnum s =
      let line, rest =  get_line s in
      add_line t lnum line;
      match rest with
      | Some s -> iter (lnum+1) s
      | None -> ()
    in
    iter lnum s

end

let indent_file path =
  let printer = lines in
  let str = if path = "" then Tokenstr.of_channel stdin else Tokenstr.of_path path in

  let rec loop last_orig_region state str = match Tokenstr.destr str with
    | None -> state

    | Some (({ Tokenstr.token = None } as i), str) ->
        (* The token is white space *)
 

    | Some (({Tokenstr.token = Parser.EOF} as i), _) ->
        let space_between_region, space_between = i.Tokenstr.space in
        (try Printer.add_string printer (Region.lnum space_between_region) space_between with Exit -> ());
        state

    | Some ({ Tokenstr.token = t; region = orig_region; 
              space = (space_between_region, space_between); substr }, str) ->

        let last_line = Region.lnum last_orig_region in
        let current_line = Region.lnum orig_region in
        let new_line = last_line <>  current_line in (* Is this token at a new line? *)

        (* Where the cursor move *)
        let _cursor_info = match cursor with
          | None -> None
          | Some lines_cols ->
              match
                Pos.Region.contain_lines_cols last_orig_region lines_cols,
                Pos.Region.contain_lines_cols orig_region lines_cols
              with
              | (`In | `Right), _ -> None (* far past *)
              | `Left, `Right -> None (* far future *)
              | `Left, `Left -> (* cursor in the space_between *)
                  Some `In_space_between 
              | `Left, `In -> (* cursor on the token *)
                  Some `In_the_token
              | _ -> assert false (* It must be more tolerant, but for now... *)
        in

        match check_lines (Region.lnum orig_region) lines with
        | `Over -> 
            (* The token is outside of our interest. 
               Print the remaining things and go out *)
            (try Printer.add_string printer (Region.lnum space_between_region) space_between with Exit -> ());
            state
        | (`Before | `Inside as line_status) -> 
            (*
              Format.eprintf "<%s %d>@."
            *)

            let state =
              if new_line then { state with State.orig_indent = Region.columns orig_region } else state
            in

            let fix_indent = match line_status with
              | `Inside -> true
              | `Before -> false
              | `Over -> assert false
            in

            let pre, post = update_state state new_line fix_indent str orig_region t in
            
            (* printing *)

            if new_line then begin
              (* the line 1 has no previous new line char *)
              let spaces = try String.sub space_between 0 (String.rindex space_between '\n' + 1) with _ -> "" in
              let indent_string = String.make (State.indent pre) ' ' in

              (* CR jfuruse: can be a bug. something from space_between_region *)
              Printer.add_string printer last_line spaces;

              if debug then begin
                Printer.add_string printer current_line indent_string;
                if pre == post then
                  Printer.add_debug printer current_line
                    (Printf.sprintf "-- %s\n" (Sexplib.Sexp.to_string_mach (Stack.sexp_of_t pre.State.bases)))
                else
                  Printer.add_debug printer current_line
                    (Printf.sprintf "-- %s // %s\n"
                      (Sexplib.Sexp.to_string_mach (Stack.sexp_of_t pre.State.bases))
                      (Sexplib.Sexp.to_string_mach (Stack.sexp_of_t post.State.bases)))
              end;

              Printer.add_string printer current_line indent_string;
              Printer.add_string printer current_line substr
            end else begin
              Printer.add_string printer current_line space_between;
              Printer.add_string printer current_line substr;
            end;

            (* Now move to the next token *)

            let post =
              if new_line then
                { post with
                  State.last_token = (if t <> Parser.COMMENT then Some t else state.State.last_token)
                ; last_indent = State.indent pre
                }
              else
                { post with
                  State.last_token = (if t <> Parser.COMMENT then Some t else state.State.last_token)
                }
            in

            loop orig_region post str
  in
  let final_state = loop Region.zero State.init str in
  if showstate then State.print final_state;
  Tokenstr.close str

let indent_file path = try indent_file path with Exit -> ()

let _ = List.iter indent_file paths