From: Gabriel Scherer <gabriel.scherer@gmail.com>
To: "Çagdas Bozman" <cagdas.bozman@ocamlpro.com>
Cc: caml-list@inria.fr
Subject: Re: [Caml-list] try ocaml website
Date: Mon, 26 Dec 2011 09:09:25 +0100 [thread overview]
Message-ID: <CAPFanBH4tC9hgqmcW13tJS-t7e7JJNwCwwh0JmY1g-gfeeU37A@mail.gmail.com> (raw)
In-Reply-To: <CAAFKszFL5mP5Sr9+SavZWv=wFBC8vmunYdNS5Ob4Zebyk0v3zQ@mail.gmail.com>
[-- Attachment #1: Type: text/plain, Size: 1296 bytes --]
I played with the toplevel a bit, but was frustrated by the limitation
of one-liner input.
Even in an interactive toplevel it is nice, I think, to be able to
write multiline programs.
The simple solution is to make ";;" mandatory, which clearly delimits
the end of user input (instead of considering all 'enter' as end of
input). The tryocaml toplevel is however very lenient regarding use of
";;", and I suppose this is a deliberate design choice.
I have implemented an heuristic to allow multiline input, based on a
past discussion with Nicolas Pouillard about, if I remember correctly,
Scala's toplevel behavior. The idea is to accumulate lines of input
until the parser either succeeds, or there is a syntax error; when it
just consumes all the line without producing a result, we wait for
more input.
Patch attached.
On Fri, Dec 23, 2011 at 7:49 PM, Çagdas Bozman
<cagdas.bozman@ocamlpro.com> wrote:
>> The only "real" thing that it's missing is a way to reset the toplevel
>> without reloading the page. It's the same problem as with the real
>> toplevel now that I think about it, but I didn't think of reloading the
>> page directly!
>>
>
> You can clear the editor view by using the command "clear()".
>
> --
> Çagdas Bozman <cagdas.bozman@ocamlpro.com>
[-- Attachment #2: 0001-Change-the-toplevel-to-allow-multiline-input.patch --]
[-- Type: text/x-diff, Size: 10335 bytes --]
From 263598f929d5d79d8dfe4ccb341565c0053abe06 Mon Sep 17 00:00:00 2001
From: Gabriel Scherer <gabriel.scherer@gmail.com>
Date: Sun, 25 Dec 2011 10:40:41 +0100
Subject: [PATCH] Change the toplevel to allow multiline input
The heuristic is the following: if parsing the input consumed the
whole line without raising a syntax error, we assume that it is
unfinished multiline input. We end the input as soon as parsing raises
a syntactic error, or succeeds.
Note that this may sometimes terminate input earlier than
intended. For example, if you try to write:
let rec fac = function
| 0 -> 1
| n -> ...
then parsing will return a result at the end of the "| 0 -> 1", and
evaluate this incomplete definition. You have to write instead:
let rec fac = begin function
| 0 -> 1
| n -> ...
end
So that `begin` make all prefixes of the input (before `end`) invalid.
---
tryocaml/toplevel.ml | 240 +++++++++++++++++++++++++++++++++++--------------
1 files changed, 171 insertions(+), 69 deletions(-)
diff --git a/tryocaml/toplevel.ml b/tryocaml/toplevel.ml
index d3f0ac7..2fb5bad 100644
--- a/tryocaml/toplevel.ml
+++ b/tryocaml/toplevel.ml
@@ -75,39 +75,6 @@ let start ppf =
Toploop.input_name := "";
exec ppf "open Tutorial"
-let at_bol = ref true
-let consume_nl = ref false
-
-let refill_lexbuf s p ppf buffer len =
- if !consume_nl then begin
- let l = String.length s in
- if (!p < l && s.[!p] = '\n') then
- incr p
- else if (!p + 1 < l && s.[!p] = '\r' && s.[!p + 1] = '\n') then
- p := !p + 2;
- consume_nl := false
- end;
- if !p = String.length s then
- 0
- else begin
- let c = s.[!p] in
- incr p;
- buffer.[0] <- c;
- if !at_bol then Format.fprintf ppf "# ";
- at_bol := (c = '\n');
- if c = '\n' then
- Format.fprintf ppf "@."
- else
- Format.fprintf ppf "%c" c;
- 1
- end
-
-let ensure_at_bol ppf =
- if not !at_bol then begin
- Format.fprintf ppf "@.";
- consume_nl := true; at_bol := true
- end
-
let update_lesson_text () =
if !Tutorial.this_lesson <> 0 then
try
@@ -172,9 +139,6 @@ let text_of_html html =
done;
Buffer.contents b
-
-
-
let update_debug_message =
let b = Buffer.create 100 in
Tutorial.debug_fun := (fun s -> Buffer.add_string b s; Buffer.add_string b "<br/>");
@@ -195,39 +159,176 @@ let update_debug_message =
with _ -> ()
-let loop s ppf buffer =
- let need_terminator = ref true in
- for i = 0 to String.length s - 2 do
- if s.[i] = ';' && s.[i+1] = ';' then need_terminator := false;
- done;
- let s = if !need_terminator then s ^ ";;" else s in
- let lb = Lexing.from_function (refill_lexbuf s (ref 0) ppf) in
- begin try
- while true do
- begin
- try
- let phr = !Toploop.parse_toplevel_phrase lb in
- ensure_at_bol ppf;
- Buffer.clear buffer;
- Tutorial.print_debug s;
- ignore (Toploop.execute_phrase true ppf phr);
- let res = Buffer.contents buffer in
- Tutorial.check_step ppf s res;
- update_lesson_text ();
- update_lesson_number ();
- update_lesson_step_number ();
- with
- End_of_file ->
- raise End_of_file
- | x ->
- ensure_at_bol ppf;
- Errors.report_error ppf x
- end;
- update_debug_message ();
- done
- with End_of_file -> ()
- end
+(* auxiliary type and functions for `loop`, see below *)
+type 'a parse_status =
+ | Error of exn * int
+ | Success of 'a * int
+ | Need_more_input
+
+let try_parse str p =
+ let pos = ref p in
+ let len = String.length str in
+ let lb =
+ (* add a space at the end so that input ending with ';;'
+ don't raise Need_more_input *)
+ Lexing.from_function (fun output _len ->
+ if !pos = len then (incr pos; ' '; 0)
+ else begin
+ output.[0] <- str.[!pos];
+ incr pos;
+ 1
+ end) in
+ try
+ let result = !Toploop.parse_toplevel_phrase lb in
+ Success (result, lb.Lexing.lex_last_pos)
+ with exn ->
+ if !pos = len + 1 then Need_more_input
+ else Error (exn, lb.Lexing.lex_last_pos)
+let execute_phrase phrase ppf output_buffer =
+ try
+ Buffer.clear output_buffer;
+ ignore (Toploop.execute_phrase true ppf phrase);
+ let res = Buffer.contents output_buffer in
+ Tutorial.check_step ppf s res;
+ update_lesson_text ();
+ update_lesson_number ();
+ update_lesson_step_number ();
+ with exn ->
+ Errors.report_error ppf exn
+
+let skip_whitespace s pos =
+ let rec loop i =
+ if i = String.length s then None
+ else match s.[i] with
+ | '\r' | '\n' | '\t' | ' ' -> loop (i+1)
+ | _ -> Some i in
+ loop pos
+
+let format_string pos len ppf str =
+ let last_was_r = ref false in
+ for i = pos to pos + len - 1 do
+ match str.[i] with
+ | '\r' ->
+ if !last_was_r then Format.fprintf ppf "\r";
+ last_was_r := true;
+ | c ->
+ if c = '\n' then Format.fprintf ppf "@."
+ else if !last_was_r then Format.fprintf ppf "\r";
+ Format.fprintf ppf "%c" c;
+ last_was_r := false;
+ done
+
+(* `loop` is called for each line entered in the toplevel.
+
+ In order to allow for multi-line input, we use the following
+ heuristic: if parsing the line didn't raise any syntax error, but
+ didn't succeed in parsing a complete line, we simply accumulate the
+ input into an input buffer, and wait for the next call to `loop`.
+
+ Once a syntax error is encountered, or we have succeeded in parsing
+ a whole phrase, we show the result and clear the input buffer.
+
+ For example, if the user enters "let x =", it is not a syntax
+ error, and we wait for the next line of input. If it is "1", we
+ have the complete phrase "let x = 1" which we execute in the
+ toplevel. If it is "1 in", we wait againt for the next line. Note
+ that his heuristic is imperfect: when the user writes "let x =\n
+ 1\n", he may have wished to end with "in x + x", but we decide that
+ the phrase stops here.
+
+ The return value of `loop` is the rest of the input, after the last
+ phrase that could be parsed.
+
+ The code is complexified by two aspects:
+
+ - We try to be lenient in asking the user to close its phrases
+ using ';;'. She is not forced to use it, and when we see that the
+ phrase is unfinished we try to add ';;' at the end and retry
+ parsing.
+
+ - We need to print the user input to the output HTML buffer, but we
+ don't do it by batch, we try to do phrase per phrase.. For example,
+ entering "1;; 2" will not print "1;; 2", then the result of the two
+ phrases, but "# 1;;", then the result of this phrase, and "# 2;;",
+ and the result of that phrase.
+*)
+let loop =
+ let input_buffer = Buffer.create 80 in
+fun line ppf output_buffer ->
+ (* last_pos is the length of the partially entered multiline input
+ that has already been printed back to the user *)
+ let last_pos = Buffer.length input_buffer in
+ Buffer.add_string input_buffer line;
+ let input = Buffer.contents input_buffer in
+ let input_closed = input ^ ";;" in
+ let format_phrase marker str suffix pos len =
+ Format.fprintf ppf "%c %a%s@." marker
+ (format_string pos len) str suffix in
+ (* parse a phrase starting at position `pos` in the input string;
+ the first parsing attempt is handled differently below,
+ as it may be incomplete *)
+ let rec parse_next pos =
+ match skip_whitespace input pos with
+ | None -> ""
+ | Some pos ->
+ match try_parse input pos with
+ | Error (exn, offset) ->
+ let len = offset+1 in
+ format_phrase '#' input "" pos len;
+ Errors.report_error ppf exn;
+ parse_next (pos + len)
+ | Success (phrase, offset) ->
+ let len = offset+1 in
+ format_phrase '#' input "" pos len;
+ execute_phrase phrase ppf output_buffer;
+ parse_next (pos + len)
+ | Need_more_input ->
+ begin match try_parse input_closed pos with
+ | Error _ | Need_more_input ->
+ begin match skip_whitespace input pos with
+ | None -> ""
+ | Some i -> String.sub input i (String.length input - i)
+ end
+ | Success (phrase, _) ->
+ let len = String.length input - pos in
+ format_phrase '#' input ";;" pos len;
+ execute_phrase phrase ppf output_buffer;
+ ""
+ end
+ in
+ (* if this is not the first line of input,
+ we use '>' rather than '#' as a visual prompt marker,
+ to let the user know that she's continuing the phrase. *)
+ let marker = if last_pos = 0 then '#' else '>' in
+ match try_parse input 0 with
+ | Error (exn, pos) ->
+ let len = pos + 1 - last_pos in
+ format_phrase marker input "" last_pos len;
+ Buffer.clear input_buffer;
+ Errors.report_error ppf exn;
+ parse_next (last_pos + len)
+ | Success (first_phrase, pos) ->
+ let len = pos + 1 - last_pos in
+ format_phrase marker input "" last_pos len;
+ Buffer.clear input_buffer;
+ execute_phrase first_phrase ppf output_buffer;
+ parse_next (last_pos + len)
+ | Need_more_input ->
+ (* if we need to close to get the phrase,
+ there are no further phrases *)
+ match try_parse input_closed 0 with
+ | Error _ | Need_more_input ->
+ let len = String.length input - last_pos in
+ format_phrase marker input "" last_pos len;
+ Buffer.add_char input_buffer '\n';
+ ""
+ | Success (phrase, _) ->
+ let len = String.length input - last_pos in
+ format_phrase marker input ";;" last_pos len;
+ Buffer.clear input_buffer;
+ execute_phrase phrase ppf output_buffer;
+ ""
let _ =
Tutorial.message_fun := (fun s ->
@@ -305,7 +406,8 @@ let run _ =
history_bckwrd := !history;
history_frwrd := [];
textbox##value <- Js.string "";
- loop s ppf buffer;
+ let remaining_input = loop s ppf buffer in
+ textbox##value <- Js.string remaining_input;
make_code_clickable ();
textbox##focus();
container##scrollTop <- container##scrollHeight;
--
1.7.5.4
next prev parent reply other threads:[~2011-12-26 8:09 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2011-12-23 18:00 Fabrice Le Fessant
2011-12-23 18:40 ` Dominique Martinet
2011-12-23 18:49 ` Çagdas Bozman
2011-12-23 19:10 ` Dominique Martinet
2011-12-23 19:29 ` Jérémie Dimino
2011-12-23 19:39 ` Dominique Martinet
2011-12-23 21:35 ` oliver
2011-12-23 19:36 ` Adrien
2011-12-26 8:09 ` Gabriel Scherer [this message]
2011-12-27 20:23 ` Fabrice Le Fessant
2011-12-23 18:40 ` oliver
2011-12-24 8:51 ` Stéphane Glondu
2011-12-24 10:16 ` oliver
2011-12-24 10:31 ` Dominique Martinet
2011-12-23 18:54 ` Török Edwin
2011-12-23 21:27 ` oliver
2011-12-23 18:56 ` Ashish Agarwal
2011-12-23 19:15 ` Philippe Strauss
2011-12-23 21:14 ` Norman Hardy
2011-12-23 21:18 ` William Le Ferrand
2011-12-23 21:32 ` oliver
2011-12-26 8:50 ` Mihamina Rakotomandimby
2011-12-26 9:05 ` Esther Baruk
2012-01-03 22:12 ` Philippe Wang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=CAPFanBH4tC9hgqmcW13tJS-t7e7JJNwCwwh0JmY1g-gfeeU37A@mail.gmail.com \
--to=gabriel.scherer@gmail.com \
--cc=cagdas.bozman@ocamlpro.com \
--cc=caml-list@inria.fr \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox