Source

ocaml-indent / main.ml

The branch '1.2.0' does not exist.
Full commit
open Pos
open Args
module State = Machine.State
module Stack = Machine.Stack

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

module Parser = Xparser

(** [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 print_debug  : t -> int-> string -> unit
  val print_string : t -> int -> string -> unit
end = struct

  type t = (int * int) option

  let print_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 print_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 with_open_in_or_stdin path f = 
  match path with
  | None -> f stdin
  | Some path ->
      let ic = open_in path in
      let res = try `Ok (f ic) with e -> `Error e in
      close_in ic;
      match res with
      | `Ok v -> v
      | `Error e -> raise e

let indent_ic ic =
  let print_string = Printer.print_string lines in
  let print_debug = Printer.print_debug lines in

  let str = Tokenstr.of_channel ic in

  let flush_remaining_space info =
    print_string (Region.lnum (fst info.space)) (snd info.space)
  in

  let rec loop last_orig_region state str = match LazyList.peek 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 at_new_line = last_line <>  current_line in (* Is this token at a new line? *)

            (* update the original indent if [at_new_line] *)
            let state =
              if at_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 = Machine.update_state state ~at_new_line ~fix_indent str orig_region t in

            (* cursor *)
            (* if [fix_indent], and the cursor is on the line, the cursor pos may move *)
            (* Where the cursor move *)
(*
            if fix_indent then begin
              let cursor_info = match cursor with
                | None -> None
                | Some lines_cols ->
                    match
                      Region.contains_lines_cols last_orig_region lines_cols,
                      Region.contains_lines_cols orig_region lines_cols
                    with
                    | (`In | `Out_left), _   -> None                   (* far past *)
                    | `Out_right, `Out_right -> None                   (* far future *)
                    | `Out_right, `Out_left  -> Some `In_space_between (* cursor in the space_between *)
                    | `Out_right, `In        -> Some `In_the_token     (* cursor on the token *)
                    | _                      -> assert false           (* It must be more tolerant, but for now... *)
              in
            end;
*)

            (* printing *)

            if not at_new_line then print_string current_line space_between
            else 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 *)
              print_string last_line spaces;

              if debug then begin
                print_string current_line indent_string;
                if pre == post then
                  print_debug current_line
                    (Printf.sprintf "-- %s\n" (Sexp.to_string_mach (Stack.sexp_of_t pre.State.bases)))
                else
                  print_debug 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;

              print_string current_line indent_string;
            end;

            print_string current_line substr;

            (* Now move to the next token *)
            (* CR jfuruse: last_token thing seems strange. The state machine should be able to
               access previous tokens freely. (But with risk of memory leak) *)
            let last_token = match t with Parser.COMMENT _ -> state.State.last_token | _ ->  Some t in
            let post = 
              { post with 
                State.last_token = last_token; 
                last_indent = if at_new_line then State.indent pre else post.State.last_indent }
            in

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

let indent_file path = with_open_in_or_stdin (if path = "" then None else Some path) indent_ic

let _ = List.iter indent_file paths