=begin pod

=head1 NAME

REPL - a more easily configurable REPL

=head1 SYNOPSIS

=begin code :lang<raku>

use REPL;

# Start a Read, Evaluate, Print Loop
repl;

=end code

=head1 DESCRIPTION

The REPL module is a re-imagining of the REPL (Read, Evaluate, Print Loop)
functionality as offered by Rakudo before the 2024.11 release.  It provides
both a programmable interface, as well as a ready made C<"repl"> CLI.

=head1 COMMAND LINE INTERFACE

=begin code

# Simplest invocation
$ repl

# Pre-loading module Foo from local distribution
$ repl -I. -MFoo

# Invocation with some custom settings
$ repl --the-prompt='[:index:] :HHMM:' --symbols=🦋,🔥
=end code
=begin output
[0] 20:51 🦋 if 42 {
[0] 20:51 🔥 say "foo"
[0] 20:51 🔥 }
foo
[0] 20:52 🦋 
=end output

The REPL command-line interface can be invoked with named arguments that
have the same name as the named arguments to the C<REPL.new> call.  They
are:

=item --editor - the editor to use (default: Any)
=item --header - whether to show the full header (default: yes)
=item --multi-line-ok - can expression span more than one line (default: yes)
=item --output-method - the output method to be used (default: gist)
=item --symbols - symbols for prompt state (default: ">,*")
=item --the-prompt - string for prompt (default: "[:index:] :symbol: ")

It is also possible to specify C<-I> and C<-M> arguments for pre-loading
any module while starting the REPL.

Any other command-line arguments will be ignored.

=head1 SUBROUTINES

=head2 repl

=begin code :lang<raku>

repl;

=end code

The C<repl> subroutine creates a new C<REPL> object for that context
and returns that.  When called in sink context, it will activate the
interactive Read, Evaluate, Print Loop.  This is the most common usage
when debugging your code.

=begin code :lang<raku>
#line 666 foo.raku
if 42 -> $answer {
    repl "Looking for clues", :$answer;
}
=end code
=begin output
Looking for clues
answer: 42
[0] >
=end output

Note that when the C<repl> subroutine is used in a debugging mode, it
will not show any header, other than the one that is specified with
the positional argument (in this case "Looking for clues").

If no explicit heafer is specified, it will show the location of the
L<C<callframe>|https://docs.raku.org/routine/callframe> that the code
is currently in (in this case that would have been: line 666 in the file
"foo.raku").

Finally it will show the value of any named arguments to help you
debug your code (in this case "answer: 32").

It is also possible to save the C<REPL> object at one place in the
code, and actually run the REPL at a later time in another scope.

If you just press ENTER when using the REPL as a debugging aid in
this manner, then it will be interpreted the same as "=quit" for
convenience (especially if the C<repl> sub is called in a loop).

=begin code :lang<raku>

my $repl = do {
    my $a = 42;
    repl
}

# ...

# activate REPL later, allowing access to $a even though that
# variable is no longer in scope
$repl.run;

=end code

=head2 standard-completions

The C<standard-completions> subroutine provides the standard completion
logic if no C<:additional-completions> argument has been specified with
either C<REPL.new> or the C<repl> subroutine.

It is provided to allow it to be added with the <:additional-completions>
argument, and to allow testing of the standard completion logic.

=head2 uniname-words

The C<uniname-words> subroutine provides the same functionality as
the C<uniname-words> subroutine provided by the
L<C<uniname-words>|https://raku.land/zef:lizmat/uniname-words>
distribution B<if> that distribution is installed.

Otherwise it will always return C<Nil>.

=head2 RUNNING A REPL

The C<REPL> role is what usually gets punned into a class.

=begin code :lang<raku>

my $repl = REPL.new;

$repl.run;

=end code

Same as above, but with all named arguments spelled out:

=begin code :lang<raku>

my $repl = REPL.new:
  :editor(Any),    # or "Readline", "LineEditor", "Linenoise"
  :output-method<gist>,  # or "Str", "raku"
  :out = $*OUT,
  :err = $*ERR,
  :val = $*OUT,
  :header,
  :multi-line-ok,
  :the-prompt("[:index:] :symbol: "),
  :symbols(">", "*"),
  :is-win($*DISTRO.is-win),
  :@completions,
  :@additional-completions,
  :compiler<Raku>,
;

=end code

The C<REPL> role embodies the information needed to run a Read,
Evaluate, Print Loop.  It allows for these named arguments:

=head3 :editor

Optional.  String indicating which editor logic to be used.

If the C<INSIDE_EMACS> environment variable is set with a true
value, then the C<Fallback> editor will be used.

