package tiny_libs

  1. Overview
  2. Docs
From-scratch libraries for teaching: graphics, audio, compression, crypto, networking and more

Install

dune-project
 Dependency

Authors

Maintainers

Sources

0.3.6.tar.gz
md5=7c636383d146d30ac6f2fa234a6253c8
sha512=c79f3823c5f8f57e5038eb640d487c61168b84aa07c61999d6622ef9fd0c890e2b03b4c6a7cdbbe9352a49e25dda00ac7bb14693cee8e3d7beeed251351a2af0

doc/src/tiny_libs.terminal/Talk.ml.html

Source file Talk.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
(* Claude Code
 *
 * Copyright (C) 2026 Yoann Padioleau
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public License
 * (LGPL) as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 *)

(* See Talk.mli *)

(* the seeded numbers: Lehmer's generator, drawn exactly as
   Playground.random_int draws them, so that a seed gives the same game
   in a test and on the Playground *)
type seed = Lehmer.t

let initial_seed (n : int) : seed = Lehmer.scramble n

let random_int (lo : int) (hi : int) (seed : seed) : int * seed =
  let seed = Lehmer.next seed in
  (lo + int_of_float (float_of_int (hi - lo + 1) *. Lehmer.to_unit seed), seed)

(*****************************************************************************)
(* Programs *)
(*****************************************************************************)

type status = Exited | Interrupted

