├── README.md ├── calc ├── README.md ├── back │ ├── dune │ └── server.ml ├── calc.html ├── compile.sh ├── front │ ├── calc.ml │ └── dune └── proto │ ├── dune │ └── protocol.atd ├── count ├── compile.sh ├── count.html ├── count.ml └── dune ├── counter ├── compile.sh ├── counter.html ├── counter.ml └── dune ├── counters ├── compile.sh ├── counters.html ├── counters.ml └── dune ├── login ├── compile.sh ├── dune ├── login.html └── login.ml ├── signup ├── compile.sh ├── dune ├── signup.html └── signup.ml ├── tic-tac-toe ├── README.md ├── compile.sh ├── dune ├── ttt.css ├── ttt.html ├── ttt.ml ├── ttt_history.html ├── ttt_history.ml └── util.ml └── timer ├── compile.sh ├── dune ├── timer.html └── timer.ml /README.md: -------------------------------------------------------------------------------- 1 | Experiments with [Daniel Bünzli](https://erratique.ch/profile)'s 2 | [Brr](https://github.com/dbuenzli/brr). 3 | -------------------------------------------------------------------------------- /calc/README.md: -------------------------------------------------------------------------------- 1 | Talk to an http server to add two integers. 2 | -------------------------------------------------------------------------------- /calc/back/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name server) 3 | (preprocess (pps lwt_ppx)) 4 | (libraries cohttp-lwt-unix atdgen proto) 5 | ) 6 | -------------------------------------------------------------------------------- /calc/back/server.ml: -------------------------------------------------------------------------------- 1 | open Cohttp 2 | open Cohttp_lwt_unix 3 | 4 | module P = Proto.Protocol_j 5 | module SS = Set.Make(String) 6 | type state = SS.t 7 | 8 | (* deal with CORS, if we have to *) 9 | let add_access_control request response_headers = 10 | let request_headers = Request.headers request in 11 | match Header.get request_headers "Origin" with 12 | | Some origin -> 13 | let h = [ 14 | (* allow the origin, in lieu of responding with just 15 | wildcard "*" *) 16 | "Access-Control-Allow-Origin", origin; 17 | 18 | (* also allow credentials, in case request was made with 19 | "withCredentials = true" *) 20 | "Access-Control-Allow-Credentials", "true" 21 | ] in 22 | 23 | Header.add_list response_headers h 24 | 25 | | _ -> response_headers 26 | 27 | let server () = 28 | let mode = `TCP (`Port 8888) in 29 | let state_0 = SS.empty in 30 | let state_mvar = Lwt_mvar.create state_0 in 31 | 32 | let callback _conn req body = 33 | let headers = Header.init () in 34 | let not_found = `Not_found, "", headers in 35 | 36 | let%lwt (status, body, headers) = 37 | match Request.meth req with 38 | | `POST -> ( 39 | let%lwt body_s = Cohttp_lwt.Body.to_string body in 40 | try 41 | let f2b = P.front_to_back_of_string body_s in 42 | let%lwt state = Lwt_mvar.take state_mvar in 43 | let state, response = 44 | match f2b with 45 | | `NewSession -> 46 | let session = Int64.to_string (Random.int64 Int64.max_int) in 47 | let state = SS.add session state in 48 | state, `NewSession session 49 | 50 | | `Add (session, (a, b)) -> 51 | match SS.find_opt session state with 52 | | None -> state, `InvalidSession 53 | | Some _ -> state, `Add (a + b) 54 | in 55 | let%lwt () = Lwt_mvar.put state_mvar state in 56 | let b2f = P.string_of_back_to_front response in 57 | let headers = add_access_control req headers in 58 | Lwt.return (`OK, b2f, headers) 59 | 60 | with 61 | | Yojson.Json_error err_msg 62 | | Atdgen_runtime__Oj_run.Error err_msg -> 63 | let response_headers = Header.add headers 64 | "content-type" "text/plain" in 65 | let body = "Error: " ^ err_msg in 66 | Lwt.return (`OK, body, response_headers) 67 | ) 68 | 69 | | _ -> 70 | Lwt.return not_found 71 | in 72 | 73 | Server.respond_string ~headers ~status ~body () 74 | in 75 | 76 | Server.create ~mode (Server.make ~callback ()) 77 | 78 | let _ = 79 | (* see https://github.com/mirage/ocaml-cohttp/issues/511 *) 80 | Lwt.async_exception_hook := ignore; 81 | ignore (Lwt_main.run (server ())) 82 | 83 | -------------------------------------------------------------------------------- /calc/calc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 6 | 7 | 8 |7 | 8 | -------------------------------------------------------------------------------- /calc/compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dune build front/calc.bc.js 4 | dune build back/server.exe 5 | -------------------------------------------------------------------------------- /calc/front/calc.ml: -------------------------------------------------------------------------------- 1 | module Http = struct 2 | open Js_of_ocaml 3 | 4 | (* request are in the payload of body of a HTTP POST; replies are in 5 | the body of a POST's response *) 6 | let do_post ~url ~request send = 7 | let req = XmlHttpRequest.create() in 8 | req##_open (Js.string "post") (Js.string url) Js._true; 9 | 10 | let callback _ = 11 | match req##.readyState with 12 | | XmlHttpRequest.DONE -> ( 13 | match Js.Opt.to_option req##.responseText with 14 | | None -> () 15 | | Some s -> send (Js.to_string s) 16 | ) 17 | | _ -> () 18 | in 19 | req##.onreadystatechange := Js.wrap_callback callback; 20 | req##send (Js.some (Js.string request)) 21 | 22 | open Note 23 | 24 | let req url request = 25 | let response, send = E.create () in 26 | do_post ~url ~request send; 27 | response 28 | 29 | end 30 | 31 | open Note 32 | 33 | module P = Proto.Protocol_j 34 | 35 | module Protocol = struct 36 | 37 | let url = "http://localhost:8888/" 38 | 39 | let req : P.front_to_back -> P.back_to_front event = 40 | fun request -> 41 | let request_p = P.string_of_front_to_back request in 42 | let response_p = Http.req url request_p in 43 | E.map P.back_to_front_of_string response_p 44 | 45 | end 46 | 47 | module Adder : sig 48 | val create : (int * int) event -> int event 49 | end = 50 | struct 51 | type state = { 52 | session : P.session option; 53 | adds : (int * int) list 54 | } 55 | 56 | let add_add add adds = List.rev (add :: List.rev adds) 57 | let rem_add adds = match adds with [] -> [] | _ :: adds -> adds 58 | 59 | let do_add_add : state -> int * int -> state = 60 | fun s add -> { s with adds = add_add add s.adds} 61 | 62 | let do_req : state -> state * P.back_to_front event = 63 | fun s -> match s.session with 64 | | None -> s, (Protocol.req `NewSession) 65 | | Some _ when s.adds = [] -> s, E.never 66 | | Some session -> s, Protocol.req (`Add (session, List.hd s.adds)) 67 | 68 | let do_resp : state -> P.back_to_front -> state * int option = 69 | fun s resp -> match resp with 70 | | `Add i -> { s with adds = rem_add s.adds}, Some i 71 | | `NewSession session -> { s with session = Some session }, None 72 | | `InvalidSession -> { s with session = None }, None 73 | 74 | let create add_ev = 75 | let init = { session = None; adds = [] } in 76 | let def s = 77 | let do_req = E.map do_req (S.changes s) in 78 | let resp = E.join (E.Pair.snd do_req) in 79 | let do_resp = S.sample s ~on:resp do_resp in 80 | let result = E.Option.on_some @@ E.Pair.snd do_resp in 81 | let s'0 = E.Pair.fst do_req in 82 | let s'1 = E.Pair.fst do_resp in 83 | let s'2 = S.sample s ~on:add_ev do_add_add in 84 | let s' = S.hold init (E.select [s'0; s'1; s'2]) in 85 | s', (s', result) 86 | in 87 | let s, result = S.fix init def in 88 | Logr.hold (S.log s (fun _ -> ())); 89 | result 90 | end 91 | 92 | open Brr 93 | open Brr_note 94 | 95 | let v = Jstr.v 96 | 97 | let main root = 98 | let arg1 = El.input () in 99 | let arg2 = El.input () in 100 | let sum_el = El.span [] in 101 | let sum_button = El.(button [txt' "sum"]) in 102 | let table = El.table [ 103 | El.tr [El.(td [txt' "arg 1:"; arg1 ])]; 104 | El.tr [El.(td [txt' "arg 2:"; arg2 ])]; 105 | El.tr [El.td [sum_button; sum_el]]; 106 | ] in 107 | El.set_children root [table]; 108 | 109 | let int_of_value el = 110 | int_of_string (Jstr.to_string (El.prop El.Prop.value el)) 111 | in 112 | 113 | let get_args _ = 114 | try 115 | let arg1_i = int_of_value arg1 in 116 | let arg2_i = int_of_value arg2 in 117 | Some (arg1_i, arg2_i) 118 | with Failure _ -> 119 | None 120 | in 121 | 122 | let add_opt_ev = Evr.on_el Ev.click get_args sum_button in 123 | let add_ev = E.Option.on_some add_opt_ev in 124 | 125 | let sum_e = Adder.create add_ev in 126 | let sum_txt = E.map (fun i -> [El.txt' (string_of_int i)]) sum_e in 127 | Elr.set_children sum_el ~on:sum_txt 128 | 129 | 130 | let () = 131 | let id = "root" in 132 | match Document.find_el_by_id G.document (v id) with 133 | | None -> Console.(info [str (Printf.sprintf "element %S not found" id)]) 134 | | Some root -> 135 | main root 136 | -------------------------------------------------------------------------------- /calc/front/dune: -------------------------------------------------------------------------------- 1 | (executable 2 | (name calc) 3 | (libraries js_of_ocaml brr.note proto) 4 | (preprocess (pps js_of_ocaml-ppx)) 5 | ) 6 | 7 | -------------------------------------------------------------------------------- /calc/proto/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name proto) 3 | (libraries atdgen) 4 | ) 5 | 6 | (rule 7 | (targets protocol_j.ml protocol_j.mli) 8 | (deps protocol.atd) 9 | (action (run atdgen -j -j-std %{deps}))) 10 | 11 | (rule 12 | (targets protocol_t.ml protocol_t.mli) 13 | (deps protocol.atd) 14 | (action (run atdgen -t %{deps}))) 15 | -------------------------------------------------------------------------------- /calc/proto/protocol.atd: -------------------------------------------------------------------------------- 1 | type session = string 2 | 3 | type front_to_back = [ 4 | | NewSession 5 | | Add of (session * (int * int)) 6 | ] 7 | 8 | type back_to_front = [ 9 | | NewSession of session 10 | | Add of int 11 | | InvalidSession 12 | ] 13 | -------------------------------------------------------------------------------- /count/compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | dune build count.bc.js 4 | -------------------------------------------------------------------------------- /count/count.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 |