forked from jrh13/hol-light
-
Notifications
You must be signed in to change notification settings - Fork 2
/
candle_boot.ml
713 lines (658 loc) · 21.1 KB
/
candle_boot.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
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
(* ------------------------------------------------------------------------- *
* Prelude
* ------------------------------------------------------------------------- *)
(* This is pointer equality, which is missing from CakeML.
|| x = y is just to get the type variables right:
*)
let (==) x y = false || x = y;;
let ref x = Ref x;;
let (/) = div;;
let (-.) = Double.(-);;
let (+.) = Double.(+);;
let ( *.) = Double.( * );;
let (/.) = Double.(/);;
let failwith msg = raise (Failure msg);;
(* This is the pretty printer for exceptions, and you need to update it
each time you add an exception definition if you want it to print something
informative (see e.g. exception Unchanged in lib.ml).
*)
let pp_exn e =
match e with
| Chr -> Pretty_printer.token "Chr"
| Div -> Pretty_printer.token "Div"
| Bind -> Pretty_printer.token "Bind"
| Subscript -> Pretty_printer.token "Subscript"
| Interrupt -> Pretty_printer.token "Interrupt"
| Failure s -> Pretty_printer.app_block "Failure" [Pretty_printer.pp_string s]
| _ -> Pretty_printer.token "<exn>";;
(*CML
(* OCaml parser doesn't like the tilde *)
val rat_minus = Rat.~;
*)
(* Some conversions in OCaml style: *)
let string_of_int n =
if n < 0 then "-" ^ Int.toString (-n)
else Int.toString n
;;
let pp_int n = Pretty_printer.token (string_of_int n);;
let pp_rat r =
let n = Rat.numerator r in
let d = Rat.denominator r in
Pretty_printer.token (string_of_int n ^ "/" ^ string_of_int d)
;;
let string_of_float = Double.toString;;
let int_of_string = Option.valOf o Int.fromString;;
(* Left shifting integers. HOL Light expects these to not be bigints, so I
suppose we can just map in and out of word64. *)
let (lsl) x y =
Word64.toInt (Word64.(<<) (Word64.fromInt x) y);;
let (lsr) x y =
Word64.toInt (Word64.(>>) (Word64.fromInt x) y);;
let (land) x y =
Word64.toInt (Word64.andb (Word64.fromInt x) (Word64.fromInt y));;
(* TODO Need a better string escaping thing. *)
let string_escaped =
let rec escape cs =
match cs with
| [] -> []
| '\\'::l -> '\\'::'\\'::escape l
| '\b'::l -> '\\'::'b'::escape l
| '\t'::l -> '\\'::'t'::escape l
| '\n'::l -> '\\'::'n'::escape l
| '"'::l -> '\\'::'"'::escape l
| c::l -> c::escape l in
fun s -> String.implode (escape (String.explode s));;
(* Add printers for things we deal with differently, e.g. bool, app_list, etc *)
let pp_bool b = Pretty_printer.token (if b then "true" else "false");;
let pp_char c =
Pretty_printer.token ("'" ^ string_escaped (String.str c) ^ "'");;
let rec pp_app_list f xs =
match xs with
| Nil -> Pretty_printer.token "Nil"
| List xs ->
Pretty_printer.app_block "List" [Pretty_printer.pp_list f xs]
| Append (l, r) ->
Pretty_printer.app_block "Append" [
Pretty_printer.tuple [pp_app_list f l; pp_app_list f r]]
;;
let abs x = if x < 0 then -x else x;;
let ignore x = x; ();;
let rec rev_append xs acc =
match xs with
| [] -> acc
| x::xs -> rev_append xs (x::acc);;
let concat_map f =
let rec concat acc xs =
match acc with
| [] -> map xs
| a::acc -> a::concat acc xs
and map xs =
match xs with
| [] -> []
| x::xs -> concat (f x) xs in
map;;
(* ------------------------------------------------------------------------- *
* Helpful banner
* ------------------------------------------------------------------------- *)
let _ = List.app print [
"\n";
"---------------------------------------\n";
" Candle \n";
"---------------------------------------\n";
"\n";
"\n";
];;
(* ------------------------------------------------------------------------- *
* Operations on filenames
* ------------------------------------------------------------------------- *)
module Filename = struct
let currentDir = ".";;
let parentDir = "..";;
let dirSep = "/";;
let isRelative fname =
try String.sub fname 0 <> '/'
with Subscript -> true;;
let concat dname fname =
if dname = currentDir then fname
else String.concat [dname; dirSep; fname];;
let (basename, dirname) =
let trimSep s = (* trim trailing separators *)
let len = String.size s in
let dsl = String.size dirSep in
let rec search i =
if i < dsl then s
else if String.substring s i dsl = dirSep then
search (i - dsl)
else
String.substring s 0 i in
search (len - 1) in
let splitPath s =
let len = String.size s in
let dsl = String.size dirSep in
let s = trimSep s in
let rec search i =
if i <= dsl then
(currentDir, s)
else if String.substring s i dsl = dirSep then
(String.substring s 0 (i - 1),
String.extract s (i + dsl) None)
else
search (i - dsl) in
search (len - 1) in
((fun s -> let (_, b) = splitPath s in b),
(fun s -> let (d, _) = splitPath s in d))
end;; (* struct *)
(* ------------------------------------------------------------------------- *
* Double-ended functional queue
* ------------------------------------------------------------------------- *)
module Queue = struct
type 'a queue = 'a list * 'a list;;
let push_back (xs, ys) y = (xs, y::ys);;
let push_front (xs, ys) x = (x::xs, ys);;
let rec dequeue (xs, ys) =
match xs with
| x::xs -> Some (x, (xs, ys))
| [] ->
match ys with
| [] -> None
| _ -> dequeue (List.rev ys, []);;
let empty = ([], []);;
let flush (xs, ys) = xs @ List.rev ys;;
end;; (* struct *)
(* ------------------------------------------------------------------------- *
* Imperative wrapper around Queue
* ------------------------------------------------------------------------- *)
module Buffer = struct
type 'a buffer = 'a Queue.queue ref;;
let push_back q x = q := Queue.push_back (!q) x;;
let push_front q x = q := Queue.push_front (!q) x;;
let dequeue q =
match Queue.dequeue (!q) with
| None -> None
| Some (x, q') ->
q := q';
Some x;;
let empty () = ref Queue.empty;;
let flush q =
let els = Queue.flush (!q) in
q := Queue.empty;
els
end;; (* struct *)
(* ------------------------------------------------------------------------- *
* Operations on strings
* ------------------------------------------------------------------------- *)
let trimLeft str =
let rec nom n =
if n >= String.size str then str
else if Char.isSpace (String.sub str n) then nom (n + 1)
else String.extract str n None in
nom 0
;;
let trimRight str =
let rec nom n =
if n < 1 then str
else if Char.isSpace (String.sub str n) then nom (n - 1)
else String.substring str 0 (n + 1) in
nom (String.size str - 1)
;;
(* ------------------------------------------------------------------------- *
* Operations on files
* ------------------------------------------------------------------------- *)
let isFile fname =
try let ins = Text_io.b_openIn fname in
Text_io.b_closeIn ins;
true
with Text_io.Bad_file_name -> false
;;
(* ------------------------------------------------------------------------- *
* Lexer for enough parts of the language to keep track on whether semi-colons
* appear on the top-level or not.
* ------------------------------------------------------------------------- *)
module Lexer = struct
type directive =
| D_load
| D_need
| D_use
;;
let string_of_directive d =
match d with
| D_load -> "load"
| D_need -> "need"
| D_use -> "use"
;;
type token =
| T_begin | T_end | T_struct | T_sig | T_semis | T_newline
| T_use | T_needs | T_loads (* converted into directives *)
| T_other of string
| T_symb of string
| T_comment of string
| T_string of string
| T_quote of string
| T_char of string
| T_number of string
| T_spaces of string
| T_done (* pseudo-token at switch from loading to user input *)
;;
let string_of_token unquote tok =
match tok with
| T_begin -> "begin"
| T_end -> "end"
| T_struct -> "struct"
| T_sig -> "sig"
| T_semis -> ";;"
| T_newline -> "\n"
| T_string s -> "\"" ^ s ^ "\""
| T_quote s ->
begin
match unquote with
| None -> "`" ^ s ^ "`"
| Some f -> "(" ^ f s ^ ")"
end
| T_char s -> "'" ^ s ^ "'"
| T_comment s | T_other s | T_symb s | T_number s | T_spaces s -> s
| T_use -> "#use"
| T_loads -> "loads"
| T_needs -> "needs"
| T_done -> "(* shouldn't happen *)"
;;
let directive_of_token t =
match t with
| T_needs -> Some D_need
| T_loads -> Some D_load
| T_use -> Some D_use
| _ -> None
;;
let scan nextChar peekChar =
let quoteSym c = c = '`' in
let tok_from_keyword =
let keywords = [
"begin", T_begin;
"end", T_end;
"struct", T_struct;
"sig", T_sig;
(* top-level directives *)
"#use", T_use;
"needs", T_needs;
"loads", T_loads;
] in
fun s -> match Alist.lookup keywords s with
| None -> T_other s
| Some tok -> tok in
let is_symbol =
let symchars = String.explode "#$&*+-/=>@^|~!?%<:.()[]{}," in
fun c -> List.exists (fun x -> x = c) symchars in
let is_alpha c =
Char.(<=) 'a' c && Char.(<=) c 'z' ||
Char.(<=) 'A' c && Char.(<=) c 'Z' in
let is_digit c =
Char.(<=) '0' c && Char.(<=) c '9' in
let is_name_char c =
is_alpha c || is_digit c || c = '_' || c = '\'' in
let is_int_char c =
is_digit c || c = '_' || c = 'l' || c = 'L' || c = 'n' in
let scan_while acc pred =
let rec nom acc =
Interrupt.check ();
match peekChar () with
| None -> None
| Some c when pred c ->
nextChar ();
nom (c::acc)
| _ -> Some (String.implode (List.rev acc)) in
nom acc in
let scan_comment () =
let rec nom acc level =
Interrupt.check ();
if level = 0 then
Some (String.implode ('('::'*'::List.rev acc))
else
match nextChar () with
| Some '(' ->
begin
match peekChar () with
| Some '*' ->
nextChar ();
nom ('*'::'('::acc) (level + 1)
| _ -> nom ('('::acc) level
end
| Some '*' ->
begin
match peekChar () with
| Some ')' ->
nextChar ();
nom (')'::'*'::acc) (level - 1)
| _ -> nom ('*'::acc) level
end
| Some c -> nom (c::acc) level
| None -> None in
nom [] 1 in
let scan_name c =
match scan_while [c] is_name_char with
| None -> None
| Some s -> Some (tok_from_keyword s) in
let scan_symb c =
Option.map (fun s -> T_symb s)
(scan_while [c] is_symbol) in
let scan_int c =
Option.map (fun s -> T_number s)
(scan_while [c] is_int_char) in
let scan_quote () =
match scan_while [] (not o quoteSym) with
| None -> None
| Some str ->
nextChar ();
Some (T_quote str) in
let skip_line () =
scan_while [] (fun x -> x <> '\n');
nextChar () in
let scan_spaces c =
Option.map (fun s -> T_spaces s)
(scan_while [c] (fun c -> c <> '\n' && Char.isSpace c)) in
let scan_escaped ch =
let rec nom acc =
Interrupt.check ();
match nextChar () with
| None -> None
| Some '\\' ->
begin
match nextChar () with
| None -> nom ('\\'::acc)
| Some c -> nom (c::'\\'::acc)
end
| Some c when c = ch -> Some (String.implode (List.rev acc))
| Some c -> nom (c::acc) in
nom [] in
let scan_strlit () =
Option.map (fun s -> T_string s)
(scan_escaped '"') in
(* This code will intentionally let through some bad tokens (it doesn't check
whether escape sequences are well formed), but those will get stuck in the
real lexer. *)
let scan_charlit_or_tyvar () =
match peekChar () with
(* Escaped character literal *)
| Some '\\' ->
begin
nextChar ();
Option.map (fun s -> T_char ("\\" ^ s))
(scan_escaped '\'')
end
(* A single tick, start of a type variable, but followed by space *)
| Some ' ' | Some '\n' | Some '\t' | Some '\r' -> Some (T_other "'")
(* Regular character literal, or a type variable *)
| Some c ->
begin
nextChar ();
match peekChar () with
(* Regular character literal *)
| Some '\'' ->
begin
nextChar ();
Some (T_char (String.str c))
end
(* Type variable *)
| Some _ -> Option.map (fun s -> T_other s)
(scan_while [c; '\''] is_name_char)
| None -> Some (T_other (String.implode ['\''; c]))
end
(* Two ticks following each other: '' *)
| Some '\'' -> Some (T_symb "''")
| None -> Some (T_symb "'") in
let rec nextToken () =
match nextChar () with
| None -> None
(* Attempt to scan semis *)
| Some ';' ->
begin
match peekChar () with
| Some ';' ->
nextChar ();
Some T_semis
| _ -> scan_symb ';'
end
(* Attempt to scan comment *)
| Some '(' ->
begin
match peekChar () with
| Some '*' ->
nextChar ();
begin
match scan_comment () with
| None -> Some (T_symb "(*")
| Some str -> Some (T_comment str)
end
| _ -> Some (T_symb "(")
end
(* Attempt to scan char literal or type variable *)
| Some '\'' -> scan_charlit_or_tyvar ()
(* Attempt to scan string literal *)
| Some '"' -> scan_strlit ()
(* A #use directive, maybe: *)
| Some '#' -> scan_name '#'
(* Newlines *)
| Some '\n' -> Some T_newline
(* Anything else *)
| Some c ->
if quoteSym c then
scan_quote ()
else if is_digit c then
scan_int c
else if is_symbol c then
scan_symb c
else if is_alpha c || c = '_' then
scan_name c
else if Char.isSpace c then
scan_spaces c
else
Some (T_other (String.str c)) in
fun () -> Interrupt.check (); nextToken ()
;;
end;; (* struct *)
(* ------------------------------------------------------------------------- *
* CakeML struct: setting up REPL, reading/loading.
* ------------------------------------------------------------------------- *)
module Cakeml = struct
let loadPath = ref [Filename.currentDir];;
let (input1 : (unit -> char option) ref) =
ref (fun () -> Text_io.input1 Text_io.stdIn);;
let prompt1 = ref "# ";;
let prompt2 = ref " ";;
let userInput = ref true;;
let unquote = ref (fun (s: string) -> s);;
exception Repl_error;;
let () =
let prompt = ref (!prompt2) in
let peekChar, nextChar =
let lookahead = ref (None: char option) in
let peek () =
match !lookahead with
| Some c -> Some c
| None ->
match (!input1) () with
| None -> None
| Some c ->
lookahead := Some c;
Some c in
let next () =
match !lookahead with
| None -> (!input1) ()
| Some c ->
lookahead := None;
Some c in
peek, next in
(* Load files from disk and keep track on what has been loaded.
*)
let load =
let loadedFiles = (ref [] : string list ref) in
let loadMsg s = print ("- Loading " ^ s ^ "\n") in
let load_use fname =
loadMsg fname;
Text_io.b_inputLinesFrom '\n' fname in
let load fname =
loadMsg fname;
match Text_io.b_inputLinesFrom '\n' fname with
| None -> None
| Some lns ->
begin
if not (List.exists (fun x -> x = fname) (!loadedFiles)) then
loadedFiles := fname :: !loadedFiles
end;
Some lns in
let load1 fname =
if List.exists (fun x -> x = fname) (!loadedFiles) then
begin
print ("- Already loaded: " ^ fname ^ "\n");
None
end
else
load fname in
let loadOnPath pragma fname =
let paths = List.map (fun p -> Filename.concat p fname) (!loadPath) in
match List.find isFile paths with
| None ->
print ("- No such file: " ^ fname ^ "\n");
Repl.nextString := "";
failwith ("No such file : " ^ fname)
| Some fname ->
let loader = match pragma with
| Lexer.D_load -> load
| Lexer.D_need -> load1
| Lexer.D_use -> load_use in
(match loader fname with
| None -> []
| Some ls -> ls) in
loadOnPath in
(* Instantiate lexer *)
let scan1 = Lexer.scan nextChar peekChar in
(* Enqueue input here *)
let input_buffer = (Buffer.empty () : Lexer.token Buffer.buffer) in
(* Set up a nextChar/peekChar pair on the list of lines, lex all of it,
* and then stuff it all into input_buffer.
*)
let scan_lines lines =
let inp = ref lines in
let idx = ref 0 in
let rec peekChar () =
match !inp with
| [] -> None
| s::ss ->
try Some (String.sub s (!idx))
with Subscript ->
(* Look into next string. It should not be empty. *)
match ss with
| [] -> None
| s::ss -> try Some (String.sub s 0)
with Subscript -> None in
let rec nextChar () =
match !inp with
| [] -> None
| s::ss ->
try let res = String.sub s (!idx) in
idx := (!idx) + 1;
Some res
with Subscript ->
idx := 0;
inp := ss;
nextChar () in
let scan = Lexer.scan nextChar peekChar in
let rec nom acc =
match scan () with
| None -> List.app (Buffer.push_front input_buffer) acc
| Some tok -> nom (tok::acc) in
nom [];
Buffer.push_back input_buffer Lexer.T_done in
let next () =
match Buffer.dequeue input_buffer with
| Some tok -> Some tok
| None -> scan1 () in
let output_buffer = (Buffer.empty () : Lexer.token Buffer.buffer) in
let rec next_nonspace () =
match next () with
| Some (Lexer.T_spaces _) -> next_nonspace ()
| res -> res in
let rec scan level =
try match next () with
| None -> None
(* Use token as a loading directive. *)
| Some (Lexer.T_use | Lexer.T_needs | Lexer.T_loads as tok) ->
begin
let dir = Option.valOf (Lexer.directive_of_token tok) in
match next_nonspace () with
(* Attempt to convert into directive: *)
| Some (Lexer.T_string fname as tok') ->
begin
match next_nonspace () with
(* OK directive, perform load: *)
| Some (Lexer.T_semis) ->
let lines = load dir fname in
if List.null lines then scan level else
begin
userInput := false;
scan_lines lines;
scan level
end
(* Malformed *)
| _ ->
failwith
(String.concat [
"\nREPL error: ";
Lexer.string_of_directive dir;
" \"string\" should be followed by a double";
" semicolon [;;].\n"])
end
(* Malformed *)
| _ ->
failwith
(String.concat [
"\nREPL error: ";
Lexer.string_of_directive dir;
" should be followed by a \"string literal\" and then a";
" double semicolon [;;].\n"])
end
| Some (Lexer.T_done) ->
userInput := true;
scan level
| Some tok ->
Buffer.push_back output_buffer tok;
match tok with
| Lexer.T_begin | Lexer.T_struct | Lexer.T_sig ->
scan (level + 1)
| Lexer.T_end -> scan (level - 1)
| Lexer.T_semis when level = 0 ->
prompt := !prompt1;
Some (Buffer.flush output_buffer)
| Lexer.T_newline when !userInput ->
print (!prompt);
prompt := !prompt2;
scan level
| _ -> scan level
with Interrupt ->
print "Compilation interrupted\n";
raise Repl_error in
let checkError () =
let err = !Repl.errorMessage in
Repl.errorMessage := "";
if err <> "" then raise Repl_error in
let next () =
try checkError ();
match scan 0 with
| None ->
Repl.isEOF := true;
Repl.nextString := ""
| Some ts ->
Repl.isEOF := false;
Repl.nextString :=
String.concat
(List.map (Lexer.string_of_token (Some (!unquote))) ts)
with Repl_error ->
if not (!userInput) then print (!prompt1);
Buffer.flush input_buffer;
Buffer.flush output_buffer;
Repl.nextString := "";
userInput := true in
Repl.readNextString := (fun () ->
print (!prompt1);
next ();
Repl.readNextString := next)
;;
end;; (* struct *)