copied from js_of_ocaml/toplevel/examples/lwt_toplevel

This commit is contained in:
cqc
2024-02-10 16:52:36 -06:00
commit 0f1fd67e8a
20 changed files with 1391 additions and 0 deletions

82
b64.ml Normal file
View File

@ -0,0 +1,82 @@
(*
* Copyright (c) 2006-2009 Citrix Systems Inc.
* Copyright (c) 2010 Thomas Gazagnaire <thomas@gazagnaire.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*)
let default_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
let uri_safe_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
let padding = '='
let of_char ?(alphabet = default_alphabet) x =
if x = padding then 0 else String.index alphabet x
let to_char ?(alphabet = default_alphabet) x = alphabet.[x]
let decode ?alphabet input =
let length = String.length input in
let input =
if length mod 4 = 0 then input else input ^ String.make (4 - (length mod 4)) padding
in
let length = String.length input in
let words = length / 4 in
let padding =
match length with
| 0 -> 0
| _ when input.[length - 2] = padding -> 2
| _ when input.[length - 1] = padding -> 1
| _ -> 0
in
let output = Bytes.make ((words * 3) - padding) '\000' in
for i = 0 to words - 1 do
let a = of_char ?alphabet input.[(4 * i) + 0]
and b = of_char ?alphabet input.[(4 * i) + 1]
and c = of_char ?alphabet input.[(4 * i) + 2]
and d = of_char ?alphabet input.[(4 * i) + 3] in
let n = (a lsl 18) lor (b lsl 12) lor (c lsl 6) lor d in
let x = (n lsr 16) land 255 and y = (n lsr 8) land 255 and z = n land 255 in
Bytes.set output ((3 * i) + 0) (char_of_int x);
if i <> words - 1 || padding < 2 then Bytes.set output ((3 * i) + 1) (char_of_int y);
if i <> words - 1 || padding < 1 then Bytes.set output ((3 * i) + 2) (char_of_int z)
done;
Bytes.unsafe_to_string output
let encode ?(pad = true) ?alphabet input =
let length = String.length input in
let words = (length + 2) / 3 in
(* rounded up *)
let padding_len = if length mod 3 = 0 then 0 else 3 - (length mod 3) in
let output = Bytes.make (words * 4) '\000' in
let get i = if i >= length then 0 else int_of_char input.[i] in
for i = 0 to words - 1 do
let x = get ((3 * i) + 0) and y = get ((3 * i) + 1) and z = get ((3 * i) + 2) in
let n = (x lsl 16) lor (y lsl 8) lor z in
let a = (n lsr 18) land 63
and b = (n lsr 12) land 63
and c = (n lsr 6) land 63
and d = n land 63 in
Bytes.set output ((4 * i) + 0) (to_char ?alphabet a);
Bytes.set output ((4 * i) + 1) (to_char ?alphabet b);
Bytes.set output ((4 * i) + 2) (to_char ?alphabet c);
Bytes.set output ((4 * i) + 3) (to_char ?alphabet d)
done;
for i = 1 to padding_len do
Bytes.set output (Bytes.length output - i) padding
done;
if pad
then Bytes.unsafe_to_string output
else Bytes.sub_string output 0 (Bytes.length output - padding_len)

40
b64.mli Normal file
View File

@ -0,0 +1,40 @@
(*
* Copyright (c) 2006-2009 Citrix Systems Inc.
* Copyright (c) 2010 Thomas Gazagnaire <thomas@gazagnaire.com>
* Copyright (c) 2014-2016 Anil Madhavapeddy <anil@recoil.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*)
(** Base64 RFC4648 implementation.
Base64 is a group of similar binary-to-text encoding schemes that represent binary
data in an ASCII string format by translating it into a radix-64 representation. It
is specified in RFC 4648. *)
val default_alphabet : string
(** A 64-character string specifying the regular Base64 alphabet. *)
val uri_safe_alphabet : string
(** A 64-character string specifying the URI- and filename-safe Base64 alphabet. *)
val decode : ?alphabet:string -> string -> string
(** [decode s] decodes the string [s] that is encoded in Base64 format. Will leave
trailing NULLs on the string, padding it out to a multiple of 3 characters.
[alphabet] defaults to {!default_alphabet}.
@raise Not_found if [s] is not a valid Base64 string. *)
val encode : ?pad:bool -> ?alphabet:string -> string -> string
(** [encode s] encodes the string [s] into base64. If [pad] is false, no trailing padding
is added. [pad] defaults to [true], and [alphabet] to {!default_alphabet}. *)

28
colorize.fake.ml Normal file
View File

