454 lines
11 KiB
OCaml
454 lines
11 KiB
OCaml
(* Julien Verlaguet, Yoann Padioleau
|
|
*
|
|
* Copyright (C) 2011 Facebook
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public License
|
|
* version 2.1 as published by the Free Software Foundation, with the
|
|
* special exception on linking described in file license.txt.
|
|
*
|
|
* This library is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the file
|
|
* license.txt for more details.
|
|
*)
|
|
|
|
(*****************************************************************************)
|
|
(* Prelude *)
|
|
(*****************************************************************************)
|
|
(* julien: this is a copy/paste of the original pp.ml.
|
|
* It is slightly modified, and I don't know how much these modifications
|
|
* affect xhpizer. I hope to be able to merge these two files back together.
|
|
*)
|
|
|
|
(*****************************************************************************)
|
|
(* Types *)
|
|
(*****************************************************************************)
|
|
|
|
(* we use a backtracking model *)
|
|
exception Fail
|
|
|
|
type env = {
|
|
(* the actual printing hook, sometimes temporarily set to do_nothing()
|
|
* when trying something before actually printing it. *)
|
|
print: (string -> unit);
|
|
|
|
(* stack of margin, push'ed and pop'ed when processing {} *)
|
|
mutable margin: int list;
|
|
|
|
(* current column *)
|
|
mutable cmargin: int;
|
|
(* current line *)
|
|
mutable line: int;
|
|
|
|
(* depth in the tree of try_ *)
|
|
mutable level: int;
|
|
|
|
(* for the parenthesis automatic insertion *)
|
|
mutable priority: int;
|
|
|
|
(* pad: ?? *)
|
|
mutable last_nl: bool;
|
|
mutable emptyl: bool;
|
|
mutable failed: bool;
|
|
mutable pushed: bool;
|
|
}
|
|
|
|
(*****************************************************************************)
|
|
(* Helpers *)
|
|
(*****************************************************************************)
|
|
|
|
let empty o = {
|
|
print = o;
|
|
margin = [0];
|
|
priority = 0;
|
|
cmargin = 0;
|
|
level = 0;
|
|
pushed = false;
|
|
last_nl = false;
|
|
emptyl = false;
|
|
failed = false;
|
|
line = 0;
|
|
}
|
|
|
|
let debug env f =
|
|
let buf = Buffer.create 256 in
|
|
let env' = { env with print = (fun s -> Buffer.add_string buf s)} in
|
|
(try f env' with _ -> ());
|
|
Printf.printf "Debug %s\n" (Buffer.contents buf)
|
|
|
|
let do_nothing _ = ()
|
|
|
|
(*****************************************************************************)
|
|
(* Newlines and spaces *)
|
|
(*****************************************************************************)
|
|
|
|
let print env x =
|
|
(* todo: this is the case right now for comments.
|
|
* we should normalize those comments?
|
|
* if (String.contains x '\n')
|
|
* then failwith (Printf.sprintf "%s contains a newline\n" x);
|
|
*)
|
|
|
|
env.last_nl <- false;
|
|
env.emptyl <- false;
|
|
|
|
env.cmargin <- env.cmargin + String.length x;
|
|
if env.cmargin >= 80
|
|
then
|
|
(* there is nothing to backtrack on, so just print it *)
|
|
if env.level = 0
|
|
then begin env.print x; env.failed <- true end
|
|
else raise Fail
|
|
else env.print x
|
|
|
|
let spaces env =
|
|
for _i = 1 to List.hd env.margin do
|
|
print env " ";
|
|
done
|
|
|
|
let newline env =
|
|
env.pushed <- false;
|
|
if env.last_nl
|
|
then env.emptyl <- true;
|
|
env.last_nl <- true;
|
|
env.cmargin <- 0;
|
|
env.line <- env.line + 1;
|
|
env.print "\n"
|
|
|
|
let newline_opt env =
|
|
if env.last_nl
|
|
then ()
|
|
else newline env
|
|
|
|
let space_or_nl env =
|
|
if env.cmargin < 75
|
|
then print env " "
|
|
else (newline env; spaces env)
|
|
|
|
(*****************************************************************************)
|
|
(* Margins *)
|
|
(*****************************************************************************)
|
|
|
|
let margin_offset = ref 2
|
|
|
|
let push env =
|
|
env.pushed <- true;
|
|
env.margin <- List.hd env.margin + !margin_offset :: env.margin
|
|
|
|
let pop env =
|
|
env.margin <- List.tl env.margin
|
|
|
|
let nest env f =
|
|
push env;
|
|
f env;
|
|
pop env
|
|
|
|
let nest_opt env f =
|
|
if env.pushed
|
|
then f env
|
|
else begin
|
|
push env;
|
|
f env;
|
|
pop env
|
|
end
|
|
|
|
let nestc env f =
|
|
env.margin <- env.cmargin :: env.margin;
|
|
f env;
|
|
pop env
|
|
|
|
let nest_block env f =
|
|
print env "{";
|
|
newline env;
|
|
nest env f;
|
|
spaces env;
|
|
print env "}"
|
|
|
|
let nest_block_nl env f =
|
|
nest_block env f;
|
|
newline env
|
|
|
|
(*****************************************************************************)
|
|
(* Lists *)
|
|
(*****************************************************************************)
|
|
|
|
let rec simpl_list env f sep = function
|
|
| [] -> ()
|
|
| [x] -> f env x
|
|
| x :: rl -> f env x; print env sep; simpl_list env f sep rl
|
|
|
|
let rec list_sep env f sep = function
|
|
| [] -> ()
|
|
| [x] -> f env x
|
|
| x :: rl -> f env x; sep env; list_sep env f sep rl
|
|
|
|
let flat_list env f opar l sep cpar =
|
|
print env opar;
|
|
list_sep env f (fun env -> print env sep; print env " ") l;
|
|
print env cpar
|
|
|
|
(* pad: used to take a last_nl parameter, but it was not used *)
|
|
let nl_nested_list env f opar l sep cpar =
|
|
print env opar;
|
|
nest env (fun env ->
|
|
newline env;
|
|
spaces env;
|
|
list_sep env f (fun env -> print env sep; newline env; spaces env) l;
|
|
newline env;
|
|
);
|
|
if cpar <> ""
|
|
then begin
|
|
spaces env;
|
|
print env cpar
|
|
end
|
|
|
|
(*****************************************************************************)
|
|
(* Backtracking combinators *)
|
|
(*****************************************************************************)
|
|
|
|
let fail () = raise Fail
|
|
|
|
let try_ env f =
|
|
f { env with print = do_nothing; level = env.level + 1};
|
|
f env
|
|
|
|
|
|
let choice_left env f1 f2 =
|
|
try try_ env f1
|
|
with
|
|
| Fail when env.level = 0 ->
|
|
(try f2 env with Fail -> assert false)
|
|
(* otherwise, just let the exception bubble up more *)
|
|
|
|
|
|
let choice_right env f1 f2 =
|
|
try try_ env f1
|
|
with Fail ->
|
|
f2 env
|
|
|
|
let try_hard env f =
|
|
try
|
|
f { env with print = do_nothing; level = 1 };
|
|
f env
|
|
with Fail ->
|
|
let env' = { env with failed = false; print = do_nothing; level = 0 } in
|
|
f env';
|
|
if env'.failed
|
|
then raise Fail
|
|
else f { env with level = 0 }
|
|
|
|
let cut_list env f l =
|
|
List.iter (
|
|
fun x ->
|
|
choice_right env
|
|
(fun env -> f env x)
|
|
(fun env -> newline env; spaces env; f env x)
|
|
) l
|
|
|
|
|
|
let list env f opar l sep cpar =
|
|
let simple = (fun env -> flat_list env f opar l sep cpar) in
|
|
let nested = (fun env -> nl_nested_list env f opar l sep cpar) in
|
|
choice_right env simple nested
|
|
|
|
let list_left env f opar l sep cpar =
|
|
let simple = (fun env -> if l <> [] then print env " "; flat_list env f opar l sep cpar) in
|
|
let nested = (fun env -> nl_nested_list env f opar l sep cpar) in
|
|
choice_left env simple nested
|
|
|
|
let nested_arg env f opar l sep cpar =
|
|
let rec elt = function
|
|
| [] -> assert false
|
|
| [x] ->
|
|
f env x;
|
|
newline env;
|
|
| x :: rl ->
|
|
f env x; print env sep; newline env; spaces env;
|
|
elt rl
|
|
in
|
|
nestc env (
|
|
fun env ->
|
|
print env opar;
|
|
nestc env (
|
|
fun _env ->
|
|
elt l;
|
|
);
|
|
spaces env;
|
|
print env cpar;
|
|
)
|
|
|
|
|
|
let fun_args env f opar l sep cpar =
|
|
let simple = (
|
|
fun env ->
|
|
let line = env.line in
|
|
flat_list env f opar l sep cpar;
|
|
if line <> env.line then fail();
|
|
) in
|
|
let nl_nested = (fun env -> nl_nested_list env f opar l sep cpar) in
|
|
choice_right env simple nl_nested
|
|
|
|
|
|
let nested_list env f opar l sep cpar last_nl =
|
|
env.margin <- env.cmargin :: env.margin;
|
|
print env opar;
|
|
env.margin <- env.cmargin :: env.margin;
|
|
list_sep env f (fun env -> print env sep; newline env; spaces env) l;
|
|
if last_nl
|
|
then begin
|
|
print env sep;
|
|
newline env;
|
|
pop env;
|
|
spaces env;
|
|
print env cpar;
|
|
end
|
|
else begin
|
|
print env cpar;
|
|
pop env;
|
|
end;
|
|
pop env
|
|
|
|
let fun_params env f l =
|
|
let opar = "(" in
|
|
let sep = "," in
|
|
let cpar = ")" in
|
|
let simple = (
|
|
fun env ->
|
|
let line = env.line in
|
|
flat_list env f opar l sep cpar;
|
|
if line <> env.line then fail();
|
|
) in
|
|
let nl_nested = (fun env -> nested_list env f opar l sep cpar true) in
|
|
choice_right env simple nl_nested
|
|
|
|
(*****************************************************************************)
|
|
(* Parenthesis handling *)
|
|
(*****************************************************************************)
|
|
|
|
let paren prio env f =
|
|
let old_prio = env.priority in
|
|
env.priority <- prio;
|
|
if (prio >= old_prio) || (prio = -1)
|
|
then f env
|
|
else begin
|
|
print env "(";
|
|
f env;
|
|
print env ")";
|
|
end;
|
|
env.priority <- old_prio
|
|
|
|
(*****************************************************************************)
|
|
(* String helpers *)
|
|
(*****************************************************************************)
|
|
(* module PpString = struct *)
|
|
|
|
let char_is_space = function
|
|
| ' ' | '\t' | '\n' | '\r' -> true
|
|
| _ -> false
|
|
|
|
let is_space s i = char_is_space s.[i]
|
|
|
|
let rec is_only_space s i =
|
|
if i >= String.length s
|
|
then true
|
|
else is_space s i && is_only_space s (i+1)
|
|
|
|
let strip s =
|
|
let c1 = ref 0 in
|
|
let c2 = ref (String.length s - 1) in
|
|
while is_space s !c1 do
|
|
incr c1;
|
|
done;
|
|
while is_space s !c2 do
|
|
decr c2;
|
|
done;
|
|
let c2 = String.length s - 1 - !c2 in
|
|
String.sub s !c1 (String.length s - !c1 - c2)
|
|
|
|
let space = function
|
|
| ' ' | '\t' -> true
|
|
| _ -> false
|
|
|
|
let rec find_cut x start i =
|
|
if i < 20
|
|
then start
|
|
else if space x.[i]
|
|
then i
|
|
else find_cut x start (i-1)
|
|
|
|
let rec string quote sep env x =
|
|
choice_left env (
|
|
fun env ->
|
|
print env x
|
|
) (
|
|
fun env ->
|
|
let size = 80 - env.cmargin - String.length sep - 1 in
|
|
let size = find_cut x size size in
|
|
let s = String.sub x 0 size in
|
|
let rest = String.sub x size (String.length x - size) in
|
|
print env s;
|
|
print env quote;
|
|
print env sep;
|
|
newline env;
|
|
spaces env;
|
|
print env quote;
|
|
string quote sep env rest
|
|
)
|
|
|
|
let string quote sep env x =
|
|
if env.cmargin >= 20
|
|
then begin
|
|
print env quote;
|
|
print env x;
|
|
print env quote
|
|
end
|
|
else
|
|
nestc env (
|
|
fun env ->
|
|
print env quote;
|
|
string quote sep env x;
|
|
print env quote;
|
|
)
|
|
|
|
let first_char_escape env s =
|
|
if s = "" then 0 else
|
|
match s.[0] with
|
|
| 'A' .. 'Z' | 'a' .. 'z' | '&' | ' ' | '\n' | '<' | '>' -> 0
|
|
| _c ->
|
|
print env "{'";
|
|
let size = ref 1 in
|
|
while !size < String.length s && not (char_is_space s.[!size]) do incr size done;
|
|
let size = !size in
|
|
print env (String.sub s 0 size);
|
|
print env "'}";
|
|
if size < String.length s then print env " ";
|
|
size
|
|
|
|
let print_text env s =
|
|
let size = ref (String.length s - 1) in
|
|
while !size >= 0 && char_is_space s.[!size] do
|
|
decr size;
|
|
done;
|
|
let size = !size in
|
|
let buf = Buffer.create 80 in
|
|
let last_is_space = ref true in
|
|
nestc env (
|
|
fun env ->
|
|
let i = first_char_escape env s in
|
|
for i = i to size do
|
|
if Common2.is_space s.[i]
|
|
then
|
|
if !last_is_space
|
|
then ()
|
|
else begin
|
|
last_is_space := true;
|
|
print env (Buffer.contents buf);
|
|
Buffer.clear buf;
|
|
space_or_nl env;
|
|
end
|
|
else (last_is_space := false; Buffer.add_char buf s.[i])
|
|
done;
|
|
print env (Buffer.contents buf);
|
|
Buffer.clear buf;
|
|
)
|