package alba

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

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_header
          (error_type:string)
        : t
      =
      (* Print the error header. *)
      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
        =
      (* Print the source code in [range] with line numbers and error markers
       *)
      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
             (* normal line *)
             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 (* Pretty_make *)









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 (* reversed *)
      }

    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 =
      (* Find a directory with a file named "alba-workspace.yml" in the
         directory [path] and all its parent directories.

         Return the path to the directory, the directory entries and the
         position of the file "alba-workspace.yml" in the entries.  *)
      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 (* not the root of the workspace *)
           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 =
      (* Find the packages in the workspace [ws_path].

         Return a list of paths
       *)
      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 =
      (* Find the root of the workspace and a list of package directories in
         the workspace.  *)
      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
OCaml

Innovation. Community. Security.