type 'a talk =
  | Done of 'a
  | Print of string * 'a talk
  | Read_line of (string -> 'a talk)
  | Read_key of (string -> 'a talk)
  | Random of int * (int -> 'a talk)
  | Spawn of unit talk * (status -> 'a talk)
  | Step of (unit -> 'a talk)

let return x = Done x
let print s = Print (s, Done ())
let read_line = Read_line (fun l -> Done l)
let read_key = Read_key (fun k -> Done k)
let random n = Random (n, fun i -> Done i)
let spawn child = Spawn (child, fun st -> Done st)
let step = Step (fun () -> Done ())

(* the rest of the program glued after the end of [m]: the requests
   stay the same, and where [m] was Done, [f] takes over *)
let rec ( let* ) (m : 'a talk) (f : 'a -> 'b talk) : 'b talk =
  match m with
  | Done x -> f x
  | Print (s, k) -> Print (s, ( let* ) k f)
  | Read_line k -> Read_line (fun l -> ( let* ) (k l) f)
  | Read_key k -> Read_key (fun key -> ( let* ) (k key) f)
  | Random (n, k) -> Random (n, fun i -> ( let* ) (k i) f)
  | Spawn (child, k) -> Spawn (child, fun st -> ( let* ) (k st) f)
  (* [f] applied only when the step is taken: what follows is built as
     late as that *)
  | Step k -> Step (fun () -> ( let* ) (k ()) f)

let ask (question : string) : string talk =
  let* () = print question in
  read_line

(*****************************************************************************)
(* Running without a screen *)
(*****************************************************************************)

let run ?(seed = 1) (program : 'a talk) (answers : string list) : string =
  let out = Buffer.create 256 in
  let steps = ref 0 in
  (* the answers and the seed left, if the program reached its end: a
     spawned child's end is where its parent carries on *)
  let rec go : 'b. 'b talk -> string list -> seed -> (string list * seed) option =
   fun p answers seed ->
    match p, answers with
    | Done _, _ -> Some (answers, seed)
    | Print (s, k), _ -> Buffer.add_string out s; go k answers seed
    | Random (n, k), _ ->
        let i, seed = random_int 0 (n - 1) seed in
        go (k i) answers seed
    (* the answer echoed, as the paper would show it *)
    | Read_line k, a :: rest -> Buffer.add_string out (a ^ "\n"); go (k a) rest seed
    | Read_key k, a :: rest -> go (k a) rest seed
    | (Read_line _ | Read_key _), [] -> None
    | Spawn (child, k), _ -> (
        match go child answers seed with
        | Some (answers, seed) -> go (k Exited) answers seed
        | None -> None)
    (* a million steps: a program that doesn't end *)
    | Step k, _ ->
        incr steps;
        if !steps > 1_000_000 then None else go (k ()) answers seed
  in
  ignore (go program answers (initial_seed seed));
  Buffer.contents out

(*****************************************************************************)
(* The machine *)
(*****************************************************************************)

type machine = {
  program : unit talk;
  (* the parents waiting for their spawned child, the innermost first *)
  parents : (status -> unit talk) list;
  vt : Vt.t;
  tty : Line_discipline.t;
  (* bytes printed but not yet on the screen, at a baud rate *)
  (* newest first, joined when it goes to the screen: a string appended
     to at each print would be copied whole each time, and a BASIC loop
     prints thousands of lines a frame *)
  outbox : string list;
  (* characters a second, None: at once *)
  cps : float option;
  budget : float;
  seed : seed;
  (* the steps taken since the last frame *)
  steps : int;
}

(* bytes put in the outbox, and the outbox's bytes in order *)
let add (s : string) (outbox : string list) : string list = if s = "" then outbox else s :: outbox
let pending (m : machine) : string = String.concat "" (List.rev m.outbox)

(* everything in the outbox on the screen, unless a baud rate
   rations it (then [tick] does it) *)
let flush (m : machine) : machine =
  match m.cps with
  | None -> { m with vt = Vt.feed m.vt (pending m); outbox = [] }
  | Some _ -> m

(* the steps a frame may take: a program computing at length (BASIC's
   10 GOTO 10) runs this much a frame, then the screen is drawn and the
   keyboard read, Control-C included *)
let steps_per_frame = 20_000

(* at a baud rate, the bytes waiting for the line: past this, a program
   printing in a loop waits for them, as write(2) blocks on a tty whose
   output buffer is full *)
let output_buffer = 256
let full (m : machine) : bool = m.cps <> None && List.fold_left (fun n s -> n + String.length s) 0 m.outbox >= output_buffer

(* the program run until it reads, ends, or has taken its steps for
   this frame: its prints into the outbox, its random numbers drawn,
   the tty set to the mode its read needs *)
let rec advance (m : machine) : machine =
  match m.program with
  | Step k when m.steps < steps_per_frame && not (full m) -> advance { m with program = k (); steps = m.steps + 1 }
  | Step _ -> flush { m with tty = Line_discipline.set_mode m.tty Cooked }
  | Print (s, k) -> advance { m with program = k; outbox = add (Line_discipline.output s) m.outbox }
  | Random (n, k) ->
      let i, seed = random_int 0 (n - 1) m.seed in
      advance { m with program = k i; seed }
  | Spawn (child, k) -> advance { m with program = child; parents = k :: m.parents }
  | Done () when m.parents <> [] -> exit_child m Exited
  | Read_key _ -> flush { m with tty = Line_discipline.set_mode m.tty Raw }
  | Read_line _ | Done () -> flush { m with tty = Line_discipline.set_mode m.tty Cooked }

(* the innermost program over: its parent carries on, told how *)
and exit_child (m : machine) (st : status) : machine =
  match m.parents with
  | k :: parents -> advance { m with program = k st; parents }
  | [] -> advance { m with program = Done () }

let start ?baud ~(seed : int) ~(rows : int) ~(cols : int) (program : unit talk) : machine =
  advance
    {
      program;
      parents = [];
      vt = Vt.create ~rows ~cols;
      tty = Line_discipline.create ();
      outbox = [];
      (* a character is 10 bits on the line (a start bit, 8, a stop
         bit); the Model 33 had two stop bits, so its 110 baud were
         exactly 10 characters a second *)
      cps = Option.map (fun b -> float_of_int b /. if b = 110 then 11. else 10.) baud;
      budget = 0.;
      seed = initial_seed seed;
      steps = 0;
    }

(* a key at a time, since a line or a key given to the program can
   change the tty's mode for the next one *)
let input (m : machine) (bytes : string) : machine =
  let one (m : machine) (key : string) : machine =
    let tty, echo, events = Line_discipline.input m.tty key in
    let m = { m with tty; outbox = add echo m.outbox } in
    List.fold_left
      (fun (m : machine) (ev : Line_discipline.event) ->
        match ev, m.program with
        | Line l, Read_line k -> advance { m with program = k l }
        | Key s, Read_key k -> advance { m with program = k s }
        | (Interrupt | End_of_file), (Read_line _ | Read_key _ | Step _) -> exit_child m Interrupted
        (* typed ahead of a question, or after the end: dropped *)
        | _ -> m)
      m events
  in
  flush (List.fold_left one m (Line_discipline.split_keys bytes))

let tick (m : machine) (dt : float) : machine =
  (* a new frame: the steps counted again, a computing program resumed *)
  let m = match m.program with Step _ -> advance { m with steps = 0 } | _ -> { m with steps = 0 } in
  match m.cps with
  | None -> m
  | Some _ when m.outbox = [] -> { m with budget = 0. }
  | Some cps ->
      let budget = m.budget +. (dt *. cps) in
      let text = pending m in
      let n = min (int_of_float budget) (String.length text) in
      let rest = String.sub text n (String.length text - n) in
      { m with vt = Vt.feed m.vt (String.sub text 0 n); outbox = add rest []; budget = budget -. float_of_int n }

let screen (m : machine) = m.vt
let reading (m : machine) = m.outbox = [] && match m.program with Read_line _ | Read_key _ -> true | _ -> false
let finished (m : machine) = m.outbox = [] && match m.program with Done () -> true | _ -> false