forked from mattwparas/steel
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Repl server library (mattwparas#301)
* include web stuff * checkpoint * wip * move repl to its own directory * provide the repl function * add some basic handling * clean up function names * some more handling * move the web library to the wip directory * wrap entrypoints in test mode
- Loading branch information
1 parent
9b2ada5
commit b8ac1f1
Showing
16 changed files
with
372 additions
and
30 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
(define package-name 'steel/repl) | ||
(define version "0.1.0") | ||
|
||
;; Core library, requires no dependencies | ||
(define dependencies '()) | ||
|
||
;; Entrypoint in this case is a client that can connect | ||
;; to a repl server? | ||
(define entrypoint '(#:name "repl-connect" #:path "repl-client.scm")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
(require-builtin steel/tcp) | ||
(require-builtin #%private/steel/readline) | ||
|
||
(require "steel/sync") | ||
|
||
(define channels (channels/new)) | ||
(define sender (channels-sender channels)) | ||
(define receiver (channels-receiver channels)) | ||
|
||
;; After every input if we should shutdown. | ||
(define shutdown (channels/new)) | ||
(define shutdown-sender (channels-sender shutdown)) | ||
(define shutdown-receiver (channels-receiver shutdown)) | ||
|
||
(define MAGIC-START (string->bytes "%start-repl#%")) | ||
(define MAGIC-START-LENGTH (bytes-length MAGIC-START)) | ||
|
||
(define (read-size buffer port) | ||
(define next (read-byte port)) | ||
(if (equal? next #x0A) | ||
(begin | ||
(string->int (bytes->string/utf8 buffer))) | ||
(begin | ||
(bytes-push! buffer next) | ||
(read-size buffer port)))) | ||
|
||
;; Handle user input, forward, dump out things happening, continue. | ||
(define (repl-loop [host "0.0.0.0"] [port 8080]) | ||
(define stream (tcp-connect (string-append host ":" (int->string port)))) | ||
(define reader (tcp-stream-reader stream)) | ||
(define writer (tcp-stream-writer stream)) | ||
|
||
;; Print out the startup message for the repl, and then | ||
;; we'll enter the event loop waiting for input. | ||
(#%repl-display-startup) | ||
(define rl (#%create-repl)) | ||
|
||
(define buffer (bytevector)) | ||
|
||
(define (loop) | ||
(define next (read-char reader)) | ||
(cond | ||
[(equal? next #\#) | ||
(let ([maybe-magic (read-bytes MAGIC-START-LENGTH reader)]) | ||
(if (equal? maybe-magic MAGIC-START) | ||
(let ([size (read-size buffer reader)]) | ||
|
||
(bytes-clear! buffer) | ||
|
||
;; Read the next value | ||
(define next-value (read-bytes size reader)) | ||
(define value-as-string (bytes->string/utf8 next-value)) | ||
|
||
(unless (equal? "#<void>" value-as-string) | ||
(display "=> ") | ||
(display value-as-string) | ||
(newline)) | ||
|
||
(channel/send sender #t)) | ||
;; Next should be the length, until the next newline | ||
(begin | ||
(write-char next (current-output-port)) | ||
(display (bytes->string/utf8 maybe-magic)))))] | ||
|
||
[(eof-object? next) | ||
(displayln "Connection closed.") | ||
(return! void)] | ||
|
||
[else (write-char next (current-output-port))]) | ||
|
||
;; Remote repl... add bindings to readline? | ||
;; That could help? | ||
; (write-char (read-char reader) (current-output-port)) | ||
|
||
(loop)) | ||
|
||
(define (driver) | ||
(with-handler (lambda (err) | ||
(displayln err) | ||
(channel/send shutdown-sender #t)) | ||
(loop))) | ||
|
||
(spawn-native-thread driver) | ||
|
||
;; Read input, send on the stream | ||
(define (input-loop) | ||
(define input (#%read-line rl)) | ||
(#%repl-add-history-entry rl input) | ||
|
||
;; Show the error, go again | ||
(with-handler (lambda (err) (displayln err)) | ||
(write (read (open-input-string input)) writer) | ||
(newline writer) | ||
;; Selection. Wait on either an acknowledgement, or a shutdown? | ||
(define result (receivers-select receiver shutdown-receiver)) | ||
(case result | ||
[(0) (input-loop)] | ||
[(1) (displayln "Shutting down")] | ||
[else void]))) | ||
(input-loop)) | ||
|
||
(define (main) | ||
;; Fetch the args, check if there is a host provided. | ||
;; If not, default to the loop back host. | ||
(define args (command-line)) | ||
|
||
(match (drop args 2) | ||
[(list) (repl-loop)] | ||
[(list "--port" port) (repl-loop "0.0.0.0" (string->int port))] | ||
[(list "--host" host) (repl-loop host)] | ||
[(list "--host" host "--port" port) (repl-loop host (string->int port))])) | ||
|
||
(unless (get-test-mode) | ||
(main)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
(require-builtin steel/tcp) | ||
(require "steel/sync") | ||
|
||
(provide repl-serve) | ||
|
||
;; Thread id -> TCP reader. Remove when finished. | ||
(define #%repl-manager (hash)) | ||
|
||
(define (#%mark-connected tcp-stream) | ||
(set! #%repl-manager (hash-insert #%repl-manager (current-thread-id) tcp-stream))) | ||
|
||
(define (#%set-thread-closed!) | ||
(define connection (hash-get #%repl-manager (current-thread-id))) | ||
;; Close it all down | ||
(tcp-shutdown! connection) | ||
(set! #%repl-manager (hash-remove #%repl-manager (current-thread-id))) | ||
|
||
; (display "...Shutting down thread" (stdout)) | ||
; (newline (stdout)) | ||
) | ||
|
||
(define (#%shutdown?) | ||
(not (hash-contains? #%repl-manager (current-thread-id)))) | ||
|
||
(define (quit) | ||
;; Attempt to set this thread closed no matter what. | ||
(with-handler (lambda (_) void) (#%set-thread-closed!))) | ||
|
||
(define (repl-serve [port 8080] [thread-pool-size 2]) | ||
(define listener (tcp-listen (string-append "0.0.0.0:" (int->string port)))) | ||
(define tp (make-thread-pool thread-pool-size)) | ||
|
||
(while | ||
#t | ||
;; Accept the stream | ||
(define input-stream (tcp-accept listener)) | ||
(submit-task | ||
tp | ||
(lambda () | ||
;; TODO: Set up dedicated logging stream, flushes on its own | ||
;; background thread? | ||
(define reader-port (tcp-stream-buffered-reader input-stream)) | ||
(define writer-port (tcp-stream-writer input-stream)) | ||
;; Continue to accept connections until this one disconnects | ||
(define (repl-loop) | ||
;; Assume, that for now, we are comfortable with the fact | ||
;; that stdout / etc will get printed from the | ||
(let ([expr (read reader-port)]) | ||
|
||
(unless (eof-object? expr) | ||
;; It is probably possible to just serialize the eventual | ||
;; error message directly, and send that over. That way | ||
;; the stack trace is maintained? | ||
(define result (with-handler (lambda (err) (to-string err)) (eval expr))) | ||
|
||
;; Close the thread. | ||
(when (#%shutdown?) | ||
(close-output-port writer-port) | ||
(close-input-port reader-port) | ||
(return! #t)) | ||
|
||
;; TODO: Merge this into one display call. | ||
;; It all has to come through in one fell swoop, such that | ||
;; return values are atomic on the output stream. | ||
(define output-string (open-output-string)) | ||
(display result output-string) | ||
(define formatted (get-output-string output-string)) | ||
|
||
;; Don't send back a void, just have it be the length of 0 | ||
(display | ||
(string-append "#%start-repl#%" (int->string (string-length formatted)) "\n" formatted)) | ||
|
||
(repl-loop)))) | ||
|
||
(#%mark-connected input-stream) | ||
|
||
;; Set up the repl to also grab std out | ||
(parameterize ([current-output-port writer-port]) | ||
(repl-loop)) | ||
|
||
(displayln "Closing connection."))))) | ||
|
||
(unless (get-test-mode) | ||
(repl-serve)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.