((section 2 "Outdated egg!" (p "This is an egg for CHICKEN 4, the unsupported old release.  You're almost certainly looking for " (int-link "/eggref/5/nrepl" "the CHICKEN 5 version of this egg") ", if it exists.") (p "If it does not exist, there may be equivalent functionality provided by another egg; have a look at the " (link "https://wiki.call-cc.org/chicken-projects/egg-index-5.html" "egg index") ". Otherwise, please consider porting this egg to the current version of CHICKEN.")) (section 2 "NREPL" (toc) (p "A networked REPL for Chicken Scheme. Each new incoming connection runs in a new " (tt "srfi-18") " thread.") (section 3 "Requirements" (p "None except the " (tt "tcp") " and " (tt "srfi-18") " units from CHICKEN core.")) (section 3 "API" (def (sig (procedure " (nrepl port [spawn])" (id nrepl))) (p "Listen to TCP port " (tt "port") " number and (blockingly) wait for incoming connections.  " (tt "(spawn)") " is called for each incomming connection without arguments where " (tt "current-input-port") ", " (tt "current-output-port") " and " (tt "current-error-port") " are bound to the TCP connection.") (p "You can use " (tt "spawn") ", for example, for authentication:") (highlight scheme "    \n(nrepl 1234\n       (lambda ()\n         (thread-start! ;; otherwise accept-loop will be blocked\n          (lambda ()\n            (display \";; please enter an accept token: \")\n            (define token (read-line))\n            (if (equal? token \"abc\")\n                (nrepl-loop)\n                (begin (print \";; access denied\")\n                       (close-input-port (current-input-port))\n                       (close-output-port (current-output-port))\n               (close-output-port (current-error-port))))))))") (p "blockquoteYou can use " (tt "tcp-addresses") " and " (tt "tcp-port-numbers") " to find out where the new session is coming from.") (p (tt "nrepl") " will loop for accepting incomming connections unless " (tt "spawn") " returns " (tt "#f") ".")) (def (sig (procedure " (nrepl-loop #!key eval read print writeln)" (id nrepl-loop))) (p "Start a standard REPL-loop: print the prompt, read an s-expression, evaluate the expression, print the result and repeat. Exceptions are reported and data is flushed. This can be used inside the optionally supplied " (tt "spawn") "-procedure above."))) (section 3 "Practical use" (p "Any source-code you send down a " (tt "nrepl") " session will not be persisted anywhere.  You can reset your program state by restarting your program which may be useful sometimes.") (section 4 "Terminal users" (p "Editing code directly from " (tt "nc localhost 1234") " isn't pleasant. Luckily, " (link "http://freecode.com/projects/rlwrap" "rlwrap") " works along " (tt "nrepl") " to improve this experience:") (highlight bash "    \n ➤ rlwrap nc localhost 1234\n;; nrepl on (csi -s example.scm)\n#;1> (define (hello) (print \"this will be in my history\"))") (p (link "http://freecode.com/projects/rlwrap" "rlwrap") " will also save your read-line history for the next invokation " (tt "rlwrap nc localhost 1234") " which is handy!")) (section 4 ((link "https://www.gnu.org/software/emacs/" "Emacs") " users") (p (tt "nrepl") " plays very nicely with " (link "https://www.gnu.org/software/emacs/" "Emacs") "! If you're used to running " (tt "M-x run-scheme") " and sending source-code from buffers into your REPL, an " (tt "nrepl") " endpoint can be used as a Scheme interpreter. You can specify that you want to use " (tt "nrepl") " with a prefix. For example:") (pre "   C-u M-x run-scheme RET nc localhost 1234") (p "Note that telling " (link "https://www.gnu.org/software/emacs/" "Emacs") " that " (tt "nc localhost 1234") " is your Scheme interpreter is tricky because " (tt "C-u M-x run-scheme") " might not let you enter spaces. You can enter spaces by pressing " (tt "C-q") " before pressing space.")) (section 4 "Example HTTP-server work-flow" (p "A real-world use-case for " (tt "nrepl") " might be something like the following. Let's make a simple hello-world HTTP server using " (link "http://api.call-cc.org/doc/spiffy" "spiffy") ".") (highlight scheme "    \n(use spiffy nrepl)\n\n(define (app c)\n  (send-response body: \"hello world\\n\"))\n\n(thread-start!\n (lambda ()\n   (vhost-map `((\".*\" . ,(lambda (c) (app c)))))\n   (start-server)))\n\n(print \"starting nrepl on port 1234\")\n(nrepl 1234)") (p "Now spiffy runs on port " (tt "8080") ":") (highlight bash "    \n ➤ curl localhost:8080\nhello world") (p "What's nice about this is that, since " (tt "app") " is a top-level variable, it can be replaced from the REPL:") (highlight bash "    \n ➤ nc localhost 1234\n;; nrepl on (csi -s example.scm)\n#;1> (define (app c) (send-response body: \"repl hijack!\\n\"))\n#;1> ^C") (p "Now " (tt "spiffy") " will use our top-level " (tt "app") " for its proceeding requests:") (highlight bash "    \n ➤ curl localhost:8080\nrepl hijack!") (p "Note that " (tt "app") " must be wrapped in a " (tt "lambda") " for this to work, because the REPL can only replace top-level variable definitions.") (p "The implications of this can be quite dramatic in terms of work-flow. If you write your app in a REPL-friendly way like this, you can modify you program behaviour on-the-fly from the REPL and never have to restart your process and lose its state.")) (section 4 "Example CPU-intensive main thread" (p (tt "nrepl") " can be used for live-coding interactive application such as games. Adding " (tt "(thread-start! (lambda () (nrepl 1234)))") " usually Just Works, where you can redefine top-level function and game state on-the-fly.") (p "However, if the game-loop is eating up a lot of scheduler-time, you may find that your REPL becomes unresponsive. A good way to fix this is to wrap both the REPL and the game-loop in a mutex. This has another advantage in that it will ensure your REPL will not interfere with game-state (or OpenGL state) during game-loop iteration.") (highlight scheme "    \n;;; wrapping nrepl eval in a mutex for responsiveness\n;;; and game-loop thread-safety. running this and then doing:\n;;;     echo '(thread-sleep! 1)' | rlwrap nc localhost 1234\n;;; should pause the game-loop for 1 second\n(use nrepl)\n\n(define with-main-mutex\n  (let ((main-mutex (make-mutex)))\n    (lambda (proc)\n      (dynamic-wind (lambda () (mutex-lock! main-mutex))\n                    proc\n                    (lambda () (mutex-unlock! main-mutex))))))\n\n(thread-start!\n (lambda ()\n   (nrepl 1234\n          (lambda ()\n            (thread-start!\n             (lambda ()\n               (nrepl-loop eval: (lambda (x) (with-main-mutex (lambda () (eval x)))))))))))\n\n(define (game-step)\n  (print* \"\\r\"  (current-milliseconds) \"   \")\n  (thread-sleep! 0.05))\n\n(let loop ()\n  (with-main-mutex game-step)\n  (loop))")) (section 4 ((tt "nrepl") " in compiled code") (p (tt "nrepl") " also works inside a compiled program. However, sometimes " (link "http://api.call-cc.org/doc/chicken/modules" "modules") " disappear due to compiler optimizations.")) (section 4 ((tt "nrepl") " on Android") (p (tt "nrepl") " has been used successfully on Android target hardware for remote interactive development.  Check out " (link "https://github.com/chicken-mobile/chicken-android-template" "this") " Android example project."))) (section 3 "Source code repository" (p "You can find the source " (link "https://github.com/Adellica/chicken-nrepl" "here") ".")) (section 3 "Author" (p "Kristian Lein-Mathisen at " (link "https://github.com/Adellica/" "Adellica"))) (section 3 "License" (p "BSD"))))