Source file ShellCoreLua.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
exception
LuaEngineShutdown of {
trace : MlFront_Thunk.BuildWriters.Standard.backtrace_item list;
exitcode_posix : int;
exitcode_windows : int;
lua_source_file : Wiring.BuildContext.V.file_object;
cant_do : string;
}
(** An extension of {!DkZero_Base.Exceptions.EngineShutdown} *)
(** {1 Modname} *)
type modname = {
global_opt : string option;
modname : string;
version_opt : string option;
}
let log_app msgf =
Wiring.BuildContext.run_isolated_promise @@ Wiring.BuildContext.Log.app msgf
let parse_modname =
let stop s =
log_app (fun l ->
l
"FATAL: Invalid -l argument `%s`. Use -l modname, -l g=modname or -l \
g=modname@version."
s);
exit 1
in
let modname_modver global_opt s =
match String.split_on_char '@' s with
| [ modname ] -> { global_opt; modname; version_opt = None }
| [ modname; version ] ->
{ global_opt; modname; version_opt = Some version }
| _ -> stop s
in
fun s ->
match String.split_on_char '=' s with
| [ modname ] -> modname_modver None modname
| [ g; modname ] -> modname_modver (Some g) modname
| _ -> stop s
(** {1 Lua Arg} *)
type embedded_details = {
embedded_rule_modid : MlFront_Core.StandardModuleId.t;
embedded_rule_semver : MlFront_Thunk.ThunkSemver64.t;
embedded_rule_file_object : Wiring.BuildContext.Io.file_object;
}
(** The type of a Lua argument in nanopass 1 *)
type lua_arg1 =
| Interpreter1 of string
| DoString1 of string
| Require1 of modname
| LuaScript1 of string
| UiRule1 of MlFront_Thunk.ThunkCommand.module_version
| Arg1 of string
(** The type of a Lua argument in nanopass 2 *)
type lua_arg2 =
| Interpreter2 of string
| DoString2 of string
| Require2 of modname
| LuaScript2 of string
| UiRule2 of {
rule_modver : MlFront_Thunk.ThunkCommand.module_version;
user_request_json : MlFront_Thunk.ThunkJson.t;
}
| FileWithUiRuleEmbedded2 of {
file : MlFront_Core.FilePath.t;
embedded_lines : (Fmlib_parse.Position.range * string) list;
embedded_details : embedded_details;
}
| Arg2 of string
(** The type of a Lua argument in nanopass 3 *)
type lua_arg3 =
| DoString3 of string
| Require3 of modname
| LuaScript3 of string
| UiRule3 of {
ctx : Wiring.BuildContext.t;
build_state : Wiring.BuildContext.State.t;
rule_modver : MlFront_Thunk.ThunkCommand.module_version;
source_file : Wiring.BuildContext.Io.file_object;
tasks : (module Wiring.BuildContext.THUNK_TASKS);
user_request_json : MlFront_Thunk.ThunkJson.t;
}
| FileWithUiRuleEmbedded3 of {
ctx : Wiring.BuildContext.t;
build_state : Wiring.BuildContext.State.t;
file : MlFront_Core.FilePath.t;
embedded_lines : (Fmlib_parse.Position.range * string) list;
tasks : (module Wiring.BuildContext.THUNK_TASKS);
embedded_details : embedded_details;
}
(** {1 Build} *)
open struct
module AU =
MlFront_Thunk.ThunkLuaUtils.AstUtils (MlFront_Thunk.ThunkLuaScript.Ast)
end
let quick_error ~command_name msg =
log_app (fun l ->
l "FATAL: The `%s` subcommand failed.@.%s" command_name
(MlFront_Thunk.ThunkStrings.left_pad msg));
exit 1
module type RUNNER = sig
type state
type value
type chunk
type analysis
val mk : unit -> state
val getchunks_from_file :
lua_file:string ->
(chunk list, string * [ `LineCol of int * int ] option) result
val dostring :
exit_on_error:bool -> sourcename:string -> state -> string -> value list
val dostring_analysis : sourcename:string -> state -> string -> analysis list
val dofile : state -> string -> value list
val dofile_analysis : state -> string -> analysis list
val run_interactive :
?prompt:string -> sourcename:string -> in_channel -> state -> unit
end
module MakeRunner
(InWhat : sig
val in_what : string
val command_name : string
end)
(I :
MlFront_Lua.Lua.INTERP
with module Value = MlFront_Thunk.ThunkLuaScript.Value
and module Ast = MlFront_Thunk.ThunkLuaScript.Ast) :
RUNNER
with type state = I.state
and type value = I.value
and type chunk = I.Ast.chunk
and type analysis = I.value MlFront_Thunk.ThunkLuaTypAnalysis.t = struct
type state = I.state
type value = I.value
type chunk = I.Ast.chunk
type analysis = I.value MlFront_Thunk.ThunkLuaTypAnalysis.t
module IoUtils = MlFront_Thunk.ThunkLuaUtils.IoUtils' (I)
module ScriptUtils = MlFront_Thunk.ThunkLuaScript.ScriptUtils (I)
let quick_error msg = quick_error ~command_name:InWhat.command_name msg
let mk () = I.mk ()
let getchunks_from_file ~lua_file =
let g = I.mk () in
let map = MlFront_Lua.Luasrcmap.mk () in
MlFront_Lua.Luasrcmap.sync map 0 (lua_file, 1, 1);
In_channel.with_open_bin lua_file (fun ic ->
I.with_stack
(MlFront_Thunk.ThunkLuaScript.Value.srcloc ~file:lua_file
~linedefined:0)
g
(IoUtils.lex_into_chunks map)
(Lexing.from_channel ic))
let dostring_full ~exit_on_error ~sourcename g s =
let file =
Printf.sprintf "dostring(%s)"
(MlFront_Thunk.ThunkLuaPrint0.literal_string
(MlFront_Thunk.ThunkStrings.abbreviate s))
in
I.with_stack
(I.Value.srcloc ~file ~linedefined:0)
g
(fun lexbuf ->
match
IoUtils.do_lexbuf_return_all ~sourcename ~in_what:InWhat.in_what g
lexbuf
with
| Error (msg, _loc_opt) ->
if exit_on_error then
quick_error
(Printf.sprintf "In:\n%s\ngot: %s"
(MlFront_Thunk.ThunkStrings.left_pad (String.trim s))
msg)
else
( MlFront_Thunk.ThunkRanges.inner_range
(MlFront_Thunk.ThunkRanges.raw_range_of_string s),
MlFront_Thunk.ThunkLuaUtils.RuleNameMap.empty,
[ I.Value.LuaValueBase.Nil; I.Value.LuaValueBase.String msg ] )
| Ok (chunks, srcmap, v) -> (
let return_range =
AU.return_range ~script_contents:s chunks srcmap
in
let ranges_result =
AU.rule_ranges ~script_contents:s chunks srcmap
in
match ranges_result with
| Error msg ->
if exit_on_error then quick_error msg
else
( return_range,
MlFront_Thunk.ThunkLuaUtils.RuleNameMap.empty,
[
I.Value.LuaValueBase.Nil; I.Value.LuaValueBase.String msg;
] )
| Ok rule_ranges -> (return_range, rule_ranges, v)))
(Lexing.from_string s)
let dostring ~exit_on_error ~sourcename g s =
match dostring_full ~exit_on_error ~sourcename g s with
| _ret_range, _rule_ranges, v -> v
let dofile_full g lua_file =
let script_contents =
In_channel.with_open_bin lua_file In_channel.input_all
in
try
let file =
Printf.sprintf "dofile(%s)"
(MlFront_Thunk.ThunkLuaPrint0.literal_string lua_file)
in
I.with_stack
(MlFront_Thunk.ThunkLuaScript.Value.srcloc ~file ~linedefined:0)
g
(fun lexbuf ->
match
IoUtils.do_lexbuf_return_all ~sourcename:lua_file
~in_what:InWhat.in_what g lexbuf
with
| Error (msg, Some (`LineCol (line, col))) ->
let script_content =
In_channel.with_open_bin lua_file In_channel.input_all
in
let _range, rendered =
MlFront_Thunk.ThunkLuaUtils.render_lua_error
?downgrade_errors_into_warnings:None ~origin:lua_file
~code:"2fc2a99b" ~line ~col ~script_content ~msg ()
in
quick_error rendered
| Error (msg, None) -> quick_error msg
| Ok (chunks, srcmap, v) ->
let return_range =
AU.return_range ~script_contents chunks srcmap
in
let rule_ranges = AU.rule_ranges ~script_contents chunks srcmap in
(return_range, rule_ranges, v))
(Lexing.from_string script_contents)
with Failure msg -> quick_error msg
let dofile g lua_file =
let _ret_range, _rule_ranges, v = dofile_full g lua_file in
v
let dostring_analysis ~sourcename g s =
let return_range, rule_ranges, return_values =
dostring_full ~exit_on_error:true ~sourcename g s
in
(match
ScriptUtils.include_script_outputs_in_analysis ~return_range ~rule_ranges
return_values g
with
| Error msg -> quick_error msg
| Ok () -> ());
let getresult = ScriptUtils.get_analysis g in
match getresult with
| Error msg -> quick_error msg
| Ok typedvalues -> typedvalues
let dofile_analysis g lua_file =
let return_range, rule_ranges_result, return_values =
dofile_full g lua_file
in
match rule_ranges_result with
| Error msg -> quick_error msg
| Ok rule_ranges -> (
(match
ScriptUtils.include_script_outputs_in_analysis ~return_range
~rule_ranges return_values g
with
| Error msg -> quick_error msg
| Ok () -> ());
let getresult = ScriptUtils.get_analysis g in
match getresult with
| Error msg -> quick_error msg
| Ok typedvalues -> typedvalues)
let print_values ~to_string ~label values =
List.iter (fun v -> Printf.printf "%s%s\n" label (to_string v)) values
let run_interactive ?prompt ~sourcename infile g =
let rec loop n pfx =
(match prompt with
| Some prompt_str -> Printf.printf "%s%d> %!" prompt_str n
| None -> ());
let line = input_line infile in
if String.length line > 0 && line.[String.length line - 1] = '\\' then
loop n (pfx ^ String.sub line 0 (String.length line - 1) ^ "\n")
else begin
let values =
dostring ~exit_on_error:false ~sourcename g (pfx ^ line ^ "\n")
in
print_values ~label:"(return) "
~to_string:MlFront_Thunk.ThunkLuaPrint.show_value values;
flush stdout;
flush stderr;
loop (n + 1) ""
end
in
try loop 1 "" with End_of_file -> ()
end
open struct
let start_phase3 ctx ~traces ~lua_valuescan_origin ~prefetch2_lua_scripts
module_or : Wiring.ShellCore.phase3 =
let initiator =
DkZero_Base.BuildRequest.UserInitiated
{ agent = "dk0 lua"; request_slot = None }
in
let state, tasks, prefetch_keys =
Wiring.BuildEngine.load_state_and_tasks_gracefully ~lua_valuescan_origin
~lua_allow_fetch:true ctx ~traces module_or
in
{ ctx; initiator; state; tasks; prefetch_keys; prefetch2_lua_scripts }
end
let mk_source cmdline =
let contents =
List.map
MlFront_Thunk.ThunkLexers.ValueShellLexer.Token.quote_literal_if_needed
cmdline
|> String.concat " "
in
let file =
Wiring.BuildContext.Io.inmemory_file
~origin:
(MlFront_Core.FilePath.append_exn
MlFront_Thunk.ThunkIo.shmdir_for_inmem_filesystem "argv")
contents
in
match
Wiring.BuildContext.run_isolated_promise
(Wiring.BuildContext.Io.checksum_file ~algo:`SHA256 file)
with
| `Error msg -> ShellCore.quick_error msg
| `Checksum sha256 -> (file, sha256)
let start_config ~baseconfig ~install ~random_seed ~verbosity ~progress
~nobuiltininc ~nosysinc ~noworkspaceinc ~sysincludedirs ~userincludedirs
~cells ~local_packages ~build_number ~long_ids ~import debugmodes module_or
=
let ({ preconfig } : Wiring.ShellCore.phase1) =
ShellVSL.start_phase1 ~baseconfig ~random_seed ~cells ~install ()
in
let config =
ShellVSL.start_phase2 ~preconfig ~autofix:false ~verbosity ~progress
~nobuiltininc ~nosysinc ~noworkspaceinc ~sysincludedirs ~userincludedirs
~local_packages ~build_number ~long_ids ~import debugmodes module_or
in
(preconfig, config)
let start_build ~preconfig ctx ~command_name ~lua_valuescan_origin
~prefetch2_lua_scripts ~wait_trace_store ~invalidations parent_txn =
let baseconfig = DkZero_Base.Config.preconfig_baseconfig preconfig in
let newgen, state_after_run, lua_interpreter, shell =
Txn.with_child_txn ~mode:`Create ~wait:wait_trace_store parent_txn
baseconfig (fun txn ->
let tracefd = Txn.tracefd txn in
let traces =
Invalidations.load_traces_gracefully ~preconfig ~reader_generation:0
~invalidations tracefd
in
let shell =
start_phase3 ctx ~traces ~lua_valuescan_origin ~prefetch2_lua_scripts
()
in
let shell =
let state2 = ShellVSL.start_phase4 ~shell ~baseconfig () in
{ shell with state = state2 }
in
let shell =
let lua_script_keys =
List.map
(fun (modid, version) ->
Wiring.BuildContext.K.create_for_scriptmodule
~debug_reference:None ~module_id:modid ~module_semver:version
())
prefetch2_lua_scripts
in
let kont =
Wiring.BuildTaskUnresolved.run_multiple ctx ~tasks:shell.tasks
lua_script_keys
in
let state' =
Wiring.BuildTaskUnresolved.run_unit_continuation kont shell.state
in
{ shell with state = state' }
in
let lua_interpreter =
Wiring.BuildContext.State.lua_interpreter shell.state
in
let state_after_run = shell.state in
let (newgen : int) =
ShellVSL.finish_phase1 shell.ctx state_after_run tracefd
in
(newgen, state_after_run, lua_interpreter, shell))
in
let module ForBuild = struct
let in_what = "in the build"
let command_name = command_name
end in
let module I = (val lua_interpreter) in
let module Runner = MakeRunner (ForBuild) (I) in
let g = Wiring.BuildContext.State.lua_state state_after_run in
let doscript lua_file =
let (_ : Runner.value list) = Runner.dofile g lua_file in
()
in
( g,
Runner.dostring,
doscript,
Runner.run_interactive,
shell.tasks,
shell.state,
newgen )
(** {1 Arg table} *)
let create_argtable_statement lua_args =
let lua_argv = Array.of_list (lua_args : lua_arg2 list) in
let script_index =
let rec aux j =
if j >= Array.length lua_argv then None
else
match lua_argv.(j) with
| LuaScript2 _ | FileWithUiRuleEmbedded2 _ -> Some j
| _ -> aux (j + 1)
in
aux 0
in
let offset = match script_index with Some idx -> 0 - idx | None -> 0 in
let i = ref 0 in
let buf = Buffer.create 256 in
Buffer.add_string buf "arg = {}\n";
let l s =
Buffer.add_string buf (MlFront_Thunk.ThunkLuaPrint0.literal_string s)
in
let idx () =
Buffer.add_string buf (Printf.sprintf "arg[%d] = " (!i + offset))
in
let optopt name =
idx ();
l name;
Buffer.add_string buf "\n"
in
let optval value =
idx ();
l value;
Buffer.add_string buf "\n"
in
while !i < List.length lua_args do
(match lua_argv.(!i) with
| Interpreter2 s -> optopt s
| DoString2 s ->
optopt "-e";
i := !i + 1;
optval s
| Require2 { global_opt; modname; version_opt } -> (
optopt "-l";
i := !i + 1;
let modnamver =
match version_opt with Some v -> modname ^ "@" ^ v | None -> modname
in
match global_opt with
| Some g -> optval (g ^ "=" ^ modnamver)
| None -> optval modnamver)
| LuaScript2 s -> optval s
| UiRule2 { rule_modver; user_request_json = _ } ->
optval (MlFront_Thunk.ThunkCommand.show_module_version rule_modver)
| FileWithUiRuleEmbedded2 { file; embedded_lines = _; embedded_details = _ }
->
optval (MlFront_Core.FilePath.to_string file)
| Arg2 s -> optval s);
i := !i + 1
done;
Buffer.contents buf
(** {1 Evaluation} *)
(** Evaluate scripts and libraries. Raises {!LuaEngineShutdown} on errors. *)
let evaluate_args ~command_name ~dostring ~doscript g lua_args =
List.fold_left
(fun acc_state ->
(function
| DoString3 s ->
let (_ : MlFront_Thunk.ThunkLuaScript.Value.value list) =
dostring ~exit_on_error:true ~sourcename:"<cli>" g s
in
acc_state
| Require3 { global_opt; modname; version_opt } ->
let lua_require_statement =
match version_opt with
| None ->
Printf.sprintf "%srequire(%s)"
(match global_opt with Some g -> g ^ " = " | None -> "")
(MlFront_Thunk.ThunkLuaPrint0.literal_string modname)
| Some version ->
Printf.sprintf
"local _build_imported = require(%s)\n\
%s_build_imported.at(%s)"
(MlFront_Thunk.ThunkLuaPrint0.literal_string modname)
(match global_opt with Some g -> g ^ " = " | None -> "")
(MlFront_Thunk.ThunkLuaPrint0.literal_string version)
in
let (_ : MlFront_Thunk.ThunkLuaScript.Value.value list) =
dostring ~exit_on_error:true ~sourcename:"<cli>" g
lua_require_statement
in
acc_state
| LuaScript3 lua_file ->
if Sys.file_exists lua_file then (
doscript lua_file;
acc_state)
else
let msg =
Printf.sprintf "Lua script file `%s` not found." lua_file
in
quick_error ~command_name msg
| UiRule3
{
ctx;
build_state;
rule_modver;
source_file;
tasks;
user_request_json;
} -> begin
let build_state = Option.value ~default:build_state acc_state in
let latest_cant_do = ref "run UI rule" in
try
let k_kont =
let open Wiring.BuildContext.Syntax in
let* apply_aliases =
Wiring.BuildTraceStore.get_apply_aliases ctx
in
Wiring.BuildTaskUnresolved.instantiate_rule ctx ~tasks
~ui_rights:`AnyRuleTypeAllowed
~apply_aliases:(Some apply_aliases) ~debug_reference:None
~module_id:(MlFront_Thunk.ThunkCommand.modver_id rule_modver)
~module_semver:
(MlFront_Thunk.ThunkCommand.modver_semver rule_modver)
~hostscript_filepath:None ~user_request_json
~user_request_slot:None ()
in
let key, state' =
Wiring.BuildTaskUnresolved.run_continuation k_kont build_state
in
let kont = Wiring.BuildTaskUnresolved.run_single ctx ~tasks key in
let state'' : Wiring.BuildContext.State.t =
Wiring.BuildTaskUnresolved.run_unit_continuation kont state'
in
Some state''
with
| DkZero_Base.Exceptions.EngineShutdown
{ trace; exitcode_posix; exitcode_windows }
->
let bt = Printexc.get_raw_backtrace () in
Printexc.raise_with_backtrace
(LuaEngineShutdown
{
trace;
exitcode_posix;
exitcode_windows;
lua_source_file = source_file;
cant_do = !latest_cant_do;
})
bt
end
| FileWithUiRuleEmbedded3
{
ctx;
build_state;
file = hostscript_filepath;
embedded_lines = _;
tasks;
embedded_details =
{
embedded_rule_modid;
embedded_rule_semver;
embedded_rule_file_object;
};
} -> begin
let build_state = Option.value ~default:build_state acc_state in
let latest_cant_do = ref "run embedded UI rule" in
try
let k_kont =
let open Wiring.BuildContext.Syntax in
let* apply_aliases =
Wiring.BuildTraceStore.get_apply_aliases ctx
in
Wiring.BuildTaskUnresolved.instantiate_rule ctx ~tasks
~ui_rights:`AnyRuleTypeAllowed
~apply_aliases:(Some apply_aliases) ~debug_reference:None
~module_id:embedded_rule_modid
~module_semver:embedded_rule_semver
~hostscript_filepath:(Some hostscript_filepath)
~user_request_json:(`Assoc []) ~user_request_slot:None ()
in
let key, state' =
Wiring.BuildTaskUnresolved.run_continuation k_kont build_state
in
let kont = Wiring.BuildTaskUnresolved.run_single ctx ~tasks key in
let state'' : Wiring.BuildContext.State.t =
Wiring.BuildTaskUnresolved.run_unit_continuation kont state'
in
Some state''
with
| DkZero_Base.Exceptions.EngineShutdown
{ trace; exitcode_posix; exitcode_windows }
->
let bt = Printexc.get_raw_backtrace () in
Printexc.raise_with_backtrace
(LuaEngineShutdown
{
trace;
exitcode_posix;
exitcode_windows;
lua_source_file = embedded_rule_file_object;
cant_do = !latest_cant_do;
})
bt
end))
None lua_args