Как скомпилировать простой скрипт OCaml командной строки в Javascript

У меня есть простое приложение OCaml из командной строки, которое выполняет вычисление Sys.argv.(1) и выводит результат на стандартный вывод. Я могу скомпилировать его в Javascript с помощью js_of_ocaml, но это дает мне много ошибок о том, что caml_ml_output_char не определено. Я исправил эти ошибки, заглушив printfs, поэтому он работает, но зависает Firefox во время работы.

Как я могу аккуратно скомпилировать простой скрипт командной строки OCaml в веб-страницу на основе Javascript; без сохранения разветвленной версии или замораживания браузера?


person gmatht    schedule 28.05.2016    source источник


Ответы (2)


Вы, вероятно, захотите использовать веб-воркеры, так как запуск программного обеспечения, не разработанного для совместной многозадачности Javascript в потоке пользовательского интерфейса, может привести к блокировке браузера. Вы можете добавить следующий заголовок в начало файла OCaml, чтобы перегрузить обычные реализации OCaml Sys и print.

(* JsHeader.ml *)
let output_buffer_ = Buffer.create 1000
let flush x=let module J = Js.Unsafe in let () = J.call 
        (J.variable "postMessage") (J.variable "self")
        [|J.inject (Js.string (Buffer.contents output_buffer_))|]
     in Buffer.clear output_buffer_

let print_string = Buffer.add_string output_buffer_
let print_char = Buffer.add_char output_buffer_
let print_newline () = print_char '\n'
let print_endline s = print_string (s^"\n"); flush ()
let caml_ml_output_char = print_char
let printf fmt = Printf.bprintf output_buffer_ fmt
module Printf = struct
    include Printf
    let printf fmt = Printf.bprintf output_buffer_ fmt
end

Самый естественный способ передачи аргументов командной строки — через URL-адрес, отправленный веб-воркеру. Мы можем переопределить модуль Ocaml Sys, чтобы вместо этого читать ?argv как последовательность строк с нулевым завершением.

module Sys = struct
    let char_split delim s = (*Str.split is overkill*)
        let hd = ref "" in let l = ref [] in 
        String.iter (fun c -> 
            if c = delim
            then  (l := (!hd)::(!l); hd := "")
            else hd := (!hd) ^ (String.make 1 c)
        ) s;
        List.rev ((!hd)::(!l)) 
    let getenv x = List.assoc x Url.Current.arguments
    let argv = Array.of_list (char_split '\x00' (getenv "?argv"))
    let executable_name = argv.(0)
end

Теперь, когда мы ввели заголовок, мы можем ввести простую программу командной строки OCaml:

(* cli.ml *)
let _ = print_string (Array.fold_left (^) "" (Array.make 40 (String.lowercase (Sys.argv.(1)^"\n"))) )

Эта программа командной строки использует ОС для сброса вывода, но нам придется вручную сбрасывать вывод. Вы также можете отправить нулевой символ, чтобы Javascript знал, что команда завершена. Этого можно добиться, добавив следующий нижний колонтитул.

(* JsFooter.ml *)
let _ = flush stdout; print_endline "\x00" 

Мы можем объединить файлы и скомпилировать их следующим образом:

 cat JsHeader.ml cli.ml JsFooter.ml > merged.ml
 ocamlbuild -use-menhir -menhir "menhir" \
   -pp "camlp4o -I /opt/local/lib/ocaml/site-lib js_of_ocaml/pa_js.cmo" \
   -cflags -I,+js_of_ocaml,-I,+site-lib/js_of_ocaml -libs js_of_ocaml \
   -lflags -I,+js_of_ocaml,-I,+site-lib/js_of_ocaml merged.byte
 js_of_ocaml merged.byte

Теперь, когда мы создали файл merged.js, мы можем обернуть javascript в простую веб-страницу, например следующую:

<html>
<head>
<meta http-equiv="Content-Type" content="text/xhtml+xml; charset=UTF-8" />
<title>ml2js sample_cli</title>
<script type="text/javascript">
<!--
var worker;
function go () {
  var output=document.getElementById ("output");
  var argv = encodeURIComponent("/bin/sample_cli\0"+document.getElementById ("input").value);
  if (worker) {
    worker.terminate();
  }
  worker = new Worker ("sample_cli.js?argv="+argv);
  document.getElementById ("output").value="";
  worker.onmessage = function (m) {
    if (typeof m.data == 'string') {
    if (m.data == "\0\n") {
        output.scrollTop = output.scrollHeight
    } else {
        output.value+=m.data;
    }
    }
  }
}
//-->
</script>
</head>

<body onload=go()>
<textarea id="input" rows="2" cols="60" onkeyup="go()" onchange="go()" style="width:90%">SAMPLE_INPUT</textarea> 
<button onclick="go()">go</button><br>
<textarea id="output" rows="0" cols="60" style="width:100%;height:90%" readonly onload=go()>
Your browser does not seem to support Webworkers.
Try Firefox, Chrome or IE10+. 
</textarea>
</body>

</html>

См. http://www.dansted.org/app/bctl-plain.html для примера этого подхода в действии и https://github.com/gmatht/TimeLogicUnify/blob/master/ATL/js/webworker/ml2js.sh для скрипта, который добавляет соответствующие заголовки, нижние колонтитулы и т. д.

person gmatht    schedule 28.05.2016

Какую версию js_of_ocaml вы используете? Вы не должны получать ошибки с caml_ml_output_char. При работе на узле у вас должен быть правильно установлен sys.argv. В браузере Sys.argv имеет значение [|"a.out"|]. Пожалуйста, откройте проблему GitHub на https://github.com/ocsigen/js_of_ocaml/issues/new если у вас все еще есть проблема с этим.

person hhugo    schedule 30.05.2016
comment
Я использую 1.4-1build1 на Ubuntu. Даже если более поздние версии js_of_ocaml справились с задачей лучше, например, Sys.argv, предположительно, мне все равно нужно будет обернуть мой скрипт ml, как описано выше, чтобы сделать его действительно полезным в браузере? (Кстати, это, вероятно, лучше как комментарий, чем ответ?) - person gmatht; 31.05.2016
comment
Я изменил свой вопрос, упомянув, что меня особенно интересует веб-страница. - person gmatht; 22.07.2016