(* camlp5r *)
(* ocpp.ml,v *)
(* Copyright (c) INRIA 2007-2017 *)

open Versdep;

value buff = ref (string_create 80);
value store len x =
  do {
    if len >= string_length buff.val then
      buff.val := string_cat buff.val (string_create (string_length buff.val))
    else ();
    string_set buff.val len x;
    succ len
  }
;
value get_buff len = bytes_to_string (string_sub buff.val 0 len);

value rec copy_strip_locate cs =
  match cs with parser
  [ [: `'$' :] -> maybe_locate cs
  | [: `c :] -> do { print_char c; copy_strip_locate cs }
  | [: :] -> () ]
and maybe_locate cs =
  match cs with parser
  [ [: `'1'..'9' :] -> locate cs
  | [: :] -> do { print_char '$'; copy_strip_locate cs } ]
and locate cs =
  match cs with parser
  [ [: `'0'..'9' :] -> locate cs
  | [: `':' :] -> inside_locate cs
  | [: :] -> raise (Stream.Error "colon char expected") ]
and inside_locate cs =
  match cs with parser
  [ [: `'$' :] -> copy_strip_locate cs
  | [: `'\\'; `c :] -> do { print_char c; inside_locate cs }
  | [: `c :] -> do { print_char c; inside_locate cs }
  | [: :] -> raise (Stream.Error "end of file in locate directive") ]
;

value quot name pos str =
  let loc = Ploc.make_unlined (pos, pos + String.length str) in
  let exp =
    try
      match Quotation.find name with
      [ Quotation.ExStr f -> f
      | _ -> raise Not_found ]
    with
    [ Not_found -> Ploc.raise loc Not_found ]
  in
  let new_str =
    try exp True str with
    [ Ploc.Exc _ exc -> Ploc.raise loc exc
    | exc -> Ploc.raise loc exc ]
  in
  let cs = Stream.of_string new_str in copy_strip_locate cs
;

value rec ident len =
  parser
  [ [: `('A'..'Z' | 'a'..'z' | '0'..'9' | '_' | ''' as c); s :] ->
      ident (store len c) s
  | [: :] -> get_buff len ]
;

value rec copy cs =
  match cs with parser
  [ [: `'<' :] -> maybe_quot cs
  | [: `'"' :] -> do { print_char '"'; inside_string cs }
  | [: `c :] -> do { print_char c; copy cs }
  | [: :] -> () ]
and maybe_quot cs =
  match cs with parser
  [ [: `'<' :] ep -> inside_quot "" ep 0 cs
  | [: `':'; i = ident 0; `'<' ? "less char expected" :] ep ->
      inside_quot i ep 0 cs
  | [: :] -> do { print_char '<'; copy cs } ]
and inside_quot name pos len cs =
  match cs with parser
  [ [: `'>' :] -> maybe_end_quot name pos len cs
  | [: `c :] -> inside_quot name pos (store len c) cs
  | [: :] -> raise (Stream.Error "end of file in quotation") ]
and maybe_end_quot name pos len cs =
  match cs with parser
  [ [: `'>' :] -> do { quot name pos (get_buff len); copy cs }
  | [: :] -> inside_quot name pos (store len '>') cs ]
and inside_string cs =
  match cs with parser
  [ [: `'"' :] -> do { print_char '"'; copy cs }
  | [: `c :] -> do { print_char c; inside_string cs }
  | [: :] -> raise (Stream.Error "end of file in string") ]
;

value copy_quot cs = do { copy cs; flush stdout; };

value loc_fmt =
  match Sys.os_type with
  [ "MacOS" ->
      ("File \"%s\"; line %d; characters %d to %d\n### " : format 'a 'b 'c)
  | _ -> ("File \"%s\", line %d, characters %d-%d:\n" : format 'a 'b 'c) ]
;

value print_location loc file =
  let (file, line, c1, c2) = Ploc.from_file file loc in
  do { Printf.eprintf loc_fmt file line c1 c2; flush stderr; }
;

value file = ref "";
Arg.parse [] (fun x -> file.val := x) "ocpp <objects> <file>";

value main () =
  try
    if file.val <> "" then
      copy_quot (Stream.of_channel (open_in_bin file.val))
    else ()
  with exc ->
    do {
      print_newline ();
      flush stdout;
      let exc =
        match exc with
        [ Ploc.Exc loc exc -> do { print_location loc file.val; exc }
        | exc -> exc ]
      in
      raise exc
    }
;

Odyl_main.name.val := "ocpp";
Odyl_main.go.val := main;
