erlint / src / formatter.ml

open Token

module Po = Program_options

module R = Return

type error = { line_number : int
	     ; msg         : string
	     }

type warning = error

type parser_state =
  | Top_level
  | Function
  | Compiler_directive

type state = { program_options   : Program_options.t
	     (* The number of indentations, not the number of spaces *)
	     ; indentation_level : int
	     (* This is the current line we are reading, starting at 0 *)
	     ; src_line_number   : int
	     (* This is current line number we are writing, starting at 0 *)
	     ; dst_line_number   : int
	     ; parser_state      : parser_state list
	     ; builder           : Format_builder.t
	     ; warnings          : warning list
	     }


let default_state program_options buffer =
  { program_options   = program_options
  ; indentation_level = 0
  ; src_line_number   = 0
  ; dst_line_number   = 0
  ; parser_state      = [Top_level]
  ; builder           = Format_builder.t
  ; warnings          = []
  }


let string_of_token = function
  | Token.Keyword kwd -> kwd
  | Token.Atom atm    -> atm
  | Token.Var var     -> var
  | Token.Char char   -> char
  | Token.Number num  -> num
  | Token.String str  -> str
  | Token.Comment com -> "%" ^ com
  | Token.Newline     -> "\n"
  | Token.Dot         -> "."

let incr ?(step = 1) num = num + step

let incr_src_line state =
  { state with src_line_number = incr state.src_line_number }

let incr_dst_line state =
  Buffer.add_string state.buffer (string_of_token Newline);
  { state with
    dst_line_number = incr state.dst_line_number;
    imputed_line_length = 0
  }

(*
 * Kind of lame I'm naming this bind but this is effectively
 * a monad-like thing so might as well stick to the literature?
 *)
let bind f = function
  | R.Success (state, rest) ->
    f state rest
  | R.Failure error ->
    R.Failure error

let fail state msg =
  R.Failure { line_number = state.src_line_number
	    ; msg = msg
	    }

let add_warning state msg =
  { state with
    warnings = { line_number = state.dst_line_number
	       ; msg = msg
	       }::state.warnings
  }

let is_line_too_long state line =
  (String.length line
   + state.indentation_level * state.program_options.Po.indent_spaces
   > state.program_options.Po.max_line_length)

let succeed state =
  R.Success (Buffer.contents state.buffer, state.warnings)

let rec format_top_level state = function
  | Newline::xs ->
    format_top_level (incr_dst_line (incr_src_line state)) xs
  | (Comment text)::xs -> begin
    Buffer.add_string state.buffer (string_of_token (Comment text));
    if is_line_too_long state text then
      format_top_level
	(add_warning state "Comment longer than maximum line length")
	xs
    else
      format_top_level state xs
  end
  | (Keyword "-")::(Atom "module")::xs ->
    bind
      format_top_level
      (format_module
	 { state with
	   state.parser_state =
	     Compiler_directive::state.parser_state;
	   line_builder =
	     Line_builder.add_tokens
	       state.line_builder
	       [Keyword "-"; Atom "module"]
	 }
	 xs)
  | [] -> succeed state
  | _::xs -> format_top_level state xs
and format_module state = function
  | (Keyword "(")::(Atom mod_name)::(Keyword ")")::Dot::Newline::xs ->
    


let format_code program_options code =
  (*
   * Initiate the buffer at the number of tokens * 10, just
   * a reasonable round number
   *)
  let buffer = Buffer.create (List.length code * 10) in
  let state = default_state program_options buffer in
  match format_top_level state code with
    | R.Success (state, []) ->
      succeed state
    | R.Success (state, _) ->
      fail state "Did not consume entire input"
    | R.Failure error ->
      R.Failure error

let format_to_channel program_options out_chan code =
  match format_code program_options code with
    | R.Success (formatted_code, warnings) -> begin
      output_string out_chan formatted_code;
      R.Success warnings
    end
    | R.Failure errors ->
      R.Failure errors
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.