Source file cell.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
(*---------------------------------------------------------------------------
  Copyright (c) 2026 The Raven authors. All rights reserved.
  SPDX-License-Identifier: ISC
  ---------------------------------------------------------------------------*)

(* ───── Identifiers ───── *)

type id = string

let () = Random.self_init ()

let fresh_id () =
  let n = 12 in
  let chars = "abcdefghijklmnopqrstuvwxyz0123456789" in
  let b = Bytes.create (n + 2) in
  Bytes.unsafe_set b 0 'c';
  Bytes.unsafe_set b 1 '_';
  for i = 0 to n - 1 do
    Bytes.unsafe_set b (i + 2) chars.[Random.int 36]
  done;
  Bytes.unsafe_to_string b

(* ───── Outputs ───── *)

type output =
  | Stdout of string
  | Stderr of string
  | Error of string
  | Display of { mime : string; data : string }

type Format.stag += Display_tag of { mime : string; data : string }

(* ───── Attributes ───── *)

type attrs = { collapsed : bool; hide_source : bool }

let default_attrs = { collapsed = false; hide_source = false }

(* ───── Cells ───── *)

type t =
  | Code of {
      id : id;
      source : string;
      language : string;
      outputs : output list;
      execution_count : int;
      attrs : attrs;
    }
  | Text of { id : id; source : string; attrs : attrs }

let code ?id ?(language = "ocaml") ?(attrs = default_attrs) source =
  let id = match id with Some id -> id | None -> fresh_id () in
  Code { id; source; language; outputs = []; execution_count = 0; attrs }

let text ?id ?(attrs = default_attrs) source =
  let id = match id with Some id -> id | None -> fresh_id () in
  Text { id; source; attrs }

let id = function Code c -> c.id | Text t -> t.id
let source = function Code c -> c.source | Text t -> t.source
let attrs = function Code c -> c.attrs | Text t -> t.attrs

let set_source s = function
  | Code c -> Code { c with source = s }
  | Text t -> Text { t with source = s }

let set_attrs a = function
  | Code c -> Code { c with attrs = a }
  | Text t -> Text { t with attrs = a }

let set_outputs os = function
  | Code c -> Code { c with outputs = os }
  | Text _ as t -> t

let apply_cr s =
  let lines = String.split_on_char '\n' s in
  let apply_line line =
    match String.rindex_opt line '\r' with
    | None -> line
    | Some i -> String.sub line (i + 1) (String.length line - i - 1)
  in
  String.concat "\n" (List.map apply_line lines)

let rec append_or_coalesce o acc = function
  | [] -> List.rev (o :: acc)
  | [ Stdout prev ] -> begin
      match o with
      | Stdout next -> List.rev (Stdout (apply_cr (prev ^ next)) :: acc)
      | _ -> List.rev (o :: Stdout prev :: acc)
    end
  | out :: rest -> append_or_coalesce o (out :: acc) rest

let append_output o = function
  | Code c -> Code { c with outputs = append_or_coalesce o [] c.outputs }
  | Text _ as t -> t

let clear_outputs = function
  | Code c -> Code { c with outputs = [] }
  | Text _ as t -> t

let increment_execution_count = function
  | Code c -> Code { c with execution_count = c.execution_count + 1 }
  | Text _ as t -> t