Source

ocaml-indent / main.ml

Full commit
open Pos
open Machine
open Args

module Sexp = Sexplib.Sexp (* No open Sexplib, since Parser corrides with Sexplib.Parser *)

(** [l] is in the interested area or not? *)
let check_line l = function
  | None -> `Inside
  | Some (start, end_) ->
      if l < start then `Before
      else if l <= end_ then `Inside
      else `Over

module Printer : sig
  type t = (int * int) option
  val add_debug  : t -> int-> string -> unit
  val add_string : t -> int -> string -> unit
end = struct

  type t = (int * int) option

  let add_debug t lnum s = match check_line 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.

      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_line 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
    try iter lnum s with Exit -> ()

end

open Tokenstr

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

  let flush_remaining_space info =
    ignore (Printer.add_string lines (Region.lnum (fst info.space)) (snd info.space))
  in

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

    | Some (({ token = Parser.EOF } as info), _) -> 
        flush_remaining_space info;
        state

    | Some (({ token = t; 
               region = orig_region; 
               space = (_space_between_region, space_between); 
               substr } as info), str) ->

        match check_line (Region.lnum orig_region) lines with
        | `Over -> 
            (* The token is outside of our interest. 
               Print the remaining things and go out *)
            flush_remaining_space info;
            state
        | (`Before | `Inside as line_status) -> 
            (*
              Format.eprintf "<%s %d>@."
            *)

            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

            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 lines last_line spaces;

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

              Printer.add_string lines current_line indent_string;
              Printer.add_string lines current_line substr
            end else begin
              Printer.add_string lines current_line space_between;
              Printer.add_string lines 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