(* $Id$ *)

open Printf

let add_extension tbl s =
  let i =
    try String.index s ':' 
    with Not_found ->
      failwith "Invalid -x argument"
  in
  let id = String.sub s 0 i in
  let raw_tpl = String.sub s (i+1) (String.length s - i - 1) in
  let cmd_tpl = Cppo_command.parse raw_tpl in
  if Hashtbl.mem tbl id then
    failwith ("Multiple definitions for extension " ^ id)
  else
    Hashtbl.add tbl id cmd_tpl

let () =
  let extensions = Hashtbl.create 10 in
  let files = ref [] in
  let header = ref [] in
  let incdirs = ref [] in
  let out_file = ref None in
  let preserve_quotations = ref false in
  let show_exact_locations = ref false in
  let show_no_locations = ref false in
  let options = [
    "-D", Arg.String (fun s -> header := ("#define " ^ s ^ "\n") :: !header),
    "DEF
          Equivalent of interpreting '#define DEF' before processing the
          input";

    "-U", Arg.String (fun s -> header := ("#undef " ^ s ^ "\n") :: !header),
    "IDENT
          Equivalent of interpreting '#undef IDENT' before processing the
          input";

    "-I", Arg.String (fun s -> incdirs := s :: !incdirs),
    "DIR
          Add directory DIR to the search path for included files";

    "-include", Arg.String (fun s -> header := (sprintf "#include %S\n" s) ::
                                                 !header),
    "FILE
          Equivalent of interpreting '#include \"FILE\"' before processing the
          input";

    "-o", Arg.String (fun s -> out_file := Some s),
    "FILE
          Output file";

    "-q", Arg.Set preserve_quotations,
    "
          Identify and preserve camlp4 quotations";

    "-s", Arg.Set show_exact_locations,
    "
          Output line directives pointing to the exact source location of 
          each token, including those coming from the body of macro 
          definitions.  This behavior is off by default.";

    "-n", Arg.Set show_no_locations,
    "
          Do not output any line directive other than those found in the 
          input (overrides -s).";

    "-version", Arg.Unit (fun () ->
			    print_endline Cppo_version.cppo_version;
			    exit 0),
    "
          Print the version of the program and exit.";

    "-x", Arg.String (fun s -> add_extension extensions s),
    "NAME:CMD_TEMPLATE
          Define a custom preprocessor target section starting with:
            #ext \"NAME\"
          and ending with:
            #endext

          NAME must be a lowercase identifier of the form [a-z][A-Za-z0-9_]*

          CMD_TEMPLATE is a command template supporting the following
          special sequences:
            %F  file name (unescaped; beware of potential scripting attacks)
            %B  number of the first line
            %E  number of the last line
            %%  a single percent sign

          Filename, first line number and last line number are also
          available from the following environment variables:
          CPPO_FILE, CPPO_FIRST_LINE, CPPO_LAST_LINE.

          The command produced is expected to read the data lines from stdin
          and to write its output to stdout."
  ]
  in
  let msg = sprintf "\
Usage: %s [OPTIONS] [FILE1 [FILE2 ...]]
Options:" Sys.argv.(0) in
  let add_file s = files := s :: !files in
  Arg.parse options add_file msg;
  
  let inputs =
    let preliminaries =
      match List.rev !header with
	  [] -> []
	| l ->
	    let s = String.concat "" l in
	    [ Sys.getcwd (),
	      "<command line>", 
	      (fun () -> Lexing.from_string s),
	      (fun () -> ()) ]
    in
    let main =
      match List.rev !files with
	  [] -> [ Sys.getcwd (),
		  "<stdin>", 
		  (fun () -> Lexing.from_channel stdin),
		  (fun () -> ()) ]
	| l -> 
	    List.map (
	      fun file -> 
		let ic = lazy (open_in file) in
		Filename.dirname file,
		file,
		(fun () -> Lexing.from_channel (Lazy.force ic)),
		(fun () -> close_in (Lazy.force ic))
	    ) l
    in
    preliminaries @ main
  in

  let env = Cppo_eval.builtin_env in
  let buf = Buffer.create 10_000 in
  let _env =
    try Cppo_eval.include_inputs
      ~extensions
      ~preserve_quotations: !preserve_quotations
      ~incdirs: (List.rev !incdirs)
      ~show_exact_locations: !show_exact_locations
      ~show_no_locations: !show_no_locations
      buf env inputs
    with Cppo_types.Cppo_error msg
      | Failure msg ->
          eprintf "Error: %s\n%!" msg;
          exit 1
  in
  match !out_file with
      None ->
	print_string (Buffer.contents buf);
	flush stdout
    | Some file ->
	let oc = open_out file in
	output_string oc (Buffer.contents buf);
	close_out oc
