Source

ocaml-indent / main.ml

The default branch has multiple heads

Full commit
open Pos
open Machine

let paths, debug, lines, showstate, cursor =
  let rev_paths = ref [] in
  let debug = ref false in
  let lines = ref None in
  let showstate = ref false in
  let cursor = ref None in
  Arg.parse [
    ("-debug", Arg.Set debug, "debugging");
    ("-lines", Arg.String (fun s ->
      try
        let pos = String.index s '-' in
        let (start,end_) = (int_of_string (String.sub s 0 pos),
                            int_of_string (String.sub s (pos+1) (String.length s - pos - 1))) in
        if start <= 0 || end_ <= 0 || start > end_ then
          failwith (Printf.sprintf "Wrong -lines specification: %s" s);
        lines := Some (start, end_)
      with
      | _ -> failwith (Printf.sprintf "Wrong -lines specification: %s" s)),
     "lines, ex. 10-12") ;
    ("-cursor", Arg.String (fun s ->
      try
        let pos = String.index s ':' in
        let (rows,cols) = (int_of_string (String.sub s 0 pos),
                           int_of_string (String.sub s (pos+1) (String.length s - pos - 1))) in
        if rows <= 0 || cols < 0 then
          failwith (Printf.sprintf "Wrong -cursor specification: %s" s);
        cursor := Some (rows, cols)
      with
      | _ -> failwith (Printf.sprintf "Wrong -cursor specification: %s" s)),
     "cursor position, ex. 10:12") ;
    ("-show-state", Arg.Set showstate, "show state at the last");
  ] (fun s -> rev_paths := s :: !rev_paths) "indent paths";
  let paths = List.rev !rev_paths in
  let paths = if paths = [] then [""] else paths in (* CR jfuruse: ugly *)
  begin match paths, !lines with
  | [], _ -> assert false
  | [_], _ -> ()
  | _, Some _ -> failwith "Region can be specified with at most one file"
  | _ -> ()
  end;
  paths, !debug, !lines, !showstate, !cursor

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

  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

  let add_string t lnum s = try add_string t lnum s with Exit -> ()

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 = Parser.EOF} as i), _) ->
        let space_between = i.Tokenstr.space in
        Printer.add_string printer (Region.lnum last_orig_region) space_between;
        state

    | Some ({ Tokenstr.token = t; region = orig_region; space = 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

        (* 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 -> 
            Printer.add_string printer last_line space_between;
            state
        | (`Before | `Inside as line_status) -> 
            (*
              Format.eprintf "<%s %d>@."
              (Sexplib.Sexp.to_string_mach (Parser.sexp_of_token t))
              Fatal error: exception Lexer.Error(0, _)
            *)


            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

              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 _ = List.iter indent_file paths