manual, code example preprocessor : full conversion to compiler-libs (#1863)

* manual tools: use toploop directly in caml_tex2
* manual tool: improved error messages
* manual: always print errors and warnings
* fix nefarious interaction with GPR#1120
* move manual/tools/caml_tex2 to tools/caml_tex
* Basic text for caml-tex
master
Florian Angeletti 2018-07-25 10:38:08 +02:00 committed by GitHub
parent cc2b15ee3f
commit 4be6cafcc3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 370 additions and 161 deletions

1
.gitignore vendored
View File

@ -245,6 +245,7 @@ _ocamltestd
/tools/stripdebug.opt
/tools/make_opcodes
/tools/make_opcodes.ml
/tools/caml-tex
/utils/config.ml

View File

@ -152,6 +152,9 @@ Working version
- GPR#1831: move the local exceptions and exception cases to the main chapters.
(Florian Angeletti, review by Perry E. Metzger and Jeremy Yallop)
- GPR#1863: caml-tex2, move to compiler-libs
(Florian Angeletti, review by Sébastien Hinderer and Gabriel Scherer)
### Compiler distribution build system:
- GPR#1776: add -no-install-bytecode-programs and related configure options to

View File

@ -117,7 +117,7 @@ Latex extensions
### Caml environments
The tool `tools/caml-tex2` is used to generate the latex code for the examples
The tool `tools/caml-tex` is used to generate the latex code for the examples
in the introduction and language extension parts of the manual. It implements
two pseudo-environments: `caml_example` and `caml_eval`.
@ -145,10 +145,10 @@ otherwise an error would be raised.
The `verbatim` does not require a final `;;` and is intended to be
a lighter mode for code examples.
By default, `caml_tex2` raises an error and stops if the output of one
By default, `caml-tex` raises an error and stops if the output of one
the `caml_example` environment contains an unexpected error or warning.
If such an error or warning is, in fact, expected, it is necessary to
indicate the expected output status to `caml_tex2` by adding either
indicate the expected output status to `caml-tex` by adding either
an option to the `caml_example` environment:
```latex
\begin{caml_example}{toplevel}[error]

View File

@ -5,12 +5,11 @@ LD_PATH = "$(TOPDIR)/otherlibs/str:$(TOPDIR)/otherlibs/unix"
TOOLS = ../../tools
CAMLLATEX = $(SET_LD_PATH) \
$(OCAMLRUN) $(TOOLS)/caml-tex2 \
-caml "TERM=norepeat $(OCAML)" -n 80 -v false
$(OCAMLRUN) $(TOPDIR)/tools/caml-tex \
-repo-root $(TOPDIR) -n 80 -v false
TEXQUOTE = $(OCAMLRUN) $(TOOLS)/texquote2
TRANSF = $(SET_LD_PATH) $(OCAMLRUN) $(TOOLS)/transf
FILES = comp.tex top.tex runtime.tex native.tex lexyacc.tex intf-c.tex \
depend.tex profil.tex debugger.tex browser.tex ocamldoc.tex \
warnings-help.tex ocamlbuild.tex flambda.tex spacetime.tex \

View File

@ -5,8 +5,8 @@ LD_PATH = "$(TOPDIR)/otherlibs/str:$(TOPDIR)/otherlibs/unix"
TOOLS = ../../tools
CAMLLATEX = $(SET_LD_PATH) \
$(OCAMLRUN) $(TOOLS)/caml-tex2 \
-caml "TERM=norepeat $(OCAML)" -n 80 -v false
$(OCAMLRUN) $(TOPDIR)/tools/caml-tex \
-repo-root $(TOPDIR) -n 80 -v false
TEXQUOTE = $(OCAMLRUN) $(TOOLS)/texquote2
TRANSF = $(SET_LD_PATH) $(OCAMLRUN) $(TOOLS)/transf

View File

@ -5,8 +5,8 @@ LD_PATH = "$(TOPDIR)/otherlibs/str:$(TOPDIR)/otherlibs/unix"
TOOLS = ../../tools
CAMLLATEX = $(SET_LD_PATH) \
$(OCAMLRUN) $(TOOLS)/caml-tex2 \
-caml "TERM=norepeat $(OCAML)" -n 80 -v false
$(OCAMLRUN) $(TOPDIR)/tools/caml-tex \
-repo-root $(TOPDIR) -n 80 -v false
TEXQUOTE = $(OCAMLRUN) $(TOOLS)/texquote2
TRANSF = $(SET_LD_PATH) $(OCAMLRUN) $(TOOLS)/transf

View File

@ -699,7 +699,7 @@ The following idiom separates description and definition.
\begin{caml_example*}{toplevel}
class type ['a] iterator =
object method fold : ('b -> 'a -> 'b) -> 'b -> 'b end;;
class intlist l =
class intlist' l =
object (self : int #iterator)
method empty = (l = [])
method fold f accu = List.fold_left f accu l

View File

@ -6,7 +6,6 @@ htmlgen
htmlquote
latexscan.ml
dvi2txt
caml-tex2
*.dSYM
*.cm[io]
*.o

View File

@ -2,7 +2,7 @@ TOPDIR=../..
COMPFLAGS=-I $(OTOPDIR)/otherlibs/str -I $(OTOPDIR)/otherlibs/unix
include $(TOPDIR)/Makefile.tools
all: texquote2 transf caml-tex2
all: texquote2 transf
transf: transf.cmo htmltransf.cmo transfmain.cmo
@ -10,15 +10,9 @@ transf: transf.cmo htmltransf.cmo transfmain.cmo
transfmain.cmo: transf.cmo htmltransf.cmo
caml-tex2: caml_tex2.ml
$(OCAMLC) $(TOPDIR)/compilerlibs/ocamlcommon.cma -I $(TOPDIR)/parsing \
-o $@ str.cma unix.cma $<
texquote2: texquote2.ml
$(OCAMLC) -o $@ $<
%.cmo: %.ml
$(OCAMLC) -c $<
@ -33,4 +27,4 @@ texquote2: texquote2.ml
clean:
rm -f *.o *.cm? *.cmx?
rm -f transf.ml htmltransf.ml
rm -f texquote2 transf caml-tex2
rm -f texquote2 transf

View File

@ -0,0 +1 @@
redirections.ml

View File

@ -0,0 +1,19 @@
(* TEST
reference="${test_source_directory}/redirections.reference"
output="redirections.output"
script = "${ocamlrun} ${ocamlsrcdir}/tools/caml-tex \
-repo-root ${ocamlsrcdir} ${test_source_directory}/${test_file} -o ${output}"
* script with unix,str
** check-program-output
*)
\begin{caml_example}{toplevel}
[@@@warning "+A"];;
1 + 2. [@@expect error];;
let f x = () [@@expect warning 27];;
\end{caml_example}
\begin{caml_example}{toplevel}
Format.printf "Hello@.";
print_endline "world";;
\end{caml_example}

View File

@ -0,0 +1,30 @@
(* TEST
reference="${test_source_directory}/redirections.reference"
output="redirections.output"
script = "${ocamlrun} ${ocamlsrcdir}/tools/caml-tex \
-repo-root ${ocamlsrcdir} ${test_source_directory}/${test_file} -o ${output}"
* script with unix,str
** check-program-output
*)
\camlexample{toplevel}
\caml\camlinput\?[@@@warning "+A"];;
\endcamlinput\endcaml
\caml\camlinput\?1 + \<2.\> ;;
\endcamlinput\camlerror\:Error: This expression has type float but an expression was expected of type
\: int
\endcamlerror\endcaml
\caml\camlinput\?let f \<x\> = () ;;
\endcamlinput\camlwarn\:Warning 27: unused variable x.
\:val f : \textquotesingle\-a -> unit = <fun>
\endcamlwarn\endcaml
\endcamlexample
\camlexample{toplevel}
\caml\camlinput\?Format.printf "Hello@.";
\?print_endline "world";;
\endcamlinput\camloutput\:Hello
\:world
\:- : unit = ()
\endcamloutput\endcaml
\endcamlexample

View File

@ -398,6 +398,26 @@ install::
"$(INSTALL_BINDIR)/"
endif
CAMLTEX= ../compilerlibs/ocamlcommon.cma \
../compilerlibs/ocamlbytecomp.cma \
../compilerlibs/ocamltoplevel.cma \
../otherlibs/str/str.cma \
../otherlibs/$(UNIXLIB)/unix.cma \
caml_tex.ml
#Scan latex files, and run ocaml code examples
caml-tex: INCLUDES+= -I ../otherlibs/str -I ../otherlibs/$(UNIXLIB)
caml-tex: $(CAMLTEX)
../runtime/ocamlrun ../ocamlc -nostdlib -I ../stdlib $(LINKFLAGS) \
-linkall -o $@ $(CAMLTEX)
# we need str and unix which depend on the bytecode version of other tools
# thus we delay building caml-tex to the opt.opt stage
opt.opt:caml-tex
clean::
rm -f -- caml-tex caml_tex.cm?
# Common stuff
.SUFFIXES:

View File

@ -1,7 +1,22 @@
(* $Id$ *)
(**************************************************************************)
(* *)
(* OCaml *)
(* *)
(* Xavier Leroy, projet Gallium, INRIA Paris *)
(* Jacques Garrigue, Nagoya University *)
(* Florian Angeletti *)
(* *)
(* Copyright 2018 Institut National de Recherche en Informatique et *)
(* en Automatique. *)
(* *)
(* All rights reserved. This file is distributed under the terms of *)
(* the GNU Lesser General Public License version 2.1, with the *)
(* special exception on linking described in the file LICENSE. *)
(* *)
(**************************************************************************)
[@@@warning "a-40-6"]
open StdLabels
open Printf
open Str
let camlbegin = "\\caml"
@ -12,17 +27,18 @@ let camlbunderline = "\\<"
let camleunderline = "\\>"
let start newline out s args =
Printf.fprintf out "%s%s" camlbegin s;
List.iter (Printf.fprintf out "{%s}") args;
if newline then Printf.fprintf out "\n"
Format.fprintf out "%s%s" camlbegin s;
List.iter (Format.fprintf out "{%s}") args;
if newline then Format.fprintf out "\n"
let stop newline out s =
Printf.fprintf out "%s%s" camlend s;
if newline then Printf.fprintf out "\n"
Format.fprintf out "%s%s" camlend s;
if newline then Format.fprintf out "\n"
let code_env ?(newline=true) env out s =
Printf.fprintf out "%a%s\n%a"
(fun ppf env -> start false ppf env []) env s (stop newline) env
let sep = if s.[String.length s - 1] = '\n' then "" else "\n" in
Format.fprintf out "%a%s%s%a"
(fun ppf env -> start false ppf env []) env s sep (stop newline) env
let main = "example"
type example_mode = Toplevel | Verbatim | Signature
@ -37,23 +53,12 @@ let error ="error"
let warning ="warn"
let phrase_env = ""
let camllight = ref "TERM=norepeat ocaml"
let verbose = ref true
let linelen = ref 72
let outfile = ref ""
let cut_at_blanks = ref false
let files = ref []
let _ =
Arg.parse ["-n", Arg.Int (fun n -> linelen := n), "line length";
"-o", Arg.String (fun s -> outfile := s), "output";
"-caml", Arg.String (fun s -> camllight := s), "toplevel";
"-w", Arg.Set cut_at_blanks, "cut at blanks";
"-v", Arg.Bool (fun b -> verbose := b ), "output result on stderr"
]
(fun s -> files := s :: !files)
"caml-tex2: "
let repo_root = ref ""
let (~!) =
let memo = ref [] in
@ -64,6 +69,162 @@ let (~!) =
memo := (key, data) :: !memo;
data
exception Phrase_parsing of string
module Toplevel = struct
(** Initialize the toplevel loop, redirect stdout and stderr,
capture warnings and error messages *)
type output =
{
error : string; (** error message text *)
warnings : string list; (** warning messages text *)
values : string; (** toplevel output *)
stdout : string; (** output printed on the toplevel stdout *)
underlined : (int * int) list
(** locations to underline in input phrases *)
}
let buffer_fmt () =
let b = Buffer.create 30 in b, Format.formatter_of_buffer b
let error_fmt = buffer_fmt ()
let warning_fmt = buffer_fmt ()
let out_fmt = buffer_fmt ()
let flush_fmt (b,fmt) =
Format.pp_print_flush fmt ();
let r = Buffer.contents b in
Buffer.reset b;
r
(** Redirect the stdout *)
let stdout_out, stdout_in = Unix.pipe ~cloexec:true ()
let () = Unix.dup2 stdout_in Unix.stdout
let self_error_fmt = Format.formatter_of_out_channel stderr
let eprintf = Format.eprintf
let read_stdout =
let size = 50 in
let b = Bytes.create size in
let buffer = Buffer.create 100 in
let rec read_toplevel_stdout () =
match Unix.select[stdout_out][][] 0. with
| [a], _, _ ->
let n = Unix.read stdout_out b 0 size in
Buffer.add_subbytes buffer b 0 n;
if n = size then read_toplevel_stdout ()
| _ -> ()
in
fun () ->
let () = flush stdout; read_toplevel_stdout () in
let r = Buffer.contents buffer in
Buffer.reset buffer;
r
(** Store character intervals directly *)
let locs = ref []
let register_loc (loc : Location.t) =
let startchar = loc.loc_start.pos_cnum in
let endchar = loc.loc_end.pos_cnum in
if startchar >= 0 then
locs := (startchar, endchar) :: !locs
(** Capture warnings and keep them in a list *)
let warnings = ref []
let print_warning loc _ppf w =
if Warnings.report w <> `Inactive then register_loc loc;
Location.default_warning_printer loc (snd warning_fmt) w;
let w = flush_fmt warning_fmt in
warnings := w :: !warnings
let fatal ic oc fmt =
Format.kfprintf
(fun ppf -> Format.fprintf ppf "@]@."; close_in ic; close_out oc; exit 1)
self_error_fmt ("@[<hov 2> Error " ^^ fmt)
let init () =
Location.printer := (fun _ _ -> ());
Location.warning_printer := print_warning;
Clflags.color := Some Misc.Color.Never;
Clflags.no_std_include := true;
Compenv.last_include_dirs := [Filename.concat !repo_root "stdlib"];
Location.error_reporter :=
(fun _ e -> register_loc e.loc;
Location.default_error_reporter (snd error_fmt) e);
Compmisc.init_path false;
try
Toploop.initialize_toplevel_env ();
Sys.interactive := false
with _ ->
(eprintf "Invalid repo root: %s?%!" !repo_root; exit 2)
let exec (_,ppf) p =
try
ignore @@ Toploop.execute_phrase true ppf p
with exn ->
let bt = Printexc.get_raw_backtrace () in
begin try Location.report_exception (snd error_fmt) exn
with _ ->
eprintf "Uncaught exception: %s\n%s\n"
(Printexc.to_string exn)
(Printexc.raw_backtrace_to_string bt)
end
let parse fname mode s =
let lex = Lexing.from_string s in
Location.init lex fname;
Location.input_name := fname;
Location.input_lexbuf := Some lex;
try
match mode with
| Toplevel -> Parse.toplevel_phrase lex
| Verbatim -> Ptop_def (Parse.implementation lex)
| Signature ->
let sign = Parse.interface lex in
let name = Location.mknoloc "wrap" in
let str =
Ast_helper.[Str.modtype @@ Mtd.mk ~typ:(Mty.signature sign) name] in
Ptop_def str
with Syntaxerr.Error _ -> raise (Phrase_parsing s)
let take x = let r = !x in x := []; r
let read_output () =
let warnings = take warnings in
let error = flush_fmt error_fmt in
let values =
replace_first ~!{|^#\( *\*\)* *|} "" @@ flush_fmt out_fmt in
(* the inner ( *\* )* group is here to clean the starting "*"
introduced for multiline comments *)
let underlined = take locs in
let stdout = read_stdout () in
{ values; warnings; error; stdout; underlined }
(** exec and ignore all output from the toplevel *)
let eval b =
let s = Buffer.contents b in
let ast = Parse.toplevel_phrase (Lexing.from_string s) in
exec out_fmt ast;
ignore (read_output());
Buffer.reset b
end
let () =
Arg.parse ["-n", Arg.Int (fun n -> linelen := n), "line length";
"-o", Arg.String (fun s -> outfile := s), "output";
"-repo-root", Arg.String ((:=) repo_root ), "repo root";
"-w", Arg.Set cut_at_blanks, "cut at blanks";
"-v", Arg.Bool (fun b -> verbose := b ), "output result on stderr"
]
(fun s -> files := s :: !files)
"caml-tex: ";
Toplevel.init ()
(** The Output module deals with the analysis and classification
of the interpreter output and the parsing of status-related options
or annotations for the caml_example environment *)
@ -84,15 +245,15 @@ module Output = struct
(** Pretty printer for status *)
let pp_status ppf = function
| Error -> Printf.fprintf ppf "error"
| Ok -> Printf.fprintf ppf "ok"
| Warning n -> Printf.fprintf ppf "warning %d" n
| Error -> Format.fprintf ppf "error"
| Ok -> Format.fprintf ppf "ok"
| Warning n -> Format.fprintf ppf "warning %d" n
(** Pretty printer for status preceded with an undefined determinant *)
let pp_a_status ppf = function
| Error -> Printf.fprintf ppf "an error"
| Ok -> Printf.fprintf ppf "an ok"
| Warning n -> Printf.fprintf ppf "a warning %d" n
| Error -> Format.fprintf ppf "an error"
| Ok -> Format.fprintf ppf "an ok"
| Warning n -> Format.fprintf ppf "a warning %d" n
(** {1 Related latex environment } *)
let env = function
@ -103,17 +264,23 @@ module Output = struct
(** {1 Exceptions } *)
exception Parsing_error of kind * string
type source = { file:string; lines:int * int; phrase:string; output:string }
type unexpected_report = {source:source; expected:status; got:status}
type source =
{
file : string;
lines : int * int;
phrase : string;
output : string
}
type unexpected_report = {source : source; expected : status; got : status}
exception Unexpected_status of unexpected_report
let print_source ppf {file; lines = (start, stop); phrase; output} =
Printf.fprintf ppf "%s, lines %d to %d:\n\"\n%s\n\"\n\"\n%s\n\"."
Format.fprintf ppf "%s, lines %d to %d:\n\"\n%s\n\"\n\"\n%s\n\"."
file start stop phrase output
let print_unexpected {source; expected; got} =
if expected = Ok then
Printf.eprintf
Toplevel.eprintf
"Error when evaluating a caml_example environment in %a\n\
Unexpected %a status.\n\
If %a status was expected, add an [@@expect %a] annotation.\n"
@ -122,7 +289,7 @@ module Output = struct
pp_a_status got
pp_status got
else
Printf.eprintf
Toplevel.eprintf
"Error when evaluating a guarded caml_example environment in %a\n\
Unexpected %a status, %a status was expected.\n\
If %a status was in fact expected, change the status annotation to \
@ -137,27 +304,30 @@ module Output = struct
let print_parsing_error k s =
match k with
| Option ->
Printf.eprintf
Toplevel.eprintf
"Unknown caml_example option: [%s].\n\
Supported options are \"ok\",\"error\", or \"warning=n\" (with n \
a warning number).\n" s
| Annotation ->
Printf.eprintf
Toplevel.eprintf
"Unknown caml_example phrase annotation: [@@expect %s].\n\
Supported annotations are [@@expect ok], [@@expect error],\n\
and [@@expect warning n] (with n a warning number).\n" s
(** {1 Output analysis} *)
let catch_error s =
if string_match ~!{|Error:|} s 0 then Some Error else None
let catch_error = function
| "" -> None
| _ -> Some Error
let catch_warning s =
if string_match ~!{|Warning \([0-9]+\):|} s 0 then
Some (Warning (int_of_string @@ matched_group 1 s))
else
None
let catch_warning =
function
| [] -> None
| s :: _ when string_match ~!{|Warning \([0-9]+\):|} s 0 ->
Some (Warning (int_of_string @@ matched_group 1 s))
| _ -> None
let status s = match catch_warning s, catch_error s with
let status ws es =
match catch_warning ws, catch_error es with
| Some w, _ -> w
| None, Some e -> e
| None, None -> Ok
@ -207,14 +377,22 @@ module Text_transform = struct
| Underline
| Ellipsis
type t = { kind : kind; start : int; stop : int}
exception Intersection of
{line:int; file:string; left:kind; stop:int; start:int; right:kind}
{
line : int;
file : string;
left : t;
right : t;
}
let pp ppf = function
| Underline -> Format.fprintf ppf "underline"
| Ellipsis -> Format.fprintf ppf "ellipsis"
type t = { kind:kind; start:int; stop:int}
let underline start stop = { kind = Underline; start; stop}
let ellipsis start stop = { kind = Ellipsis; start; stop }
let escape_specials s =
let s1 = global_replace ~!"\\\\" "\\\\\\\\" s in
let s2 = global_replace ~!"'" "\\\\textquotesingle\\\\-" s1 in
@ -241,7 +419,7 @@ module Text_transform = struct
transform in a list of transforms *)
type partition = U of t * t list | E of t
let check_partition line file l =
let init = Ellipsis, 0 in
let init = ellipsis 0 0 in
let rec partition = function
| [] -> []
| {kind=Underline; _ } as t :: q -> underline t [] q
@ -253,21 +431,27 @@ module Text_transform = struct
if t.stop < u.stop then underline u (t::n) q
else end_underline u n (t::q)
and end_underline u n l = U(u,List.rev n) :: partition l in
let check_elt (left,stop) t =
if t.start < stop then
raise (Intersection{line;file;left;stop;start=t.start;right=t.kind})
let check_elt last t =
if t.start < last.stop then
raise (Intersection {line;file; left = last; right = t})
else
(t.kind,t.stop) in
t in
let check acc = function
| E t -> check_elt acc t
| U(u,n) ->
let _ = check_elt acc u in
let _ = List.fold_left ~f:check_elt ~init n in
u.kind, u.stop in
u in
List.fold_left ~f:check ~init (partition l)
|> ignore
let apply ts file line s =
(* remove duplicated transforms that can appear due to
duplicated parse tree elements. For instance,
[let f : (_ [@ellipsis] = ()] is transformed to
[let f: (_ [@ellipsis]) = (():(_ [@ellipsis])] with the same location
for the two ellipses. *)
let ts = List.sort_uniq compare ts in
let ts = List.sort (fun x y -> compare x.start y.start) ts in
check_partition line file ts;
let last, underline, ls =
@ -285,38 +469,6 @@ module Text_transform = struct
end
let caml_input, caml_output =
let cmd = !camllight ^ " 2>&1" in
try Unix.open_process cmd with _ -> failwith "Cannot start toplevel"
let () =
at_exit (fun () -> ignore (Unix.close_process (caml_input, caml_output)));
ignore (input_line caml_input);
ignore (input_line caml_input)
let read_output () =
let input = ref (input_line caml_input) in
input := replace_first ~!{|^#\( *\*\)* *|} "" !input;
(* the inner ( *\* )* group is here to clean the starting "*"
introduced for multiline comments *)
let underline =
if string_match ~!"Characters *\\([0-9]+\\)-\\([0-9]+\\):$" !input 0
then
let start = int_of_string (matched_group 1 !input)
and stop = int_of_string (matched_group 2 !input) in
input := input_line caml_input;
Text_transform.[{kind=Underline; start; stop}]
else []
in
let output = Buffer.create 256 in
let first_line = ref true in
while not (string_match ~!".*\"end_of_input\"$" !input 0) do
if !verbose then prerr_endline !input;
if not !first_line then Buffer.add_char output '\n' else first_line:=false;
Buffer.add_string output !input;
input := input_line caml_input;
done;
Buffer.contents output, underline
exception Missing_double_semicolon of string * int
exception Missing_mode of string * int
@ -325,7 +477,6 @@ type incompatibility =
| Signature_with_visible_answer of string * int
exception Incompatible_options of incompatibility
exception Phrase_parsing of string
module Ellipsis = struct
(** This module implements the extraction of ellipsis locations
@ -334,11 +485,11 @@ module Ellipsis = struct
An ellipsis is either an [[@ellipsis]] attribute, or a pair
of [[@@@ellipsis.start]...[@@@ellipsis.stop]] attributes. *)
exception Unmatched_ellipsis of {kind:string; start:int; stop:int}
exception Unmatched_ellipsis of {kind : string; start : int; stop : int}
(** raised when an [[@@@ellipsis.start]] or [[@@@ellipsis.stop]] is
not paired with another ellipsis attribute *)
exception Nested_ellipses of {first:int ; second:int }
exception Nested_ellipses of {first : int ; second : int}
(** raised by [[@@@ellipsis.start][@@@ellipsis.start]] *)
let extract f x =
@ -385,28 +536,13 @@ module Ellipsis = struct
);
!transforms
let find fname mode s =
let lex = Lexing.from_string s in
Location.init lex fname;
Location.input_name := fname;
Location.input_lexbuf := Some lex;
try
match mode with
| Toplevel -> begin
match Parse.toplevel_phrase lex with
| Ptop_dir _ -> []
| Ptop_def str -> extract (fun it -> it.structure it) str
end
| Verbatim ->
extract (fun it -> it.structure it) (Parse.implementation lex)
| Signature ->
extract (fun it -> it.signature it) (Parse.interface lex)
with Syntaxerr.Error _ -> raise (Phrase_parsing s)
let find = function
| Parsetree.Ptop_def ast -> extract (fun it -> it.structure it) ast
| Ptop_dir (_,_) -> []
end
let process_file file =
prerr_endline ("Processing " ^ file);
let ic = try open_in file with _ -> failwith "Cannot read input file" in
let phrase_start = ref 1 and phrase_stop = ref 1 in
let incr_phrase_start () =
@ -421,10 +557,8 @@ let process_file file =
open_out_gen [Open_wronly; Open_creat; Open_append; Open_text]
0x666 !outfile
with _ -> failwith "Cannot open output file" in
let fatal fmt =
Format.kfprintf
(fun ppf -> Format.fprintf ppf "@]@."; close_in ic; close_out oc; exit 1)
Format.err_formatter ("@[<hov 2> Error " ^^ fmt) in
let tex_fmt = Format.formatter_of_out_channel oc in
let fatal x = Toplevel.fatal ic oc x in
let re_spaces = "[ \t]*" in
let re_start = ~!(
{|\\begin{caml_example\(\*?\)}|} ^ re_spaces
@ -454,7 +588,7 @@ let process_file file =
| Toplevel -> true in
let global_expected = try Output.expected @@ matched_group 4 !input
with Not_found -> Output.Ok in
start true oc main [string_of_mode mode];
start true tex_fmt main [string_of_mode mode];
let first = ref true in
let read_phrase () =
let phrase = Buffer.create 256 in
@ -484,7 +618,8 @@ let process_file file =
)
else begin
decr phrase_stop;
let last_input = if implicit_stop then "" else matched_group 1 input in
let last_input =
if implicit_stop then "" else matched_group 1 input in
let expected =
if string_match ~!{|\(.*\)\[@@expect \(.*\)\]|} last_input 0 then
( Buffer.add_string phrase (matched_group 1 last_input);
@ -499,16 +634,13 @@ let process_file file =
in
try while true do
let implicit_stop, phrase, expected = read_phrase () in
let ellipses = Ellipsis.find file mode phrase in
if mode = Signature then fprintf caml_output "module type Wrap = sig\n";
fprintf caml_output "%s%s%s" phrase
(if mode = Signature then "\nend" else "")
(if implicit_stop then ";;\n" else "\n");
flush caml_output;
output_string caml_output "\"end_of_input\";;\n";
flush caml_output;
let output, underline = read_output () in
let status = Output.status output in
let ast = Toplevel.parse file mode phrase in
let ellipses = Ellipsis.find ast in
let () = Toplevel.(exec out_fmt) ast in
let out = Toplevel.read_output () in
let error_msgs = String.concat "" (out.warnings @ [out.error]) in
let output = String.concat "" [error_msgs; out.stdout; out.values] in
let status = Output.status out.warnings out.error in
if status <> expected then (
let source = Output.{
file;
@ -520,39 +652,48 @@ let process_file file =
{Output.got=status; expected; source} ) )
else ( incr phrase_stop; phrase_start := !phrase_stop );
let phrase =
let underline =
List.map (fun (x,y) -> Text_transform.underline x y)
out.underlined in
Text_transform.apply (underline @ ellipses)
file !phrase_stop phrase in
(* Special characters may also appear in output strings -Didier *)
let output = Text_transform.escape_specials output in
let phrase = global_replace ~!{|^\(.\)|} camlin phrase
and output = global_replace ~!{|^\(.\)|} camlout output in
start false oc phrase_env [];
code_env ~newline:omit_answer input_env oc phrase;
if not omit_answer then
code_env ~newline:false (Output.env status) oc output;
stop true oc phrase_env;
let final_output =
if omit_answer && String.length error_msgs > 0 then
global_replace ~!{|^\(.\)|} camlout error_msgs
else if omit_answer then ""
else output in
start false tex_fmt phrase_env [];
code_env ~newline:omit_answer input_env tex_fmt phrase;
if String.length final_output > 0 then
code_env ~newline:false (Output.env status) tex_fmt final_output;
stop true tex_fmt phrase_env;
flush oc;
first := false;
if implicit_stop then raise End_of_file
done
with End_of_file -> phrase_start:= !phrase_stop; stop true oc main
with End_of_file -> phrase_start:= !phrase_stop; stop true tex_fmt main
end
else if string_match ~!"\\\\begin{caml_eval}[ \t]*$" !input 0
then begin
let eval_buffer = Buffer.create 256 in
while input := input_line ic;
not (string_match ~!"\\\\end{caml_eval}[ \t]*$" !input 0)
do
fprintf caml_output "%s\n" !input;
Buffer.add_string eval_buffer !input;
Buffer.add_char eval_buffer '\n';
if string_match ~!".*;;[ \t]*$" !input 0 then begin
flush caml_output;
output_string caml_output "\"end_of_input\";;\n";
flush caml_output;
ignore (read_output ())
Toplevel.eval eval_buffer
end
done
done;
if Buffer.length eval_buffer > 0 then
( Buffer.add_string eval_buffer ";;\n"; Toplevel.eval eval_buffer )
end else begin
fprintf oc "%s\n" !input;
flush oc
Format.fprintf tex_fmt "%s\n" !input;
Format.pp_print_flush tex_fmt ()
end
done with
| End_of_file -> close_in ic; close_out oc
@ -565,28 +706,30 @@ let process_file file =
| Missing_double_semicolon (file, line_number) ->
fatal
"when evaluating a caml_example environment in %s:@;\
missing \";;\" at line %d@]@." file (line_number-2)
missing \";;\" at line %d" file (line_number-2)
| Missing_mode (file, line_number) ->
fatal "when parsing a caml_example environment in %s:@;\
missing mode argument at line %d,@ \
available modes {toplevel,verbatim}@]@."
available modes {toplevel,verbatim}"
file (line_number-2)
| Incompatible_options Signature_with_visible_answer (file, line_number) ->
fatal
"when parsing a caml_example environment in@ \
%s, line %d:@,\
the signature mode is only compatible with \"caml_example*\"@ \
Hint: did you forget to add \"*\"?@]@."
Hint: did you forget to add \"*\"?"
file (line_number-2);
| Text_transform.Intersection {line;file;left;stop;start;right} ->
| Text_transform.Intersection {line;file;left;right} ->
fatal
"when evaluating a caml_example environment in %s, line %d:@ \
Textual transforms must be well-separated.@ The \"%a\" transform \
ended at %d,@ after the start at %d of another \"%a\" transform.@ \
Hind: did you try to elide a code fragment which raised a warning?\
@]@."
spanned the interval %d-%d,@ \
intersecting with another \"%a\" transform @ \
on the %d-%d interval.@ \
Hind: did you try to elide a code fragment which raised a warning?"
file (line-2)
Text_transform.pp left stop start Text_transform.pp right
Text_transform.pp left.kind left.start left.stop
Text_transform.pp right.kind right.start right.stop
| Ellipsis.Unmatched_ellipsis {kind;start;stop} ->
fatal "when evaluating a caml_example environment,@ \
the %s mark at position %d-%d was unmatched"