Skip to content

Commit b03bdfe

Browse files
committed
feat!: Implement pre-compilation of exercises and graders
to cmi, cma and js. Includes changes to the toploop to handle the dynamic loading.
1 parent 0816f95 commit b03bdfe

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+952
-858
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ The Inconsolata font is released under the Open Font License.
5353
See [http://www.levien.com/type/myfonts/inconsolata.html](http://www.levien.com/type/myfonts/inconsolata.html).
5454

5555
The Biolinum font is licensed under the GNU General Public License with
56-
a the 'Font-Exception'.
56+
a 'Font-Exception'.
5757
See [http://www.linuxlibertine.org](http://www.linuxlibertine.org).
5858

5959
The public instance of Learn OCaml uses the Fontin font instead of
@@ -78,9 +78,9 @@ It was written by OCamlPro from 2015 to 2018.
7878

7979
The current main contributors are Érik Martin-Dorel, Yann Régis-Gianas, and Louis Gesbert.
8080

81-
The initial authors were Benjamin Canou, Çağdaş Bozman, and Grégoire Henry.
81+
The initial authors were Benjamin Canou, Çağdaş Bozman, Grégoire Henry, and Louis Gesbert.
8282

83-
It builds on the previous experience of Try OCaml, by Çağdaş Bozman, and Fabrice Le Fessant.
83+
It builds on the previous experience of Try OCaml, by Çağdaş Bozman and Fabrice Le Fessant.
8484

8585
We heavily use js_of_ocaml, so thanks to the Ocsigen team.
8686

dune

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
)
88

99
(env
10-
(release (flags -safe-string -w +a-4-42-44-45-48-3-58)
10+
(release (flags -safe-string -w +a-4-42-44-45-48-3-58-32-33)
1111
(ocamlc_flags)
1212
(ocamlopt_flags))
1313
)

dune-project

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
(lang dune 2.3)
1+
(lang dune 2.4)
22
(name learn-ocaml)
33
(version 0.16.0)
44
(allow_approximate_merlin)

src/app/learnocaml_description_main.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ let () =
7171
init_tabs ();
7272
exercise_fetch >>= fun (ex_meta, exo, _deadline) ->
7373
(* display exercise questions and prelude *)
74-
setup_tab_text_prelude_pane Learnocaml_exercise.(decipher File.prelude exo);
74+
setup_tab_text_prelude_pane Learnocaml_exercise.(decipher File.prelude_ml exo);
7575
let text_iframe = Dom_html.createIframe Dom_html.document in
7676
Manip.replaceChildren title_container
7777
Tyxml_js.Html5.[ h1 [ txt ex_meta.title] ];

src/app/learnocaml_exercise_main.ml

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -123,16 +123,21 @@ let () =
123123
in
124124
let after_init top =
125125
exercise_fetch >>= fun (_meta, exo, _deadline) ->
126-
begin match Learnocaml_exercise.(decipher File.prelude exo) with
127-
| "" -> Lwt.return true
128-
| prelude ->
129-
Learnocaml_toplevel.load ~print_outcome:true top
130-
~message: [%i"loading the prelude..."]
131-
prelude
132-
end >>= fun r1 ->
133-
Learnocaml_toplevel.load ~print_outcome:false top
134-
(Learnocaml_exercise.(decipher File.prepare exo)) >>= fun r2 ->
135-
if not r1 || not r2 then failwith [%i"error in prelude"] ;
126+
let exercise_js = Learnocaml_exercise.(decipher File.exercise_js exo) in
127+
Learnocaml_toplevel.load_cmi_from_string top
128+
Learnocaml_exercise.(decipher File.prelude_cmi exo) >>= fun _ ->
129+
Learnocaml_toplevel.load_cmi_from_string top
130+
Learnocaml_exercise.(decipher File.prepare_cmi exo) >>= fun _ ->
131+
Learnocaml_toplevel.load_js ~print_outcome:false top
132+
~message: [%i"loading the prelude..."]
133+
exercise_js
134+
>>= fun r ->
135+
if not r then Lwt.fail_with [%i"error in prelude"] else
136+
Learnocaml_toplevel.load top "open! Prelude ;;" >>= fun r ->
137+
if not r then Lwt.fail_with [%i"error in prelude"] else
138+
Learnocaml_toplevel.load top "open! Prepare ;;" >>= fun r ->
139+
if not r then Lwt.fail_with [%i"error in prelude"] else
140+
(* TODO: maybe remove Prelude, Prepare modules from the env ? *)
136141
Learnocaml_toplevel.set_checking_environment top >>= fun () ->
137142
Lwt.return () in
138143
let toplevel_launch =
@@ -189,7 +194,7 @@ let () =
189194
EB.eval top select_tab;
190195
let typecheck = typecheck top ace editor in
191196
(*------------- prelude -----------------*)
192-
setup_prelude_pane ace Learnocaml_exercise.(decipher File.prelude exo);
197+
setup_prelude_pane ace Learnocaml_exercise.(decipher File.prelude_ml exo);
193198
Js.Opt.case
194199
(text_iframe##.contentDocument)
195200
(fun () -> failwith "cannot edit iframe document")

src/grader/dune

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,41 @@
1313
(action (run odoc compile --package learn-ocaml %{deps} -o %{targets}))
1414
)
1515

16+
;; needs to be a separate lib because the module is shared between evaluator
17+
;; parts (Grading) and dynamic parts (Test_lib)
1618
(library
17-
(name testing)
19+
(name introspection_intf)
20+
(wrapped false)
21+
(modules introspection_intf)
22+
(modules_without_implementation introspection_intf)
23+
(libraries learnocaml_report ty))
24+
25+
;; dynamic part, on which Prelude/Prepare/Test_lib etc. depend
26+
(library
27+
(name learnocaml_callback)
28+
(wrapped false)
29+
(modules learnocaml_callback)
30+
(modules_without_implementation learnocaml_callback)
31+
;; hack: learnocaml_callback actually does have an implementation, but it is inserted
32+
;; into the toplevel later on, through registered callbacks. Defining this lib
33+
;; ensures the compilation of `learnocaml_callback.cmi`
34+
(libraries compiler-libs learnocaml_report introspection_intf))
35+
36+
;; dynamic part, on which Test_lib depends
37+
(library
38+
(name pre_test)
39+
(wrapped false)
40+
(modules pre_test)
41+
(modules_without_implementation pre_test)
42+
;; hack: pre_test actually does have an implementation, but it is dynamically
43+
;; generated and injected in the environment during grading. We are interested
44+
;; in pre_test.cmi to compile test_lib.cmo, then test_lib.cmo should only be
45+
;; loaded in the specific grading toplevel env.
46+
(libraries compiler-libs learnocaml_report introspection_intf))
47+
48+
;; dynamic (but pre-compiled) part
49+
(library
50+
(name testing_dyn)
1851
(wrapped false)
1952
(modes byte)
2053
(library_flags :standard -linkall)
@@ -24,18 +57,23 @@
2457
learnocaml_ppx_metaquot_lib
2558
ocplib-json-typed
2659
learnocaml_report
27-
learnocaml_repository)
28-
(modules Introspection_intf
29-
Introspection
30-
Test_lib
31-
Mutation_test)
32-
(modules_without_implementation Introspection_intf)
60+
learnocaml_repository
61+
introspection_intf
62+
;; dynamic dependencies
63+
learnocaml_callback
64+
pre_test
65+
)
66+
(modules Test_lib)
3367
(preprocess (pps learnocaml_ppx_metaquot))
3468
)
69+
(rule
70+
(target testing_dyn.js)
71+
(deps testing_dyn.cma)
72+
(action (run js_of_ocaml %{deps} --wrap-with dynload --pretty)))
3573

