Source file alba_console.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
open Fmlib
open Common
open Alba_core
module Parser = Parser_lang
module Repl_parser = Parser.Make (Parser.Command)
module Position = Character_parser.Position
type pos = Position.t
type range = pos * pos
module Pretty_make (Io:Io.SIG) =
struct
module Position = Character_parser.Position
module Located = Character_parser.Located
module Expression = Ast.Expression
module Out = Fmlib.Io.Output (Io)
module PP = Pretty_printer.Pretty (Out)
include PP
let put_left (width:int) (s:string): t =
let len = String.length s in
if len < width then
chain [string s; fill (width - len) ' ']
else
string s
let paragraphs (lst:t list): t =
chain_separated lst cut
let
(error_type:string)
: t
=
let err = " ERROR " in
let nfill =
max 0 (80 - 3 - (String.length error_type + String.length err))
in
chain [fill 2 '-';
char ' ';
string error_type;
string err;
fill nfill '-';
cut;
cut]
let print_ruler (offset: int) (n: int): t =
let n = n mod 10 in
let str = "0123456789"
in
let rec decimals i =
if i = n then
empty
else
string (string_of_int i)
<+> fill 9 ' '
<+> decimals (i + 1)
and markers n =
if n = 0 then
empty
else
string str <+> markers (n - 1)
in
fill offset ' '
<+> decimals 0
<+> cut <+> fill offset ' '
<+> markers n <+> cut
let print_tabs (offset: int) (tabs: int list): t =
let rec print col lst =
match lst with
| [] ->
empty
| [pos] ->
fill (pos - col) ' ' <+> char '.'
| pos1 :: pos2 :: rest ->
fill (pos1 - col) ' ' <+> char '.'
<+>
print pos1 (pos2 :: rest)
in
fill offset ' '
<+>
print 0 tabs
<+> cut
let print_source
(source:string)
((pos1,pos2):range)
(error_tabs: int list)
: t
=
let start_line = Position.line pos1
and end_line = Position.line pos2
and start_col = Position.column pos1
and end_col = Position.column pos2
and len = String.length source
in
let end_col =
if start_line = end_line && start_col = end_col then
end_col + 1
else
end_col
in
assert (start_line <= end_line);
assert (start_line < end_line || start_col < end_col);
let number_width =
String.length (string_of_int (end_line + 1))
in
let print_line start beyond line_no =
let line_no_str = string_of_int (line_no + 1) in
fill (number_width - String.length line_no_str) ' '
<+> string line_no_str
<+> string "| "
<+> substring source start (beyond - start)
<+> cut
and skip_line_no =
fill (number_width + 2) ' '
in
let rec print (char_offset:int) (line_no:int): t =
if len <= char_offset then
empty
else
let pos_newline = String.find (fun c -> c = '\n') char_offset source
in
(if line_no = start_line && start_line = end_line then
print_line char_offset pos_newline line_no
<+> skip_line_no
<+> fill start_col ' '
<+> fill (end_col - start_col) '^'
<+> cut
else if line_no = start_line && start_line < end_line then
skip_line_no
<+> fill start_col ' '
<+> char 'v'
<+> fill 3 '.'
<+> cut
<+> print_line char_offset pos_newline line_no
else if line_no = end_line && start_line <> end_line then
print_line char_offset pos_newline line_no
<+> skip_line_no
<+> fill (end_col - 1) '.'
<+> char '^'
<+> cut
else if pos_newline + 1 = len && start_line = line_no + 1 then
print_line char_offset pos_newline line_no
<+> skip_line_no
<+> fill (pos_newline - char_offset) ' '
<+> char '^'
<+> cut
else
print_line char_offset pos_newline line_no
)
<+> print (pos_newline + 1) (line_no + 1)
in
print 0 0
<+>
(
if error_tabs = [] then
empty
else
let tabs = true in
if tabs then
print_tabs (number_width + 2) error_tabs
else
print_ruler
(number_width + 2)
8
<+> cut
)
let print (fd:Io.File.Out.fd) (width:int) (pp:t): unit Io.t =
Out.run fd (PP.run 0 width width pp)
end
module Located = Character_parser.Located
module Make (Io:Io.SIG) =
struct
module Pretty = Pretty_make (Io)
type command_line = {
command: string option;
workspace: string;
package_paths: string list;
verbosity: int;
force: bool;
arguments: string list
}
module CLP =
Argument_parser.Make (struct type t = command_line end)
let find_in_array (p:'a->bool) (arr: 'a array): int =
Interval.find
(fun i -> p arr.(i))
0
(Array.length arr)
let find_elem_in_array (a:'a) (arr:'a array): int =
find_in_array (fun e -> e = a) arr
let has_file (name:string) (arr:string array): bool =
find_elem_in_array name arr < Array.length arr
let rec find_workspace
(path:string)
: (string * string array) option Io.t =
let open Io in
Directory.read path >>= function
| None ->
return None
| Some arr ->
let len = Array.length arr in
let pos = find_elem_in_array "alba-workspace.yml" arr
in
if pos = len then
match Path.split path with
| None ->
return None
| Some (dir,_) ->
find_workspace dir
else
return @@ Some (path,arr)
let find_packages
(ws_path:string) (entries:string array)
: string list Io.t =
let open Io in
let rec find path entries lst =
let len = Array.length entries in
let rec find_in_entries i lst =
if i = len then
return lst
else
let path1 = Path.join path entries.(i) in
Directory.read path1 >>= function
| None ->
find_in_entries (i+1) lst
| Some entries1 ->
find path1 entries1 lst >>= fun lst ->
find_in_entries (i+1) lst
in
if has_file "alba-package.yml" entries then
return @@ path :: lst
else
find_in_entries 0 lst
in
find ws_path entries []
let explore_workspace (cmd:command_line)
: (string * string list) option Io.t =
let open Io in
Stdout.line "explore workspace ..." >>= fun _ ->
Path.absolute cmd.workspace >>= fun path ->
find_workspace (Path.normalize path) >>= function
| None ->
return None
| Some (path, entries) ->
find_packages path entries >>= fun pkgs ->
return @@ Some (path,pkgs)
let compile (cmd:command_line): unit Io.t =
Io.(Stdout.line "compile ..." >>= fun _ ->
explore_workspace cmd >>= function
| None ->
Stdout.line "no workspace found"
| Some (ws_path, _) ->
Stdout.line ("workspace <" ^ ws_path ^ ">"))
let status (_:command_line): unit Io.t =
Io.Stdout.line "status ..."
let report_parse_problem
(src: string)
(p: Repl_parser.parser)
: Pretty.t
=
let module Error_printer = Repl_parser.Error_printer (Pretty) in
Error_printer.print_with_source src p
let build_and_compute
(src: string)
(e: Ast.Expression.t)
(compute: bool)
: Pretty.t
=
let std_context = Context.standard () in
match Builder.build e std_context with
| Error (range, descr) ->
let module Builder_print = Builder.Print (Pretty) in
let open Pretty in
error_header "TYPE"
<+> print_source src range []
<+> cut
<+> Builder_print.description descr
<+> cut
| Ok lst ->
Pretty.(
cut
<+> paragraphs
(List.map
(fun (t,tp) ->
let t =
if compute then
Context.compute t std_context
else
t
in
let module P = Context.Pretty (Pretty) in
P.print (Term.Typed (t, tp)) std_context
<+> cut)
lst)
<+> cut
)
let repl (_:command_line): unit Io.t =
let module State =
struct
type t = string option
let string (s: t): string =
Option.value s
let init: t = Some ""
let exit: t = None
let prompt (s:t): string option =
Option.map
(fun s ->
if s = "" then "> " else "| ")
s
let is_last (line: string): bool =
let len = String.length line in
len = 0 || line.[len-1] <> ' '
let add (line: string) (s: t): t =
Option.map
(fun s ->
if s = "" then
line
else
s ^ "\n" ^ line)
s
end
in
let parse (s:string): Repl_parser.parser =
let len = String.length s in
let rec parse i p =
let more = Repl_parser.needs_more p in
if i < len && more then
parse (i+1) (Repl_parser.put_char p s.[i])
else if more then
Repl_parser.put_end p
else
p
in
parse 0 Repl_parser.(make command)
in
let command (src: string): State.t Io.t =
let print pr =
Io.(Pretty.print File.stdout 80 pr)
in
let p = parse src in
assert (Repl_parser.has_ended p);
match Repl_parser.result p with
| Some Parser.Command.Do_nothing ->
Io.return State.init
| Some Parser.Command.Exit ->
Io.return State.exit
| Some (Parser.Command.Evaluate e) ->
Io.(print (build_and_compute src e true)
>>= fun _ -> return State.init )
| Some (Parser.Command.Type_check e) ->
Io.(print (build_and_compute src e false)
>>= fun _ -> return State.init )
| None ->
Io.(print (report_parse_problem src p)
>>= fun _ -> return State.init)
in
let next (s:State.t) (line:string): State.t Io.t =
let s = State.add line s in
if State.is_last line then
command (State.string s)
else
Io.return s
and stop (s:State.t): State.t Io.t =
Io.return s
in
Io.(cli_loop State.init State.prompt next stop >>= fun _ -> return ())
let commands: (string*(command_line->unit Io.t)*string) list =
["compile", compile,
"Compile the modules provided on the command line and all its \
dependencies if compilation is required. If no modules are provided \
all modules of the package which require compilation are compiled."
;
"status", status,
"Display all modules which require compilation or recompilation."
;
"repl", repl,
"Start an interactive programming session."
]
let command_options: (CLP.key*CLP.spec*CLP.doc) list =
let open CLP in
[("-verbosity",
Int (fun i a -> {a with verbosity = i}),
"Verbosity level (default 1)"
);
("-w", String (fun s a -> {a with workspace = s}),
"Path into an Alba workspace (default: current working directory)");
("-I", String (fun s a -> {a with package_paths =
s :: a.package_paths}),
"Add argument to search path for used packages");
("-force", Unit (fun a -> {a with force = true}),
"Force compilation, even if it is not necessary")
]
let parse (args:string array): (command_line,CLP.error) result =
let open CLP in
parse
args
{command = None;
workspace = "";
package_paths = [];
verbosity = 1;
force = false;
arguments = []}
command_options
(fun s a ->
match a.command with
| None ->
{a with command = Some s}
| Some _ ->
{a with arguments = s :: a.arguments})
let print_options: Pretty.t =
let open Pretty in
chain
(List.map
(fun (key,spec,doc) ->
chain [cut; put_left 20 (key ^ CLP.argument_type spec);
nest 20 @@ fill_paragraph doc])
command_options)
let print_commands: Pretty.t =
let open Pretty in
chain
(List.map
(fun (cmd,_,doc) ->
chain [cut; put_left 10 cmd; nest 10 @@ fill_paragraph doc])
commands)
let print_usage: Pretty.t =
let open Pretty in
chain
[string "Usage: alba command options arguments";
cut; cut;
nest_list 4 [string "Commands:"; print_commands];
cut; cut;
nest_list 4 [string "Options:"; print_options];
cut]
let print_error (s:string): unit Io.t =
let open Pretty in
print
Io.File.stderr
80
(string "Error: " <+> string s <+> cut <+> cut <+> print_usage)
let run (): unit =
let open Io in
Process.execute
(Process.command_line >>= fun args ->
match parse args with
| Ok cl ->
begin
match cl.command with
| None ->
print_error "no commands given"
| Some cmd ->
match
List.find (fun (c,_,_) -> c = cmd) commands
with
| None ->
print_error ("Unknown command '" ^ cmd ^ "'")
| Some (_,f,_) ->
f cl
end
| Error e ->
print_error (CLP.string_of_error e) >>= fun _ ->
Process.exit 1
)
end