@ -0,0 +1,28 @@
open Js_of_ocaml
open Js_of_ocaml_tyxml
let text ~a_class:cl s = Tyxml_js.Html.(span ~a:[ a_class [ cl ] ] [ txt s ])
let ocaml = text
let highlight from_ to_ e =
match Js.Opt.to_option e##.textContent with
| None -> assert false
| Some x ->
let x = Js.to_string x in
let (`Pos from_) = from_ in
let to_ =
match to_ with
| `Pos n -> n
| `Last -> String.length x - 1
in
e##.innerHTML := Js.string "";
let span kind s =
if s <> ""
then
let span = Tyxml_js.Html.(span ~a:[ a_class [ kind ] ] [ txt s ]) in
Dom.appendChild e (Tyxml_js.To_dom.of_element span)
in
span "normal" (String.sub x 0 from_);
span "errorloc" (String.sub x from_ (to_ - from_));
span "normal" (String.sub x to_ (String.length x - to_))

39
colorize.higlo.ml Normal file
View File

@ -0,0 +1,39 @@
open Js_of_ocaml
open Js_of_ocaml_tyxml
let text ~a_class:cl s = Tyxml_js.Html.(span ~a:[ a_class [ cl ] ] [ txt s ])
let ocaml ~a_class:cl s =
let tks = Higlo.Lang.parse ~lang:"ocaml" s in
let span' cl (s, _) = Tyxml_js.Html.(span ~a:[ a_class [ cl ] ] [ txt s ]) in
let make_span = function
| Higlo.Lang.Bcomment s -> span' "comment" s
| Higlo.Lang.Constant s -> span' "constant" s
| Higlo.Lang.Directive s -> span' "directive" s
| Higlo.Lang.Escape s -> span' "escape" s
| Higlo.Lang.Id s -> span' "id" s
| Higlo.Lang.Keyword (level, s) -> span' (Printf.sprintf "kw%d" level) s
| Higlo.Lang.Lcomment s -> span' "comment" s
| Higlo.Lang.Numeric s -> span' "numeric" s
| Higlo.Lang.String s -> span' "string" s
| Higlo.Lang.Symbol (level, s) -> span' (Printf.sprintf "sym%d" level) s
| Higlo.Lang.Text s -> span' "text" s
| Higlo.Lang.Title (_, s) -> span' "text" s
in
Tyxml_js.Html.(div ~a:[ a_class [ cl ] ] (List.map make_span tks))
let highlight (`Pos from_) to_ e =
let _ =
List.fold_left
(fun pos e ->
match Js.Opt.to_option (Dom_html.CoerceTo.element e) with
| None -> pos
| Some e ->
let size = Js.Opt.case e##.textContent (fun () -> 0) (fun t -> t##.length) in
if pos + size > from_ && (to_ = `Last || `Pos pos < to_)
then e##.classList##add (Js.string "errorloc");
pos + size)
0
(Dom.list_of_nodeList e##.childNodes)
in
()

9
colorize.mli Normal file
View File

@ -0,0 +1,9 @@
open Js_of_ocaml
open Js_of_ocaml_tyxml
val text : a_class:string -> string -> [> Html_types.div_content ] Tyxml_js.Html.elt
val ocaml : a_class:string -> string -> [> Html_types.div_content ] Tyxml_js.Html.elt
val highlight :
[ `Pos of int ] -> [ `Last | `Pos of int ] -> Dom_html.element Js.t -> unit

150
dune Normal file
View File

@ -0,0 +1,150 @@
(executables
(names toplevel)
(libraries
js_of_ocaml-compiler
js_of_ocaml-tyxml
js_of_ocaml-toplevel
lwt
js_of_ocaml-lwt
;; not used directly
graphics
js_of_ocaml.deriving
react
reactiveData
str
dynlink
(select
ocp_indent.ml
from
(ocp-indent.lib -> ocp_indent.ok.ml)
(-> ocp_indent.fake.ml))
(select
colorize.ml
from
(higlo -> colorize.higlo.ml)
(!higlo -> colorize.fake.ml))
(select
graphics_support.ml
from
(js_of_ocaml-lwt.graphics -> graphics_support.enabled.ml)
(-> graphics_support.disabled.ml))
(select
ppx_support.ml
from
(js_of_ocaml-ppx -> ppx_support.enabled.ml)
(-> ppx_support.disabled.ml)))
(flags
(:standard -rectypes))
(link_flags
(:standard -linkall))
(modes byte js)
(js_of_ocaml
(link_flags (:standard))
(build_runtime_flags
(:standard
+toplevel.js
+dynlink.js
--file
%{dep:examples.ml}
--file
%{dep:test_dynlink.cmo}
--file
%{dep:test_dynlink.js}))
(flags
(:standard
--toplevel
(:include effects_flags.sexp))))
(modules
(:standard \ test_dynlink examples effects_flags))
(preprocess
(pps js_of_ocaml-ppx)))
(rule
(targets test_dynlink.cmo test_dynlink.cmi)
(action
(run ocamlc -c %{dep:test_dynlink.ml})))
(rule
(targets test_dynlink.js)
(action
(run %{bin:js_of_ocaml} --pretty --toplevel %{dep:test_dynlink.cmo})))
(rule
(targets export.txt)
(deps
(package js_of_ocaml-ppx)
(package js_of_ocaml)
(package js_of_ocaml-compiler)
(package js_of_ocaml-lwt)
(package js_of_ocaml-tyxml)
(package js_of_ocaml-toplevel))
(action
(run
jsoo_listunits
-o
%{targets}
stdlib
graphics
str
dynlink
js_of_ocaml-compiler.runtime
js_of_ocaml-lwt.graphics
js_of_ocaml-ppx.as-lib
js_of_ocaml.deriving
lwt
tyxml.functor
tyxml.functor:html_types.cmi
react
reactiveData
js_of_ocaml
js_of_ocaml-lwt
js_of_ocaml-tyxml
js_of_ocaml-toplevel)))
(executable
(name effects_flags)
(modules effects_flags))
(rule
(target effects_flags.sexp)
(action
(with-stdout-to
%{target}
(run ./effects_flags.exe sexp))))
(rule
(target effects_flags.txt)
(action
(with-stdout-to
%{target}
(run ./effects_flags.exe txt))))
(rule
(targets toplevel.js)
(action
(run
%{bin:js_of_ocaml}
compile
--pretty
%{read-strings:effects_flags.txt}
--Werror
--target-env
browser
--file
%{dep:examples.ml}
--file
%{dep:test_dynlink.cmo}
--file
%{dep:test_dynlink.js}
--export
%{dep:export.txt}
--toplevel
--disable
shortvar
%{dep:toplevel.bc}
-o
%{targets})))
(alias
(name default)
(deps toplevel.js toplevel.bc.js index.html))

2
dune-project Normal file
View File

@ -0,0 +1,2 @@
(lang dune 3.4)
(name oplevel)

15
effects_flags.ml Normal file
View File

@ -0,0 +1,15 @@
let enable b n =
let f = if b then "--enable" else "--disable" in
[ f; n ]
let () =
let major = String.split_on_char '.' Sys.ocaml_version |> List.hd |> int_of_string in
let has_effect = major >= 5 in
let l = enable has_effect "effects" in
match Sys.argv |> Array.to_list |> List.tl with
| "txt" :: [] -> List.iter print_endline l
| "sexp" :: [] ->
print_endline "(";
List.iter print_endline l;
print_endline ")"
| _ -> assert false

231
examples.ml Normal file
View File

@ -0,0 +1,231 @@
(** Overview *)
let x = 10 + 10
let y = x * 3
let c = String.make x 'a'
let sin1 = sin 1.
let rec fact n = if n = 0 then 1. else float n *. fact (n - 1)
let _ = Printf.printf "fact 20 = %f\n" (fact 20)
let _ = "abc" < "def"
(** Mutually recursive function *)
let rec even n =
match n with
| 0 -> true
| x -> odd (x - 1)
and odd n =
match n with
| 0 -> false
| x -> even (x - 1)
(** Mutually recursive module *)
module rec Odd : sig
val odd : int -> bool
end = struct
let odd x = if x = 0 then false else Even.even (pred x)
end
and Even : sig
val even : int -> bool
end = struct
let even x = if x = 0 then true else Odd.odd (pred x)
end
(** Reactive dom *)
open Js_of_ocaml
open Js_of_ocaml_lwt
open Js_of_ocaml_tyxml
let display x =
Dom.appendChild (Dom_html.getElementById "output") (Tyxml_js.To_dom.of_element x)
module RList = ReactiveData.RList
let rl, rhandle = RList.create []
let li_rl = RList.map (fun x -> Tyxml_js.Html.(li [ txt x ])) rl
let ul_elt = Tyxml_js.R.Html.ul li_rl
let init =
let _ = RList.snoc "# cons \"some string\"" rhandle in
let _ = RList.snoc "# snoc \"some other\"" rhandle in
let _ = RList.snoc "# insert \"anywhere\" 1" rhandle in
let _ = RList.snoc "# remove 1" rhandle in
()
let snoc s = RList.snoc s rhandle
let cons s = RList.cons s rhandle
let insert s pos = RList.insert s pos rhandle
let remove pos = RList.remove pos rhandle
let time_signal =
let s, set = React.S.create (Sys.time ()) in
let rec loop () : unit Lwt.t =
set (Sys.time ());
Lwt.bind (Lwt_js.sleep 1.) loop
in
Lwt.async loop;
s
let div_elt =
Tyxml_js.(
Html.(
div
[ h4
[ txt "Uptime is "
; R.Html.txt
(React.S.map (fun s -> string_of_int (int_of_float s)) time_signal)
; txt " s"
]
; ul_elt
]))
let _ = display div_elt
(** Graphics: Draw *)
open Graphics_js
let () =
loop [ Mouse_motion ] (function { mouse_x = x; mouse_y = y } -> fill_circle x y 5)
(** Graphics: Draw chars*)
open Graphics_js
let () =
loop [ Mouse_motion; Key_pressed ] (function
| { key = '\000'; _ } -> ()
| { mouse_x = x; mouse_y = y; key } ->
moveto x y;
draw_char key)
(** Graphics: PingPong *)
open Js_of_ocaml_lwt
open Graphics_js
let c = 3
let x0 = 0
and x1 = size_x ()
and y0 = 0
and y1 = size_y ()
let draw_ball x y =
set_color foreground;
fill_circle x y c
let state = ref (Lwt.task ())
let wait () = fst !state
let rec pong_aux x y dx dy =
draw_ball x y;
let new_x = x + dx and new_y = y + dy in
let new_dx = if new_x - c <= x0 || new_x + c >= x1 then -dx else dx
and new_dy = if new_y - c <= y0 || new_y + c >= y1 then -dy else dy in
Lwt.bind (wait ()) (fun () -> pong_aux new_x new_y new_dx new_dy)
let rec start () =
let t = Lwt.task () in
let _, w = !state in
state := t;
clear_graph ();
Lwt.wakeup w ();
Lwt.bind (Lwt_js.sleep (1. /. 60.)) start
let pong x y dx dy = pong_aux x y dx dy
let _ = pong 111 87 2 3
let _ = pong 28 57 5 3
let _ = start ()
(** Effect handler *)
module Txn : sig
type 'a t
val atomically : (unit -> unit) -> unit
val ref : 'a -> 'a t
val ( ! ) : 'a t -> 'a
val ( := ) : 'a t -> 'a -> unit
end = struct
open Effect
open Effect.Deep
type 'a t = 'a ref
type _ Effect.t += Update : 'a t * 'a -> unit Effect.t
let atomically f =
let comp =
match_with
f
()
{ retc = (fun x _ -> x)
; exnc =
(fun e rb ->
rb ();
raise e)
; effc =
(fun (type a) (e : a Effect.t) ->
match e with
| Update (r, v) ->
Some
(fun (k : (a, _) continuation) rb ->
let old_v = !r in
r := v;
continue k () (fun () ->
r := old_v;
rb ()))
| _ -> None)
}
in
comp (fun () -> ())
let ref = ref
let ( ! ) = ( ! )
let ( := ) r v = perform (Update (r, v))
end
let example () =
let open Txn in
let exception Res of int in
let r = ref 10 in
Printf.printf "T0: %d\n" !r;
try
atomically (fun () ->
r := 20;
r := 21;
Printf.printf "T1: Before abort %d\n" !r;
raise (Res !r) |> ignore;
Printf.printf "T1: After abort %d\n" !r;
r := 30)
with Res v ->
Printf.printf "T0: T1 aborted with %d\n" v;
Printf.printf "T0: %d\n" !r

View File

@ -0,0 +1 @@
let init _ = ()

View File

@ -0,0 +1 @@
let init elt = Graphics_js.open_canvas elt

43
indent.ml Normal file
View File

@ -0,0 +1,43 @@
open Js_of_ocaml
let textarea (textbox : Dom_html.textAreaElement Js.t) : unit =
let rec loop s acc (i, pos') =
try
let pos = String.index_from s pos' '\n' in
loop s ((i, (pos', pos)) :: acc) (succ i, succ pos)
with _ -> List.rev ((i, (pos', String.length s)) :: acc)
in
let rec find (l : (int * (int * int)) list) c =
match l with
| [] -> assert false
| (i, (lo, up)) :: _ when up >= c -> c, i, lo, up
| (_, (_lo, _up)) :: rem -> find rem c
in
let v = textbox##.value in
let pos =
let c1 = textbox##.selectionStart and c2 = textbox##.selectionEnd in
if Js.Opt.test (Js.Opt.return c1) && Js.Opt.test (Js.Opt.return c2)
then
let l = loop (Js.to_string v) [] (0, 0) in
Some (find l c1, find l c2)
else None
in
let f =
match pos with
| None -> fun _ -> true
| Some ((_c1, line1, _lo1, _up1), (_c2, line2, _lo2, _up2)) ->
fun l -> l >= line1 + 1 && l <= line2 + 1
in
let v = Ocp_indent.indent (Js.to_string v) f in
textbox##.value := Js.string v;
match pos with
| Some ((c1, line1, _lo1, up1), (c2, line2, _lo2, up2)) ->
let l = loop v [] (0, 0) in
let lo1'', up1'' = List.assoc line1 l in
let lo2'', up2'' = List.assoc line2 l in
let n1 = max (c1 + up1'' - up1) lo1'' in
let n2 = max (c2 + up2'' - up2) lo2'' in
let () = (Obj.magic textbox)##setSelectionRange n1 n2 in
textbox##focus;
()
| None -> ()

3
indent.mli Normal file
View File

@ -0,0 +1,3 @@
open Js_of_ocaml
val textarea : Dom_html.textAreaElement Js.t -> unit

233
index.html Normal file
View File

@ -0,0 +1,233 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>OCaml toplevel</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" />
<style>
code, kbd, pre, samp {
font-family: Menlo,Monaco,Consolas,monospace;
}
body,html {
height: 100%;
background-color:#eee;
}
#toplevel-container {
width: 50%;
background-color: black;
color: #ccc;
overflow: auto;
overflow-x: hidden;
height: 100%;
float:left;
padding:10px;
padding-top: 20px;
}
#toplevel-container pre#output {
padding: 0px;
}
#toplevel-container #output {
background-color:transparent;
color: #ccc;
border: none;
line-height:18px;
font-size: 12px;
margin-bottom: 0px;
}
#toplevel-container textarea {
width:90%;
line-height:18px;
font-size: 12px;
background-color: transparent;
color: #fff;
border: 0;
resize: none;
outline: none;
font-family: Menlo,Monaco,Consolas,monospace;
font-weight: bold;
float:left;
margin: 0px;
padding:0px;
}
#toplevel-container #sharp {
float: left;
line-height:18px;
font-size: 12px;
font-family: Menlo,Monaco,Consolas,monospace;
white-space: pre;
}
.sharp:before{
content:"# ";
line-height:18px;
font-size: 12px;
font-family: Menlo,Monaco,Consolas,monospace;
}
.caml{
color:rgb(110, 110, 201);
}
#toplevel-side{
position:relative;
width:45%;
height: 100%;
overflow: auto;
text-align:justify;
float:left;
margin-left:30px;
}
#toplevel-side ul{
padding: 0px;
list-style-type: none;
}
.stderr {
color: #d9534f;
}
.stdout {
}
.errorloc{
border-bottom-width: 3px;
border-bottom-style: solid;
border-bottom-color: red;
}
canvas {
border: 1px dashed black;
float: left;
margin: 7px;
}
#output canvas {
background-color: #464646;
float: none;
display: block;
border: 1px dashed while;
margin: 7px;
}
#output img {
display:block;
}
#toplevel-examples {
width: 270px;
float: left;
}
#toplevel-examples .list-group-item{
padding: 5px 15px;
}
#btn-share {
float:right;
margin-top:-20px;
background-color:rgb(92, 129, 184);
border-color: rgb(70, 75, 128);
padding: 1px 5px;
display:none;
}
.clear { clear:both; }
.sharp .id { color: #59B65C ; font-style: italic }
.sharp .kw0 { color: rgb(64, 75, 190); font-weight: bold ;}
.sharp .kw1 { color: rgb(150, 0, 108); font-weight: bold ;}
.sharp .kw2 { color: rgb(23, 100, 42); font-weight: bold ;}
.sharp .kw3 { color: #59B65C; font-weight: bold ;}
.sharp .kw4 { color: #59B65C; font-weight: bold ;}
.sharp .comment { color: green ; font-style: italic ; }
.sharp .string { color: #6B6B6B; font-weight: bold ; }
.sharp .text { }
.sharp .numeric { color: #729AAF; }
.sharp .directive { font-style: italic ; color : #EB00FF; } ;
.sharp .escape { color: #409290 ; }
.sharp .symbol0 { color: orange ; font-weight: bold ; }
.sharp .symbol1 { color: #993300 ; font-weight: bold ; }
.sharp .constant { color: rgb(0, 152, 255); }
</style>
<script type="text/javascript">
window.onhashchange = function() { window.location.reload() }
var hash = window.location.hash.replace(/^#/,"");
var fields = hash.split(/&/);
var prefix = "";
var version = "";
var main = "toplevel.bc.js";
for(var f in fields){
var data = fields[f].split(/=/);
if(data[0] == "version"){
version = data[1].replace(/%20|%2B/g,"+");
}
else if (data[0] == "separate"){
main = "toplevel.bc.js";
}
}
function load_script(url){
var fileref=document.createElement('script');
fileref.setAttribute("type","text/javascript");
fileref.setAttribute("src", prefix+(version==""?"":(version+"/"))+url);
document.getElementsByTagName("head")[0].appendChild(fileref);
}
load_script("exported-unit.cmis.js");
load_script(main);
</script>
</head>
<body>
<div id="toplevel-container">
<pre id="output"></pre>
<div>
<div id="sharp" class="sharp"></div>
<textarea id="userinput">Loading ...</textarea>
<button type="button" class="btn btn-default"
id="btn-share">Share</button>
</div>
</div>
<div id="toplevel-side">
<h3>Js_of_ocaml</h3>
<h4>A compiler from OCaml bytecode to Javascript.</h4>
<p>It makes OCaml programs that run on Web browsers. It is
easy to install as it works with an existing installation of OCaml,
with no need to recompile any library. It comes with bindings for a
large part of the browser APIs.</p>
<p>This web-based OCaml toplevel is compiled using Js_of_ocaml.</p>
<h4>Command</h4>
<table class="table table-striped table-condensed">
<tbody class>
<tr>
<td>Enter/Return</td>
<td>Submit code</td>
</tr>
<tr>
<td>Ctrl + Enter</td>
<td>Newline</td>
</tr>
<tr>
<td>Up / Down</td>
<td>Browse history</td>
</tr>
<tr>
<td>Ctrl + l</td>
<td>Clear display</td>
</tr>
<tr>
<td>Ctrl + k</td>
<td>Reset toplevel</td>
</tr>
<tr>
<td>Tab</td>
<td>Indent code</td>
</tr>
</tbody>
</table>
<h4>Try to execute samples</h4>
<div id="toplevel-examples" class="list-group"></div>
<canvas width=200 height=200 id="test-canvas"></canvas>
<h4 class="clear">See the generated javascript code</h4>
<pre id="last-js">
</pre>
</div>
</body>
</html>

3
ocp_indent.fake.ml Normal file
View File

@ -0,0 +1,3 @@
let indent s _in_lines = s
(* ocp-indent not available *)

14
ocp_indent.ok.ml Normal file
View File

@ -0,0 +1,14 @@
let _ = Approx_lexer.enable_extension "lwt"
let indent s in_lines =
let output =
{ IndentPrinter.debug = false
; config = IndentConfig.default
; in_lines
; indent_empty = true
; adaptive = true
; kind = IndentPrinter.Print (fun s acc -> acc ^ s)
}
in
let stream = Nstream.of_string s in
IndentPrinter.proceed output stream IndentBlock.empty ""

1
ppx_support.disabled.ml Normal file
View File

@ -0,0 +1 @@
let init () = ()

1
ppx_support.enabled.ml Normal file
View File

@ -0,0 +1 @@
let init () = Ast_mapper.register "js_of_ocaml" (fun _ -> Ppx_js.mapper)

3
test_dynlink.ml Normal file
View File

@ -0,0 +1,3 @@
let _ = print_endline "Dynlink OK"
let f () = print_endline "Test_dynlink.f Ok"

492
toplevel.ml Normal file
View File

@ -0,0 +1,492 @@
(* Js_of_ocaml toplevel
* http://www.ocsigen.org/js_of_ocaml/
* Copyright (C) 2011 Jérôme Vouillon
* Laboratoire PPS - CNRS Université Paris Diderot
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, with linking exception;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*)
open Js_of_ocaml
open Js_of_ocaml_lwt
open Js_of_ocaml_tyxml
open Js_of_ocaml_toplevel
open Lwt
let compiler_name = "OCaml"
let by_id s = Dom_html.getElementById s
let by_id_coerce s f =
Js.Opt.get (f (Dom_html.getElementById s)) (fun () -> raise Not_found)
let do_by_id s f = try f (Dom_html.getElementById s) with Not_found -> ()
(* load file using a synchronous XMLHttpRequest *)
let load_resource_aux filename url =
Js_of_ocaml_lwt.XmlHttpRequest.perform_raw ~response_type:XmlHttpRequest.ArrayBuffer url
>|= fun frame ->
if frame.Js_of_ocaml_lwt.XmlHttpRequest.code = 200
then
Js.Opt.case
frame.Js_of_ocaml_lwt.XmlHttpRequest.content
(fun () -> Printf.eprintf "Could not load %s\n" filename)
(fun b ->
Sys_js.update_file ~name:filename ~content:(Typed_array.String.of_arrayBuffer b))
else ()
let load_resource scheme ~prefix ~path:suffix =
let url = scheme ^ suffix in
let filename = Filename.concat prefix suffix in
Lwt.async (fun () -> load_resource_aux filename url);
Some ""
let setup_pseudo_fs ~load_cmis_from_server =
Sys_js.mount ~path:"/dev/" (fun ~prefix:_ ~path:_ -> None);
Sys_js.mount ~path:"/http/" (load_resource "http://");
Sys_js.mount ~path:"/https/" (load_resource "https://");
Sys_js.mount ~path:"/ftp/" (load_resource "ftp://");
if load_cmis_from_server then Sys_js.mount ~path:"/home/" (load_resource "filesys/")
let exec' s =
let res : bool = JsooTop.use Format.std_formatter s in
if not res then Format.eprintf "error while evaluating %s@." s
module Version = struct
type t = int list
let split_char ~sep p =
let len = String.length p in
let rec split beg cur =
if cur >= len
then if cur - beg > 0 then [ String.sub p beg (cur - beg) ] else []
else if sep p.[cur]
then String.sub p beg (cur - beg) :: split (cur + 1) (cur + 1)
else split beg (cur + 1)
in
split 0 0
let split v =
match
split_char
~sep:(function
| '+' | '-' | '~' -> true
| _ -> false)
v
with
| [] -> assert false
| x :: _ ->
List.map
int_of_string
(split_char
~sep:(function
| '.' -> true
| _ -> false)
x)
let current : t = split Sys.ocaml_version
let compint (a : int) b = compare a b
let rec compare v v' =
match v, v' with
| [ x ], [ y ] -> compint x y
| [], [] -> 0
| [], y :: _ -> compint 0 y
| x :: _, [] -> compint x 0
| x :: xs, y :: ys -> (
match compint x y with
| 0 -> compare xs ys
| n -> n)
end
let setup_toplevel () =
Clflags.debug := true;
JsooTop.initialize ();
Sys.interactive := false;
if Version.compare Version.current [ 4; 07 ] >= 0 then exec' "open Stdlib";
exec'
"module Lwt_main = struct\n\
\ let run t = match Lwt.state t with\n\
\ | Lwt.Return x -> x\n\
\ | Lwt.Fail e -> raise e\n\
\ | Lwt.Sleep -> failwith \"Lwt_main.run: thread didn't return\"\n\
\ end";
let header1 = Printf.sprintf " %s version %%s" compiler_name in
let header2 =
Printf.sprintf " Compiled with Js_of_ocaml version %s" Sys_js.js_of_ocaml_version
in
exec' (Printf.sprintf "Format.printf \"%s@.\" Sys.ocaml_version;;" header1);
exec' (Printf.sprintf "Format.printf \"%s@.\";;" header2);
exec' "#enable \"pretty\";;";
exec' "#disable \"shortvar\";;";
Ppx_support.init ();
Toploop.add_directive
"load_js"
(Toploop.Directive_string (fun name -> Js.Unsafe.global##load_script_ name))
{ section = "js_of_ocaml-toplevel-example"; doc = "Load the given javascript file" };
Sys.interactive := true;
()
let resize ~container ~textbox () =
Lwt.pause ()
>>= fun () ->
textbox##.style##.height := Js.string "auto";
textbox##.style##.height
:= Js.string (Printf.sprintf "%dpx" (max 18 textbox##.scrollHeight));
container##.scrollTop := container##.scrollHeight;
Lwt.return ()
let setup_printers () =
exec'
"let _print_error fmt e = Format.pp_print_string fmt (Js_of_ocaml.Js_error.to_string \
e)";
Topdirs.dir_install_printer Format.std_formatter Longident.(Lident "_print_error");
exec' "let _print_unit fmt (_ : 'a) : 'a = Format.pp_print_string fmt \"()\"";
Topdirs.dir_install_printer Format.std_formatter Longident.(Lident "_print_unit")
let setup_examples ~container ~textbox =
let r = Regexp.regexp "^\\(\\*+(.*)\\*+\\)$" in
let all = ref [] in
(try
let ic = open_in "/static/examples.ml" in
while true do
let line = input_line ic in
match Regexp.string_match r line 0 with
| Some res ->
let name =
match Regexp.matched_group res 1 with
| Some s -> s
| None -> assert false
in
all := `Title name :: !all
| None -> all := `Content line :: !all
done;
assert false
with _ -> ());
let example_container = by_id "toplevel-examples" in
let _ =
List.fold_left
(fun acc tok ->
match tok with
| `Content line -> line ^ "\n" ^ acc
| `Title name ->
let a =
Tyxml_js.Html.(
a
~a:
[ a_class [ "list-group-item" ]
; a_onclick (fun _ ->
textbox##.value := (Js.string acc)##trim;
Lwt.async (fun () ->
resize ~container ~textbox ()
>>= fun () ->
textbox##focus;
Lwt.return_unit);
true)
]
[ txt name ])
in
Dom.appendChild example_container (Tyxml_js.To_dom.of_a a);
"")
""
!all
in
()
(* we need to compute the hash form href to avoid different encoding behavior
across browser. see Url.get_fragment *)
let parse_hash () =
let frag = Url.Current.get_fragment () in
Url.decode_arguments frag
let rec iter_on_sharp ~f x =
Js.Opt.iter (Dom_html.CoerceTo.element x) (fun e ->
if Js.to_bool (e##.classList##contains (Js.string "sharp")) then f e);
match Js.Opt.to_option x##.nextSibling with
| None -> ()
| Some n -> iter_on_sharp ~f n
let setup_share_button ~output =
do_by_id "btn-share" (fun e ->
e##.style##.display := Js.string "block";
e##.onclick :=
Dom_html.handler (fun _ ->
(* get all ocaml code *)
let code = ref [] in
Js.Opt.iter
output##.firstChild
(iter_on_sharp ~f:(fun e ->
code :=
Js.Opt.case e##.textContent (fun () -> "") Js.to_string :: !code));
let code_encoded = B64.encode (String.concat "" (List.rev !code)) in
let url, is_file =
match Url.Current.get () with
| Some (Url.Http url) -> Url.Http { url with Url.hu_fragment = "" }, false
| Some (Url.Https url) -> Url.Https { url with Url.hu_fragment = "" }, false
| Some (Url.File url) -> Url.File { url with Url.fu_fragment = "" }, true
| _ -> assert false
in
let frag =
let frags = parse_hash () in
let frags = List.remove_assoc "code" frags @ [ "code", code_encoded ] in
Url.encode_arguments frags
in
let uri = Url.string_of_url url ^ "#" ^ frag in
let append_url str =
let dom =
Tyxml_js.Html.(
p [ txt "Share this url : "; a ~a:[ a_href str ] [ txt str ] ])
in
Dom.appendChild output (Tyxml_js.To_dom.of_element dom)
in
Lwt.async (fun () ->
Lwt.catch
(fun () ->
if is_file
then failwith "Cannot shorten url with file scheme"
else
let uri =
Printf.sprintf
"http://is.gd/create.php?format=json&url=%s"
(Url.urlencode uri)
in
Lwt.bind (Js_of_ocaml_lwt.Jsonp.call uri) (fun o ->
let str = Js.to_string o##.shorturl in
append_url str;
Lwt.return_unit))
(fun exn ->
Format.eprintf
"Could not generate short url. reason: %s@."
(Printexc.to_string exn);
append_url uri;
Lwt.return_unit));
Js._false))
let setup_js_preview () =
let ph = by_id "last-js" in
let runcode : string -> 'a = Js.Unsafe.global##.toplevelEval in
Js.Unsafe.global##.toplevelEval
:= fun bc ->
ph##.innerHTML := Js.string bc;
runcode bc
let current_position = ref 0
let highlight_location loc =
let x = ref 0 in
let output = by_id "output" in
let first =
Js.Opt.get (output##.childNodes##item !current_position) (fun _ -> assert false)
in
iter_on_sharp first ~f:(fun e ->
incr x;
let _file1, line1, col1 = Location.get_pos_info loc.Location.loc_start in
let _file2, line2, col2 = Location.get_pos_info loc.Location.loc_end in
if !x >= line1 && !x <= line2
then
let from_ = if !x = line1 then `Pos col1 else `Pos 0 in
let to_ = if !x = line2 then `Pos col2 else `Last in
Colorize.highlight from_ to_ e)
let append colorize output cl s =
Dom.appendChild output (Tyxml_js.To_dom.of_element (colorize ~a_class:cl s))
module History = struct
let data = ref [| "" |]
let idx = ref 0
let get_storage () =
match Js.Optdef.to_option Dom_html.window##.localStorage with
| exception _ -> raise Not_found
| None -> raise Not_found
| Some t -> t
let setup () =
try
let s = get_storage () in
match Js.Opt.to_option (s##getItem (Js.string "history")) with
| None -> raise Not_found
| Some s ->
let a = Json.unsafe_input s in
data := a;
idx := Array.length a - 1
with _ -> ()
let push text =
let l = Array.length !data in
let n = Array.make (l + 1) "" in
!data.(l - 1) <- text;
Array.blit !data 0 n 0 l;
data := n;
idx := l;
try
let s = get_storage () in
let str = Json.output !data in
s##setItem (Js.string "history") str
with Not_found -> ()
let current text = !data.(!idx) <- text
let previous textbox =
if !idx > 0
then (
decr idx;
textbox##.value := Js.string !data.(!idx))
let next textbox =
if !idx < Array.length !data - 1
then (
incr idx;
textbox##.value := Js.string !data.(!idx))
end
let run _ =
let container = by_id "toplevel-container" in
let output = by_id "output" in
let textbox : 'a Js.t = by_id_coerce "userinput" Dom_html.CoerceTo.textarea in
let sharp_chan = open_out "/dev/null0" in
let sharp_ppf = Format.formatter_of_out_channel sharp_chan in
let caml_chan = open_out "/dev/null1" in
let caml_ppf = Format.formatter_of_out_channel caml_chan in
let execute () =
let content = Js.to_string textbox##.value##trim in
let content' =
let len = String.length content in
if try content <> "" && content.[len - 1] <> ';' && content.[len - 2] <> ';'
with _ -> true
then content ^ ";;"
else content
in
current_position := output##.childNodes##.length;
textbox##.value := Js.string "";
History.push content;
JsooTop.execute true ~pp_code:sharp_ppf ~highlight_location caml_ppf content';
resize ~container ~textbox ()
>>= fun () ->
container##.scrollTop := container##.scrollHeight;
textbox##focus;
Lwt.return_unit
in
let history_down _e =
let txt = Js.to_string textbox##.value in
let pos = textbox##.selectionStart in
try
if String.length txt = pos then raise Not_found;
let _ = String.index_from txt pos '\n' in
Js._true
with Not_found ->
History.current txt;
History.next textbox;
Js._false
in
let history_up _e =
let txt = Js.to_string textbox##.value in
let pos = textbox##.selectionStart - 1 in
try
if pos < 0 then raise Not_found;
let _ = String.rindex_from txt pos '\n' in
Js._true
with Not_found ->
History.current txt;
History.previous textbox;
Js._false
in
let meta e =
let b = Js.to_bool in
b e##.ctrlKey || b e##.altKey || b e##.metaKey
in
let shift e = Js.to_bool e##.shiftKey in
(* setup handlers *)
textbox##.onkeyup :=
Dom_html.handler (fun _ ->
Lwt.async (resize ~container ~textbox);
Js._true);
textbox##.onchange :=
Dom_html.handler (fun _ ->
Lwt.async (resize ~container ~textbox);
Js._true);
textbox##.onkeydown :=
Dom_html.handler (fun e ->
match e##.keyCode with
| 13 when not (meta e || shift e) ->
Lwt.async execute;
Js._false
| 13 ->
Lwt.async (resize ~container ~textbox);
Js._true
| 09 ->
Indent.textarea textbox;
Js._false
| 76 when meta e ->
output##.innerHTML := Js.string "";
Js._true
| 75 when meta e ->
setup_toplevel ();
Js._false
| 38 -> history_up e
| 40 -> history_down e
| _ -> Js._true);
(Lwt.async_exception_hook :=
fun exc ->
Format.eprintf "exc during Lwt.async: %s@." (Printexc.to_string exc);
match exc with
| Js_error.Exn e ->
let e = Js_error.to_error e in
Firebug.console##log e##.stack
| _ -> ());
Lwt.async (fun () ->
resize ~container ~textbox ()
>>= fun () ->
textbox##focus;
Lwt.return_unit);
Graphics_support.init (by_id_coerce "test-canvas" Dom_html.CoerceTo.canvas);
Sys_js.set_channel_flusher caml_chan (append Colorize.ocaml output "caml");
Sys_js.set_channel_flusher sharp_chan (append Colorize.ocaml output "sharp");
Sys_js.set_channel_flusher stdout (append Colorize.text output "stdout");
Sys_js.set_channel_flusher stderr (append Colorize.text output "stderr");
let readline () =
Js.Opt.case
(Dom_html.window##prompt (Js.string "The toplevel expects inputs:") (Js.string ""))
(fun () -> "")
(fun s -> Js.to_string s ^ "\n")
in
Sys_js.set_channel_filler stdin readline;
setup_share_button ~output;
setup_examples ~container ~textbox;
setup_pseudo_fs ~load_cmis_from_server:false;
setup_toplevel ();
setup_js_preview ();
setup_printers ();
History.setup ();
textbox##.value := Js.string "";
(* Run initial code if any *)
try
let code = List.assoc "code" (parse_hash ()) in
textbox##.value := Js.string (B64.decode code);
Lwt.async execute
with
| Not_found -> ()
| exc ->
Firebug.console##log_3
(Js.string "exception")
(Js.string (Printexc.to_string exc))
exc
let _ =
Dom_html.window##.onload :=
Dom_html.handler (fun _ ->
run ();
Js._false)