3674
(rule
3775
(targets test_lib.odoc)
38-
(deps .testing.objs/byte/test_lib.cmti)
76+
(deps .testing_dyn.objs/byte/test_lib.cmti)
3977
(action (run odoc compile --package learn-ocaml %{deps} -o %{targets}))
4078
)
4179

@@ -138,39 +176,47 @@
138176
)
139177

140178
(rule
141-
(targets embedded_grading_cmis.ml)
142-
(deps (:compiler-cmis
143-
%{ocaml-config:standard_library}/compiler-libs/longident.cmi
144-
%{ocaml-config:standard_library}/compiler-libs/asttypes.cmi
145-
%{ocaml-config:standard_library}/compiler-libs/ast_helper.cmi
146-
%{ocaml-config:standard_library}/compiler-libs/ast_mapper.cmi
147-
%{ocaml-config:standard_library}/compiler-libs/parsetree.cmi
148-
%{ocaml-config:standard_library}/compiler-libs/location.cmi
149-
%{ocaml-config:standard_library}/compiler-libs/parse.cmi
150-
%{ocaml-config:standard_library}/compiler-libs/pprintast.cmi)
151-
(:generated-cmis
152-
../ppx-metaquot/.ty.objs/byte/ty.cmi
153-
../ppx-metaquot/.fun_ty.objs/byte/fun_ty.cmi
154-
.testing.objs/byte/introspection_intf.cmi
155-
.learnocaml_report.objs/byte/learnocaml_report.cmi
156-
.testing.objs/byte/test_lib.cmi
157-
.testing.objs/byte/mutation_test.cmi))
179+
(targets embedded_grading_lib.ml)
180+
(deps
181+
.learnocaml_callback.objs/byte/learnocaml_callback.cmi
182+
;; .pre_test.objs/byte/pre_test.cmi -- only test_lib should be needed
183+
.testing_dyn.objs/byte/test_lib.cmi
184+
testing_dyn.cma
185+
testing_dyn.js)
158186
(action (with-stdout-to %{targets}
159-
(run ocp-ocamlres -format ocamlres %{compiler-cmis} %{generated-cmis})))
187+
(run ocp-ocamlres -format ocamlres %{deps})))
188+
)
189+
190+
;; cmis that are needed to precompile the graders for exercises
191+
(install
192+
(section share)
193+
(package learn-ocaml)
194+
(files
195+
(../ppx-metaquot/.ty.objs/byte/ty.cmi as grading_cmis/ty.cmi)
196+
(../ppx-metaquot/.fun_ty.objs/byte/fun_ty.cmi as grading_cmis/fun_ty.cmi)
197+
(.introspection_intf.objs/byte/introspection_intf.cmi as grading_cmis/introspection_intf.cmi)
198+
(.pre_test.objs/byte/pre_test.cmi as grading_cmis/pre_test.cmi)
199+
(.learnocaml_report.objs/byte/learnocaml_report.cmi as grading_cmis/learnocaml_report.cmi)
200+
(.learnocaml_callback.objs/byte/learnocaml_callback.cmi as grading_cmis/learnocaml_callback.cmi)
201+
(.testing_dyn.objs/byte/test_lib.cmi as grading_cmis/test_lib.cmi))
160202
)
161203