If it can be determined from the environment that the process is
running inside the "rlwrap" wrapper, then the C<Fallback> editor
will be used.

If the C<RAKUDO_LINE_EDITOR> environment variable is set, then
its contents will be assumed as an indication of preference and
will first be tried.  If that fails, an error message will be
shown.

Whatever is then the value, that value will be used to create a
L<C<Prompt>|https://raku.land/zef:lizmat/Prompt> object.

=head3 :output-method

String.  The name of the method to be called to display the value
of an expression.  This defaults to the value of the
C<RAKU_REPL_OUTPUT_METHOD> environment variable, or to "gist" if
that has not been specified.

Used value available with the C<.output-method> method.

=head3 :header

Boolean.  Indicate whether to show the REPL header upon entry.
Defaults to C<True>.

Used value available with the C<.header> method.

=head3 :out

Optional. The C<:out> named argument specifies the value of C<$*OUT>
whenever a command is executed. If not specified, or specified with
an undefined value, will assume the value of C<$*OUT> at command
execution time.

=head3 :err

Optional. The C<:err> named argument specifies the value of C<$*ERR>
whenever a command is executed. If not specified, or specified with
an undefined value, will assume the value of C<$*ERR> at command
execution time.

=head3 :multi-line-ok

Optional, Boolean. Indicate whether it is ok to interprete multiple
lines of input as a single statement to evaluate.  Defaults to
C<True> unless the C<RAKUDO_DISABLE_MULTILINE> environment variable
has been specified with a true value.

Used value available with the C<.multi-line-ok> method.

=head3 :the-prompt

Optional.  Specifies what will be shown to the user before the user
can enter characters.  Defaults to what has been specified with the
C<RAKUDO_REPL_PROMPT> environment variable, or C<"[:index:] :symbol: ">.

Supports all of the expansions offered by the
L<C<Prompt::Expand>|https://raku.land/zef:lizmat/Prompt::Expand>
distribution.

Note that if there is a prompt (implicitely) specified, the string
":symbol: " will be added if there is no ":symbol:" specified, to
make sure the user actually sees a prompting symbol, and to make
specifying a user prompt a bit easier.

The expanded prompt is also available with the C<.the-prompt> method.

=head3 :symbols

Optional.  Specifies the symbols that should be used for the
C<":symbol:"> placeholder in the different REPL states.  Defaults
to what has been specified as a comma-separated list with the
C<RAKUDO_REPL_SYMBOLS> environment variable.  Defaults to
C«">", "*"» if that is not specified.

Currently the following states are recognized:
=item 0 - accepting expression to be evaluated
=item 1 - previous expression not complete, accepting continuation

=head3 :is-win

Boolean.  Indicate whether certain OS dependent checks should assume
Windows semantics.  Defaults to C<$*DISTRO.is-win>.

Used value available with the C<.is-win> method.

=head3 :completions

A C<List> of strings to be used tab-completions.  If none are specified,
then a default Raku set of completions will be used.

Used value available with the C<.completions> method.

=head3 :additional-completions

=begin code

# completion that uppercases whole line if ended with a !
sub shout($line, $pos) {
    ($line.chop.uc,) if $pos == $line.chars && $line.ends-with("!")
}

my $repl = REPL.new(:additional-completions(&shout));

=end code

A C<List> of C<Callables> to be called to produce tab-completions.
If none are specified, the C<standard-completions> will be assumed.

Each C<Callable> is expected to accept two positional arguments:
the line that has been entered so far, and the position of the cursor.
It is expected to return a (potentially) empty C<List> with the
new state of the line (so including everything before and after
the completion).

=head3 :prompt

The L<C<Prompt>|https://raku.land/zef:lizmat/Prompt> object to be
used.  If specified, overrides anything related to the <C:editor>
named argument.  If not specified, will use whatever was (implicitely)
specified with C<:editor>.

=head3 :compiler

String.  The HLL compiler to be used.  This defaults to "Raku", which
is the only compiler supported at this time.

Used value available with the C<.compiler> method.

=head2 method run

Actually run the REPL.

=head1 OTHER METHODS

=head2 err

The object to be used for error output.  Defaults to C<$*ERR>.

=head2 history

Expected to take no arguments, and return an object that represents
the (possibly persistent) history of the REPL's interactive
sessions.

By default it will first look for a C<RAKUDO_HIST> environment variable
and return an C<IO::Path> object for that.  If that environment
variable is not specified, will check the C<$*HOME> and C<$*TMPDIR>
dynamic variables for the existence of a C<.raku> subdirectory
in that.  If found, will return an C<IO::Path> for the
"rakudo-history" file in that subdirectory and try to create
that if it didn't exist yet (and produce an error message if
that failed).

