diff --git a/collects/xrepl/doc-utils.rkt b/collects/xrepl/doc-utils.rkt new file mode 100644 index 0000000000..fe15ecd335 --- /dev/null +++ b/collects/xrepl/doc-utils.rkt @@ -0,0 +1,80 @@ +#lang racket/base + +(require scribble/manual scribble/core scribble/decode + racket/list racket/sandbox) + +(provide (all-from-out scribble/manual) + RL GUIDE cmd defcmd check-all-documented) + +(define RL '(lib "readline/readline.scrbl")) +(define GUIDE '(lib "scribblings/guide/guide.scrbl")) + +(define commands + (let ([c #f]) + (λ () + (unless c + (define e (call-with-trusted-sandbox-configuration + (λ () (make-evaluator 'racket/base)))) + (e '(require xrepl/xrepl)) + (e '(current-namespace (module->namespace 'xrepl/xrepl))) + (set! c (e '(for/list ([c (in-list commands-list)]) + (list (car (command-names c)) + (cdr (command-names c)) + (command-argline c) + (command-blurb c))))) + (kill-evaluator e)) + c))) +(define documented '()) + +(define (cmd* name0 . more) + (define name (if (symbol? name0) name0 (string->symbol name0))) + (define full-name + (or (and (assq name (commands)) name) + (for/or ([c (in-list (commands))]) (and (memq name (cadr c)) (car c))) + (error 'cmd "unknown command: ~s" name))) + (define content + (litchar (let ([s (format ",~a" name)]) + (if (pair? more) (apply string-append s " " more) s)))) + (link-element "plainlink" content `(xrepl ,(format "~a" full-name)))) + +(define-syntax-rule (cmd name more ...) (cmd* 'name more ...)) + +(define (cmd-index name) + (define namestr (format ",~a" name)) + (define tag `(xrepl ,(format "~a" name))) + (define content (cmd* name)) + (define ielem + (index-element #f content tag (list namestr) (list content) + 'xrepl-command)) + (toc-target-element #f (list ielem) tag)) + +(define (defcmd* name . text) + (set! documented (cons name documented)) + (define-values [other-names argline blurb] + (apply values (cond [(assq name (commands)) => cdr] + [else (error 'defcmd "unknown command: ~s" name)]))) + (define header + (list (cmd-index name) (litchar (string-append " " (or argline ""))))) + (define desc + (list (hspace 2) (make-element 'italic blurb))) + (define synonyms + (and (pair? other-names) + (list (hspace 2) + "[Synonyms: " + (add-between (map (λ (n) (litchar (format ",~a" n))) + other-names) + " ") + "]"))) + (splice + (list* (tabular #:style 'boxed `((,header) (,desc) + ,@(if synonyms `((,synonyms)) `()))) + "\n" "\n" text))) + +(define-syntax-rule (defcmd name text ...) (defcmd* 'name text ...)) + +(define (check-all-documented) + (unless (= (length documented) (length (remove-duplicates documented))) + (error 'xrepl-docs "some commands were documented multiple times")) + (let ([missing (remove* documented (map car (commands)))]) + (when (pair? missing) + (error 'xrepl-docs "missing command documentation: ~s" missing)))) diff --git a/collects/xrepl/info.rkt b/collects/xrepl/info.rkt index dd70b324f1..96e8ea9a85 100644 --- a/collects/xrepl/info.rkt +++ b/collects/xrepl/info.rkt @@ -1,3 +1,5 @@ #lang setup/infotab (define name "eXtended REPL") + +(define scribblings '(("xrepl.scrbl" () (tool-library)))) diff --git a/collects/xrepl/xrepl.scrbl b/collects/xrepl/xrepl.scrbl new file mode 100644 index 0000000000..f934492caf --- /dev/null +++ b/collects/xrepl/xrepl.scrbl @@ -0,0 +1,484 @@ +#lang scribble/doc +@(require scribble/manual "doc-utils.rkt" + scribble/decode (only-in scribble/core) + (for-label racket readline racket/help racket/enter + racket/trace profile)) + +@title{XREPL: eXtended REPL} +@author+email["Eli Barzilay" "eli@barzilay.org"] + +@defmodule[xrepl]{ + The @filepath{xrepl} collection extends the @exec{racket} @tech[#:doc + GUIDE]{REPL} significantly, turning it into a more useful tool for + interactive exploration and development. This includes ``meta + commands'', using readline, keeping past evaluation results, and + more.} + +@; --------------------------------------------------------------------- +@section{Installing XREPL} + +To use XREPL, start @exec{racket} and enter @racket[(require xrepl)]. +You will know that it works when the prompt changes to a @litchar{->}, +and, if you're working on a capable terminal, you will now have readline +editing. You can also start @exec{racket} and ask for XREPL to be +loaded using command-line arguments: +@commandline{racket -il xrepl} + +If you want to enable XREPL automatically, add this expression to your +Racket initialization file. +@margin-note*{To load XREPL conditionally (e.g., not in older Racket + versions), you can use @racket[(dynamic-require 'xrepl #f)]. This + is a plain expression that can be placed inside @racket[when] and + elsewhere.} +An easy way to do the necessary editing is to enter @cmd[install!], +which will inspect and edit your initialization file (it will describe +the change and ask for your permission). Alternatively, you can edit +the file directly: on Unix, it is @filepath{~/.racketrc}, and for +other platforms evaluate @racket[(find-system-path 'init-file)] to see +where it is. + +XREPL will set up a readline-based reader, so you do not need to load +that yourself. If your initialization file was previously set to load +readline via @racket[install-readline!], the @cmd[install!] command +will (notify you and) remove it. If you added it yourself, consider +removing it. (This is not strictly needed, but XREPL is slightly +better at detecting when to use readline.) + +@; --------------------------------------------------------------------- +@section{Meta REPL Commands} + +Most of the XREPL extensions are implemented as meta commands. These +commands are entered at the REPL, prefixed by a @litchar{,} and followed +by the command name. Note that several commands correspond directly to +Racket functions (e.g., @cmd[exit]) --- but since they work outside of +your REPL, they can be used even if the matching bindings are not +available. + +@; --------------------------------- +@subsection{Generic Commands} + +@defcmd[help]{ + Without an argument, displays a list of all known commands. Specify a + command to get help specific to that command. +} + +@defcmd[exit]{ + Exits Racket, optionally with an error code (see @racket[exit]). +} + +@defcmd[cd]{ + Sets the @racket[current-directory] to the given path. If no path is + specified, use your home directory. Path arguments are passed through + @racket[expand-user-path] so you can use @litchar{~}. An argument of + @litchar{-} means ``the previous path''. +} + +@defcmd[pwd]{ + Reports the value of @racket[current-directory]. +} + +@defcmd[shell]{ + Use @cmd[shell] (or @cmd[sh]) to run a generic shell command (via + @racket[system]). For convenience, a few synonyms are provided --- + they run the specified executables (still using @racket[system]). +} + +@defcmd[edit]{ + Runs an editor, as specified by your @envvar{EDITOR} environment + variable, with the given file/s arguments. If no files are specified + and the REPL is currently inside a module's namespace, then the file + for that module is used. If the @envvar{EDITOR} environment variable + is not set, use the @cmd[drracket] command instead. +} + +@defcmd[drracket]{ + Runs DrRacket with the specified file/s. If no files are given, and + the REPL is currently inside a module, the file for that module is + used. + + DrRacket is launched directly, without starting a new subprocess, and + it is then kept running in a hidden window so further invocations are + immediate. (When this command is used for the first time, you will + see DrRacket start as usual, and then its window will disappear --- + that window is keeping DrRacket ready for quick editing.) + + In addition to file arguments, arguments can specify one of a few + flags for additional operations: + @itemize[ + @item{@litchar{-new}: opens a new editing window. This is the default + when no files are given and the REPL is not inside a module,} + @item{@litchar{-open}: opens the specified file/s (or the current + module's file). This is the default when files are given or when + inside a module.} + @item{@litchar{-quit}: exits the running DrRacket instance. Quitting + DrRacket is usually not necessary. Therefore, if you try to quit it + from the DrRacket window, it will instead just close the window but + DrRacket will still be running in the background. Use this command + in case there is some exceptional problem that requires actually + quitting the IDE. (Once you do so, future uses of this command will + start a fresh instance.)}] +} + +@; --------------------------------- +@subsection{Binding Information} + +@defcmd[apropos]{ + Searches for known bindings in the current namespace. The arguments + specify which binding to look for: use a symbol (without a + @litchar{'}) to look for bindings that contain that name, and use a + regexp (e.g., @racket[#rx"..."]) to use a regexp for the search. + Multiple arguments are and-ed together. + + If no arguments are given, @emph{all} bindings are listed. +} + +@defcmd[describe]{ + For each of the specified names, describe where where it is coming + from and how it was defined if it names a known binding. In addition, + desribe the module (list its imports and exports) that is named by + arguments that are known module names. + + By default, bindings are searched for at the runtime level (phase 0). + You can add a different phase level for identifier lookups as a first + argument. In this case, only a binding can be described, even if the + same name is a known module. +} + +@defcmd[doc]{ + Uses Racket's @racket[help] to browse the documentation, look for a + binding, etc. Note that this can be used even in languages that don't + have the @racket[help] binding. +} + +@; --------------------------------- +@subsection{Requiring and Loading Files} + +@defcmd[require]{ + Most arguments are passed to @racket[require] as is. As a + convenience, if an argument specifies an existing file name, then use + its string form to specify the require, or use a @racket[file] in case + of an absolute path. In addition, an argument that names a known + symbolic module name (e.g., one that was defined on the REPL, or a + builtin module like @racket[#%network]), then its quoted form is used. + (Note that these shorthands do not work inside require subforms like + @racket[only-in].) +} + +@defcmd[require-reloadable]{ + Same as @cmd[require], but arranges to load the code in a way that + makes it possible to reload it later, or if a module was already + loaded (using this command) then reload it. Note that the arguments + should be simple specifications, without any require macros. If no + arguments are given, use arguments from the last use of this command + (if any). + + Module reloading is enabled by turnning off the + @racket[compile-enforce-module-constants] parameter --- note that this + prohibits some opimizations, since the compiler assumes that all + bindings may change. +} + +@defcmd[enter]{ + Uses @racket[enter!] to have the REPL go `inside' a given module's + namespace. A module name can specify an existing file as with the + @cmd[require-reloadable] command. If no module is given, and the REPL + is already in some module's namespace, then `enter!' is used with that + module, causing it to reload if needed. Using @racket[#f] makes it go + back to the toplevel namespace. + + Note that this can be used even in languages that don't have the + @racket[enter!] binding. In addition, @racket[enter!] is used in a + way that does not make it require itself into the target namespace. +} + +@defcmd[toplevel]{ + Makes the REPL go back to the toplevel namespace. Same as using the + @cmd[enter] command with a @racket[#f] argument. +} + +@defcmd[load]{ + Uses @racket[load] to load the specified file(s). +} + +@; --------------------------------- +@subsection{Debugging} + +@defcmd[time]{ + Times execution of an expression (or expressions). This is similar to + @racket{time} but the information that is displayed is a bit easier to + read. + + In addition, you can provide an initial number to specify repeating + the evaluation a number of times. In this case, each iteration is + preceded by two garbage collections, and when the iteration is done + its timing information and evaluation result(s) are displayed. When + the requested number of repetitions is done, some extreme results are + removed (top and bottom 2/7ths), and the remaining results are be + averaged. Finally, the resulting value(s) are from the last run are + returned (and can be accessed via the bindings for the last few + results, see @secref["past-vals"]). +} + +@defcmd[trace]{ + Traces the named function (or functions), using @racket[trace]. +} + +@defcmd[untrace]{ + Untraces the named function (or functions), using @racket[untrace]. +} + +@defcmd[errortrace]{ + @racketmodname[errortrace] is a useful Racket library which can + provide a number of useful services like precise profiling, test + coverage, and accurate error information. However, using it can be a + little tricky. @cmd[errortrace] and a few related commands fill this + gap, making @racketmodname[errortrace] easier to use. + + @cmd[errortrace] controls global use of @racketmodname[errortrace]. + With a flag argument of @litchar{+} errortrace instrumentation is + turned on, with @litchar{-} it is turned off, and with no arguments it + is toggled. In addition, a @litchar{?} flag displays instrumentation + state. + + Remember that @racketmodname[errortrace] instrumentation hooks into + the Racket compiler, and applies only to source code that gets loaded + from source and therefore compiled. Therefore, you should use it + @emph{before} loading the code that you want to instrument. +} + +@defcmd[profile]{ + This command can perform profiling of code in one of two very + different ways: either statistical profiling via the + @racketmodname[profile] library, or using the exact profiler feature + of @racketmodname[errortrace]. + + When given a parenthesized expression, @cmd[profile] will run it via + the statistical profiler, as with the @racket[profile] form, reporting + results as usual. This profiler adds almost no overhead, and it + requires no special setup. In particular, it does not require + pre-compiling code in a special way. However, there are some + imprecise elements to this profiling: the profiler samples stack + snapshots periodically which can miss certain calls, and it is also + sensitive to some compiler optimizations like inlining procedures and + thereby not showing them in the displayed analysis. See + @other-doc['(lib "profile/scribblings/profile.scrbl")] for more + information. + + In the second mode of operation, @cmd[profile] uses the precise + @racketmodname[errortrace] profiler. This profiler produces precise + results, but like other uses of the @racketmodname[errortrace], it + must be enabled before loading the code that is to be profiled. It + can add noticeable overhead (potentially affecting the reported + runtimes), but the results are accurate in the sense that no procedure + is skipped. (For additional details, see + @other-doc['(lib "errortrace/scribblings/errortrace.scrbl")].) + + In this mode, the arguments are flags that control the profiler. A + @litchar{+} flag turns the profiler on --- and as usual with + @racketmodname[errortrace] functionality, this applies to code that is + compiled from now on. A @litchar{-} flag turns this instrumentation + off, and without any flags it is toggled. Once the profiler is + enabled, you can run some code and then use this command to report + profiling results: use @litchar{*} to show profiling results by time, + and @litchar{#} for the results by counts. Once you've seen the + results, you can evaluate additional code to collect more profiling + information, or you can reset the results with a @litchar{!} flag. + You can also combine several flags to perform the associated + operations, for example, @cmd[prof]{*!-} will show the accumulated + results, clear them, and turn profiler instrumentation off. + + Note that using @emph{any} of these flags turns errortrace + instrumentation on, even @cmd[prof]{-} (or no flags). Use the + @cmd[errortrace] command to turn off instrumentation completely. +} + +@defcmd[execution-counts]{ + This command makes it easy to use the execution counts functionality + of @racketmodname[errortrace]. Given a file name (or names), + @cmd[execution-counts] will enable errortrace instrumentation for + coverage, require the file(s), display the results, disables coverage, + and disables instrumentation (if it wasn't previously turned on). + This is useful as an indication of how well the test coverage is for + some file. +} + +@defcmd[coverage]{ + Runs a given file and displays coverage information for the run. This + is somewhat similar to the @cmd[execution-counts] command, but instead + of using @racketmodname[errortrace] directly, it runs the file in a + (trusted) sandbox, using the @racketmodname[racket/sandbox] library + and its ability to provide coverage information. +} + +@; --------------------------------- +@subsection{Miscellaneous Commands} + +@defcmd[switch-namespace]{ + This powerful command controls the REPL's namespace. While + @cmd[enter] can be used to make the REPL go into the namespace of a + specific module, the @cmd[switch-namespace] command can switch between + @emph{toplevel namespaces}, allowing you to get multiple separate + ``workspaces''. + + Namespaces are given names that are symbols or integers, where + @litchar{*} is the name for the first initial namespace, serving as + the default one. These names are not bindings --- they are only used + to label the known namespaces. + + The most basic usage for this command is to simply specify a new name. + A namespace that corresponds to that name will be created and the REPL + will switch to that namespace. The prompt will now indicate this + namespace's name. The name is usually insignificant, except when it + is a @racket[require]-able module: in this case, the new namespace is + initialized to use that module's bindings. For example, + @cmd[switch]{racket/base} creates a new namespace that is called + @litchar{racket/base} and initializes it with + @racketmodname[racket/base]. For all other names, the new namespace + is initialized the same as the current one. + + Additional @cmd[switch] uses: + @itemize[ + @item{@cmd[switch]{!} --- reset the current namespace, recreating it + using the same initial library. Note that it is forbidden to reset + the default initial namespace, the one named @litchar{*} --- this + namespace corresponds to the one that Racket was started with, and + where XREPL was initialized. There is no technical reason for + forbidding this, but doing so is not useful as no resources will + actually be freed.} + @item{@cmd[switch]{! } --- resets the current namespace with + the explicitly given simple module spec.} + @item{@cmd[switch]{ !} --- switch to a newly made namespace. If + a namespace by that name already existed, it is rest.} + @item{@cmd[switch]{ ! } --- same, but reset to the given + module instead of what it previously used.} + @item{@cmd[switch]{- } --- drop the specified namespace, making + it possible to garbage-collect away any associated resources. You + cannot drop the current namespace or the default one (@litchar{*}).} + @item{@cmd[switch]{?} --- list all known namespaces.}] + + Do not confuse namespaces with sandboxes or custodians. The + @cmd{switch} command changes @emph{only} the + @racket[current-namespace] --- it does not install a new custodian or + restricts evaluation in any way. Note that it is possible to pass + around values from one namespace to another via past result reference; + see @secref["past-vals"]. +} + +@defcmd[syntax]{ + Manipulate syntaxes and inspect their expansion. + + Useful operations revolve around a ``currently set syntax''. With no + arguments, the currently set syntax is displayed; an argument of + @litchar{^} sets the current syntax from the last input to the REPL; + and an argument that holds any other s-expression will set it as the + current syntax. + + Syntax operations are specified via flags: + @itemize[ + @item{@litchar{+} uses @racket[expand-once] on the current syntax and + prints the resulting syntax. In addition, the result becomes the + new ``current'' syntax, so you can use this as a poor-man's syntax + stepper. (Note that in some rare cases expansion via a sequence of + @racket[expand-once] might differ from the actual expansion.)} + @item{@litchar{!} uses @racket[expand] to completely expand the + current syntax.} + @item{@litchar{*} uses the macro debugger's textual output to show + expansion steps for the current syntax, leaving macros from + @racketmodname[racket/base] intact. Does not change the current + syntax. + See @other-doc['(lib "macro-debugger/macro-debugger.scrbl")] for + details.} + @item{@litchar{**} uses the macro debugger similarly to @litchar{*}, + but expands @racketmodname[racket/base] macros too, showing the + resulting full expansion process.}] + Several input flags and/or syntaxes can be spacified in succession as + arguments to @cmd{syntax}. For example, @cmd[stx]{(when 1 2) ** !}. +} + +@defcmd[log]{ + Starts (or stops) logging events at a specific level. The level can + be: + @itemize[ + @item{a known level name (currently one of @litchar{fatal}, + @litchar{error}, @litchar{warning}, @litchar{info}, + @litchar{debug}),} + @item{@racket[#f] for no logging,} + @item{@racket[#t] for maximum logging,} + @item{an integer level specification, with @racket[0] for no logging + and bigger ones for additional verbosity.}] +} + +@defcmd[install!]{ + Convenient utility command to install XREPL in your Racket + initialization file. This is done carefully, you will be notified of + potential issues, and asked to authorize changes. +} + +@; --------------------------------------------------------------------- +@section[#:tag "past-vals"]{Past Evaluation Results} + +XREPL makes the last few interaction results available for evaluation +via special toplevel variables: @racketidfont{^}, @racketidfont{^^}, +..., @racketidfont{^^^^^}. The first, @racketidfont{^}, refers to the +last result, @racketidfont{^^} to the previous one and so on. + +As with the usual REPL printouts, @void-const results are not kept. In +case of multiple results, they are spliced in reverse, so +@racketidfont{^} refers to the last result of the last evaluation. For +example: +@verbatim[#:indent 4]{ + -> 1 + 1 + -> (values 2 3) + 2 + 3 + -> (values 4) + 4 + -> (list ^ ^^ ^^^ ^^^^) + '(4 3 2 1)} +The rationale for this is that @racketidfont{^} always refers to the +last @emph{printed} result, @racketidfont{^^} to the one before that, +etc. + +These bindings are made available only if they are not already defined, +and if they are not modified. This means that if you have code that +uses these names, it will continue to work as usual. + +@; --------------------------------------------------------------------- +@section{Hacking XREPL} + +XREPL is mainly a convenience tool, and as such you might want to hack +it to better suite your needs. Currently, there is no convenient way to +customize and extend it, but this will be added in the future. + +Meanwhile, if you're interested in tweaking XREPL, the @cmd[enter] +command can be used as usual to go into its implementation. For +example: +@verbatim[#:indent 4]{ + -> ,en xrepl/xrepl + xrepl/xrepl> ,e + xrepl/xrepl> (saved-values-char #\~) + xrepl/xrepl> ,top + -> 123 + 123 + -> ~ + 123} +While this is not intended as @emph{the} way to extend and customize +XREPL, it is a useful debugging tool should you want to do so. + +If you have any useful tweaks and extensions, please mail the author or +the Racket developer's +@hyperlink["http://racket-lang.org/community.html"]{mailing list}. + +@; --------------------------------------------------------------------- +@section{License Issues} + +Under most circumstances XREPL uses the @racketmodname[readline] +library, and therefore a similar license caveat applies: XREPL cannot be +enabled by default because of the @seclink["readline-license" #:doc +RL]{readline licensing}, you have to explicitly do so yourself to use +it. (Note that XREPL is intended to be used only for enhanced +interaction, not as a library; so there are no additional issues.) + +@; --------------------------------------------------------------------- +@(check-all-documented)