204+
162205
(library
163206
(name grading)
164207
(wrapped false)
165208
(modes byte)
166209
(library_flags :standard -linkall)
167-
(libraries testing
168-
learnocaml_ppx_metaquot
210+
(libraries learnocaml_ppx_metaquot
169211
ocplib-ocamlres.runtime
212+
toploop
213+
introspection_intf
170214
embedded_cmis
171215
ocplib_i18n
172-
learnocaml_report)
173-
(modules Embedded_grading_cmis
216+
learnocaml_report
217+
learnocaml_repository)
218+
(modules Introspection
219+
Embedded_grading_lib
174220
Grading)
175221
(preprocess (per_module ((pps ppx_ocplib_i18n learnocaml_ppx_metaquot) Grading)))
176222
)

src/grader/grader_cli.ml

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@
77
* included LICENSE file for details. *)
88

99
let display_std_outputs = ref false
10-
let dump_outputs = ref None
11-
let dump_reports = ref None
12-
let display_callback = ref false
1310
let display_outcomes = ref false
1411
let grade_student = ref None
1512
let individual_timeout = ref None
@@ -47,29 +44,25 @@ let read_student_file exercise_dir path =
4744
else
4845
Lwt_io.with_file ~mode:Lwt_io.Input fn Lwt_io.read
4946

50-
let grade ?(print_result=false) ?dirname meta exercise output_json =
47+
let grade ?(print_result=false) ?dirname
48+
~dump_outputs ~dump_reports ~display_callback
49+
meta exercise output_json =
5150
Lwt.catch
5251
(fun () ->
5352
let code_to_grade = match !grade_student with
5453
| Some path -> read_student_file (Sys.getcwd ()) path
55-
| None ->
56-
Lwt.return (Learnocaml_exercise.(decipher File.solution exercise)) in
54+
| None -> Lwt.return (Learnocaml_exercise.(decipher File.solution exercise)) in
5755
let callback =
58-
if !display_callback then Some (Printf.eprintf "[ %s ]%!\r\027[K") else None in
56+
if display_callback then Some (Printf.eprintf "[ %s ]%!\r\027[K") else None in
5957
let timeout = !individual_timeout in
6058
code_to_grade >>= fun code ->
6159
Grading_cli.get_grade ?callback ?timeout ?dirname exercise code
6260
>>= fun (result, stdout_contents, stderr_contents, outcomes) ->
6361
flush stderr;
6462
match result with
65-
| Error exn ->
63+
| Error err ->
6664
let dump_error ppf =
67-
begin match Grading.string_of_exn exn with
68-
| Some msg ->
69-
Format.fprintf ppf "%s@." msg
70-
| None ->
71-
Format.fprintf ppf "%a@." Location.report_exception exn
72-
end;
65+
Format.fprintf ppf "%s@." (Grading.string_of_err err);
7366
if stdout_contents <> "" then begin
7467
Format.fprintf ppf "grader stdout:@.%s@." stdout_contents
7568
end ;
@@ -79,7 +72,7 @@ let grade ?(print_result=false) ?dirname meta exercise output_json =
7972
if outcomes <> "" then begin
8073
Format.fprintf ppf "grader outcomes:@.%s@." outcomes
8174
end in
82-
begin match !dump_outputs with
75+
begin match dump_outputs with
8376
| None -> ()
8477
| Some prefix ->
8578
let oc = open_out (prefix ^ ".error") in
@@ -92,7 +85,7 @@ let grade ?(print_result=false) ?dirname meta exercise output_json =
9285
let (max, failure) = Learnocaml_report.result report in
9386
if !display_reports then
9487
Learnocaml_report.print (Format.formatter_of_out_channel stderr) report;
95-
begin match !dump_reports with
88+
begin match dump_reports with
9689
| None -> ()
9790
| Some prefix ->
9891
let oc = open_out (prefix ^ ".report.txt") in
@@ -103,7 +96,7 @@ let grade ?(print_result=false) ?dirname meta exercise output_json =
10396
close_out oc
10497
end ;
10598
if stderr_contents <> "" then begin
106-
begin match !dump_outputs with
99+
begin match dump_outputs with
107100
| None -> ()
108101
| Some prefix ->
109102
let oc = open_out (prefix ^ ".stderr") in
@@ -114,7 +107,7 @@ let grade ?(print_result=false) ?dirname meta exercise output_json =
114107
Format.eprintf "%s" stderr_contents
115108
end ;
116109
if stdout_contents <> "" then begin
117-
begin match !dump_outputs with
110+
begin match dump_outputs with
118111
| None -> ()
119112
| Some prefix ->
120113
let oc = open_out (prefix ^ ".stdout") in
@@ -125,7 +118,7 @@ let grade ?(print_result=false) ?dirname meta exercise output_json =
125118
Format.printf "%s" stdout_contents
126119
end ;
127120
if outcomes <> "" then begin
128-
begin match !dump_outputs with
121+
begin match dump_outputs with
129122
| None -> ()
130123
| Some prefix ->
131124
let oc = open_out (prefix ^ ".outcomes") in
@@ -163,7 +156,8 @@ let grade ?(print_result=false) ?dirname meta exercise output_json =
163156
Lwt.return (Ok ())
164157
end)
165158
(fun exn ->
166-
begin match !dump_outputs with
159+
Lwt.wrap @@ fun () ->
160+
begin match dump_outputs with
167161
| None -> ()
168162
| Some prefix ->
169163
let oc = open_out (prefix ^ ".error") in
@@ -172,15 +166,20 @@ let grade ?(print_result=false) ?dirname meta exercise output_json =
172166
"%a@!" Location.report_exception exn ;
173167
close_out oc
174168
end ;
175-
Format.eprintf "%a" Location.report_exception exn ;
176-
Lwt.return (Error (-1)))
169+
Format.eprintf "%a" Location.report_exception exn;
170+
Error (-1))
177171