=head2 out

The object to be used for standard output.  Defaults to C<$*OUT>.

=head2 prompt

The L<C<Prompt>|https://raku.land/zef:lizmat/Prompt> object to be
used when obtaining input from the user.  Also handles the C<read>,
C<readline>, C<load-history>, C<add-history>, C<save-history> and
C<editor-name> methods.

=head2 supports-completions

Returns a C<Bool> indicating whether the selected editor supports
completions.

=head2 teardown

Expected to take no arguments.  Will be called whenever the user
indicates that they want to exit the REPL.  Will call the
C<save-history> method by default.

=head2 val

The object to be used for outputting values that weren't shown
already.  Defaults to C<$*OUT>.

=head1 USER COMMANDS

The following REPL commands are currently supported: if a command is not
recognized, then C<Raku> code will be assumed and executed if possible.
Note that all REPL commands start with "C<=>" to prevent confusion with
possibly legal Raku code.

=head2 completions

Shows an introduction to completions.  Can be shortened to "=com".

=head2 context

Allows creation of and switching between two or more contexts: "new"
creates a new context and switches to it, "switch" switches to an
already existing context, and "list" shows the available contexts.
Can be shortened to "=con".

=head2 edit

Edit the file given, or the last file that was saved with =write.
Can be shortened to "=ed".

=head2 exit

Leaves the REPL.  Can be shortened all the way to "=ex".

=head2 help

Shows a list of available commands.  Can be shortened all the way to "=h".

=head2 info

Shows the name of the editor logic being used.  Can be shortened to
to "=inf".

=head2 introduction

Shows a general introduction.  Can be shortened to "=int".

=head2 output [method]

Shows the current output method.  If a second argument is specified, it
indicates the name of the output method to be used from now on.  Typical
values are "raku", "Str", "gist".  Can be shortened all the way to "=o".

=head2 quit

Leaves the REPL.  Can be shortened all the way to "=q", and is thus the
shortest way to leave the REPL with a REPL command.

=head2 read [path]

Read the code from the file with the indicated path and compiles and
executes it.  Remembers the path name so that subsequent =write
commands need not have it specified.

=head2 reset

Reset the status of the REPL as if it was freshly entered.

=head2 stack

Show the caller stack from where the REPL has been called.  Only
makes sense if the REPL is being called from within a program,
rather than from the command line.  Can be shortened to "=s".

=head2 write [path]

Write all lines entered that did B<not> produce any output to the
indicated path.  Remembers the path name from =read and =write so
that subsequent =write commands need not have it specified.

=head1 TAB COMPLETIONS

If the C<supports-completions> method returns C<True>, the standard
tab-completion logic will provide:
=item all relevant items from the CORE:: namespace
=item any relevant items from the direct context (such as a REPL command)
=item \123 wil change integer value to superscript: ¹²³, subscript: ₁₂₃
=item foo! will tab-complete to FOO, foo, Foo

Additionally, if the L<C<uniname-words>|https://raku.land/zef:lizmat/uniname-words>
module is installed:
=item any unclosed L<B<\\c[>|https://docs.raku.org/syntax/%5Cc>
sequence will tab-complete on the names of Unicode code-points
=item any B<\word> will tab-complete to the actual codepoints

=head1 ENVIRONMENT VARIABLES

These environment variables will override default settings.

=head2 RAKUDO_REPL_PROMPT

The prompt shown to the user.  May contain escape sequences as supported
by the L<C<Prompt.expand>|https://raku.land/zef:lizmat/Prompt#method-expand>
method.

=head2 RAKUDO_REPL_SYMBOLS

A comma separated list of symbols representing the states of the REPL.
Defaults to C«>,*>».

=head2 RAKUDO_HIST

Path where the history file is / should be stored.

=head2 RAKU_REPL_OUTPUT_METHOD

The name of the method with which to show results to the user.

=head2 RAKUDO_DISABLE_MULTILINE

Whether multi-line evaluations should be disabled or not.

=head1 AUTHOR

Elizabeth Mattijsen <liz@raku.rocks>

Source can be located at: https://codeberg.org/lizmat/REPL . Comments and
Pull Requests are welcome.

If you like this module, or what I'm doing more generally, committing to a
L<small sponsorship|https://github.com/sponsors/lizmat/>  would mean a great
deal to me!

=head1 COPYRIGHT AND LICENSE

Copyright 2024, 2025, 2026 Elizabeth Mattijsen

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.

=end pod

# vim: expandtab shiftwidth=4