178-
let grade_from_dir ?(print_result=false) exercise_dir output_json =
172+
let grade_from_dir
173+
?(print_result=false)
174+
~dump_outputs ~dump_reports ~display_callback
175+
exercise_dir output_json =
179176
let exercise_dir = remove_trailing_slash exercise_dir in
180177
read_exercise exercise_dir >>= fun exo ->
181178
Lwt_io.(with_file ~mode:Input (String.concat Filename.dir_sep [exercise_dir; "meta.json"]) read) >>= fun content ->
182179
let meta = (match content with
183180
| "" -> `O []
184181
| s -> Ezjsonm.from_string s)
185182
|> Json_encoding.destruct Learnocaml_data.Exercise.Meta.enc in
186-
grade ~print_result ~dirname:exercise_dir meta exo output_json
183+
grade
184+
~dump_outputs ~dump_reports ~display_callback
185+
~print_result ~dirname:exercise_dir meta exo output_json

src/grader/grader_cli.mli

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,6 @@
1111
(** Should stdout / stderr of the grader be echoed *)
1212
val display_std_outputs: bool ref
1313

14-
(** Should outputs of the grader be saved and where *)
15-
val dump_outputs: string option ref
16-
17-
(** Should the reports be saved and where *)
18-
val dump_reports: string option ref
19-
20-
(** Should the message from 'test.ml' be displayed on stdout ? *)
21-
val display_callback: bool ref
22-
2314
(** Should compiler outcome be printed ? *)
2415
val display_outcomes: bool ref
2516

@@ -39,9 +30,14 @@ val dump_dot: string option ref
3930

4031
(** Runs the grading process *)
4132
val grade:
42-
?print_result:bool -> ?dirname:string -> Learnocaml_data.Exercise.Meta.t -> Learnocaml_exercise.t -> string option ->
33+
?print_result:bool -> ?dirname:string ->
34+
dump_outputs:string option -> dump_reports:string option ->
35+
display_callback:bool ->
36+
Learnocaml_data.Exercise.Meta.t -> Learnocaml_exercise.t -> string option ->
4337
(unit, int) result Lwt.t
4438

4539
val grade_from_dir:
46-
?print_result:bool -> string -> string option ->
40+
?print_result:bool ->
41+
dump_outputs:string option -> dump_reports:string option -> display_callback:bool ->
42+
string -> string option ->
4743
(unit, int) result Lwt.t

0 commit comments

Comments
 (0)