diff options
Diffstat (limited to 'scheme')
-rw-r--r-- | scheme/chicken/geiser/chicken5.scm | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/scheme/chicken/geiser/chicken5.scm b/scheme/chicken/geiser/chicken5.scm new file mode 100644 index 0000000..f5f0c30 --- /dev/null +++ b/scheme/chicken/geiser/chicken5.scm @@ -0,0 +1,401 @@ +;; -*- geiser-scheme-implementation: 'chicken + +;; Copyright (C) 2018 Daniel J Leslie + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the Modified BSD License. You should +;; have received a copy of the license along with this program. If +;; not, see <http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5>. + +(module geiser + (geiser-eval + geiser-no-values + geiser-newline + geiser-start-server + geiser-completions + geiser-autodoc + geiser-object-signature + geiser-symbol-location + geiser-symbol-documentation + geiser-find-file + geiser-add-to-load-path + geiser-load-file + geiser-compile-file + geiser-compile + geiser-module-exports + geiser-module-path + geiser-module-location + geiser-module-completions + geiser-macroexpand + geiser-chicken-use-debug-log + geiser-chicken-load-paths) + + (import + scheme + apropos + srfi-1 + srfi-18 + (chicken base) + (chicken tcp) + (chicken file) + (chicken file posix) + (chicken format) + (chicken condition) + (chicken port) + (chicken string) + (chicken repl) + (chicken syntax)) + + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; Utilities + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (define geiser-chicken-use-debug-log (make-parameter #f)) + + (define empty-symbol (string->symbol "")) + + (define (symbol-information-list partial-string) + (map (lambda (lst) + (let* ((module (if (eq? empty-symbol (caar lst)) #f (caar lst))) + (name (cdar lst))) + (append (list name module) (cdr lst)))) + (apropos-information-list partial-string #:macros? #t))) + + (define debug-log (make-parameter #f)) + (define (write-to-log form) + (when (geiser-chicken-use-debug-log) + (when (not (debug-log)) + (debug-log (file-open "geiser.log" (+ open/wronly open/append open/text open/creat))) + (set-file-position! (debug-log) 0 seek/end)) + (file-write (debug-log) (with-all-output-to-string (lambda () (write form) (newline)))) + (file-write (debug-log) "\n"))) + + (define (string-has-prefix? s prefix) + (cond + ((= 0 (string-length prefix)) #t) + ((= 0 (string-length s)) #f) + ((eq? (string-ref s 0) (string-ref prefix 0)) + (string-has-prefix? (substring s 1) (substring prefix 1))) + (else #f))) + + ;; This really should be a chicken library function + (define (write-exception exn) + (define (write-call-entry call) + (let ((type (vector-ref call 0)) + (line (vector-ref call 1))) + (cond + ((equal? type "<syntax>") + (display (string-append type " ")) (write line) (newline)) + ((equal? type "<eval>") + (display (string-append type " ")) (write line) (newline))))) + + (display (format "Error: (~s) ~s: ~s" + ((condition-property-accessor 'exn 'location) exn) + ((condition-property-accessor 'exn 'message) exn) + ((condition-property-accessor 'exn 'arguments) exn))) + (newline) + (display "Call history: ") (newline) + (map write-call-entry ((condition-property-accessor 'exn 'call-chain) exn)) + (newline)) + + ;; And this should be a chicken library function as well + (define (with-all-output-to-string thunk) + (with-output-to-string + (lambda () + (with-error-output-to-port + (current-output-port) + thunk)))) + + (define (maybe-call func val) + (if val (func val) #f)) + + ;; Wraps output from geiser functions + (define (call-with-result module thunk) + (let* ((result (if #f #f)) + (output (if #f #f))) + + (set! output + (handle-exceptions exn + (with-all-output-to-string + (lambda () (write-exception exn))) + (with-all-output-to-string + (lambda () + (call-with-values thunk (lambda v (set! result v))))))) + + (set! result + (cond + ((list? result) + (map (lambda (v) (with-output-to-string (lambda () (write v)))) result)) + ((eq? result (if #f #t)) + (list output)) + (else + (list (with-output-to-string (lambda () (write result))))))) + + (let ((out-form + `((result ,@result) + (output . ,output)))) + (write out-form) + (write-to-log '[[RESPONSE]]) + (write-to-log out-form)) + + (newline))) + + (define (read* str) + (with-input-from-string str (lambda () (read)))) + + (define (eval* str) + (cond + ((symbol? str) (eval str)) + ((string? str) (eval (read* str))) + (else #f))) + + (define (fmt node) + (let* ((mod (cadr node)) + (sym (car node)) + (rest (cddr node)) + (type (if (or (list? rest) (pair? rest)) (car rest) rest))) + (cond + ((equal? 'macro type) + `(,sym ("args" (("required" <macro>) + ("optional" ...) + ("key"))) + ("module"))) + ((or (equal? 'variable type) + (equal? 'constant type)) + `(,sym ("value" . ,(eval* sym)))) + (else + (let ((reqs '()) + (opts '()) + (keys '()) + (args (if (or (list? rest) (pair? rest)) (cdr rest) '()))) + + (define (collect-args args #!key (reqs? #t) (opts? #f) (keys? #f)) + (when (not (null? args)) + (cond + ((or (pair? args) (list? args)) + (cond + ((eq? '#!key (car args)) + (collect-args (cdr args) reqs?: #f opts?: #f keys?: #t)) + ((eq? '#!optional (car args)) + (collect-args (cdr args) reqs?: #f opts?: #t keys?: #f)) + (else + (begin + (cond + (reqs? + (set! reqs (append reqs (list (car args))))) + (opts? + (set! opts (append opts (list (cons (caar args) (cdar args)))))) + (keys? + (set! keys (append keys (list (cons (caar args) (cdar args))))))) + (collect-args (cdr args)))))) + (else + (set! opts (list args '...)))))) + + (collect-args args) + + `(,sym ("args" (("required" ,@reqs) + ("optional" ,@opts) + ("key" ,@keys))) + ("module"))))))) + + ;; Builds a signature list from an identifier + (define (find-signatures sym) + (let ((result (symbol-information-list sym))) + (map fmt result))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Geiser core functions +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;; Basically all non-core functions pass through geiser-eval + + (define (form-has-safe-geiser? form) + (any (cut eq? (car form) <>) + '(geiser-no-values geiser-newline geiser-completions + geiser-autodoc geiser-object-signature geiser-symbol-location + geiser-symbol-documentation geiser-module-exports + geiser-module-path geiser-module-location + geiser-module-completions geiser-chicken-use-debug-log))) + + (define (form-has-any-geiser? form) + (string-has-prefix? (->string (car form)) "geiser-")) + + (define (form-defines-any-module? form) + (or + ;; Geiser seems to send buffers as (begin ..buffer contents..) + (and (eq? (car form) 'begin) + (form-defines-any-module? (cadr form))) + (any (cut eq? (car form) <>) + '(module define-library)))) + + (define (geiser-eval module form . rest) + (when (and module (not (symbol? module))) + (error "Module should be a symbol")) + + ;; All calls start at toplevel + (let* ((is-safe-geiser? (form-has-safe-geiser? form)) + (host-module (and (not is-safe-geiser?) + (not (form-has-any-geiser? form)) + (not (form-defines-any-module? form)) + module)) + (thunk (lambda () (eval form)))) + + (write-to-log `[[REQUEST host-module: ,host-module]]) + (write-to-log form) + + (if is-safe-geiser? + (call-with-result #f thunk) + (call-with-result host-module thunk)))) + + ;; Load a file + + (define (geiser-load-file file) + (let* ((file (if (symbol? file) (symbol->string file) file)) + (found-file (geiser-find-file file))) + (call-with-result #f + (lambda () + (when found-file + (load found-file)))))) + + ;; The no-values identity + + (define (geiser-no-values) + (values)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Miscellaneous +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;; Invoke a newline + + (define (geiser-newline . rest) + (newline)) + + ;; Spawn a server for remote repl access + + (define (geiser-start-server . rest) + (let* ((listener (tcp-listen 0)) + (port (tcp-listener-port listener))) + (define (remote-repl) + (receive (in out) (tcp-accept listener) + (current-input-port in) + (current-output-port out) + (current-error-port out) + + (repl))) + + (thread-start! (make-thread remote-repl)) + + (write-to-log `(geiser-start-server . ,rest)) + (write-to-log `(port ,port)) + + (write `(port ,port)) + (newline))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Completions, Autodoc and Signature +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (define (geiser-completions prefix . rest) + (let ((prefix (->string prefix))) + (filter (cut string-has-prefix? <> prefix) + (map ->string (map car (symbol-information-list prefix)))))) + + (define (geiser-module-completions prefix . rest) + '()) + + (define (geiser-autodoc ids . rest) + (cond + ((null? ids) '()) + ((not (list? ids)) + (geiser-autodoc (list ids))) + (else + (let ((details (find-signatures (car ids)))) + (if (null? details) + (geiser-autodoc (cdr ids)) + details))))) + + (define (geiser-object-signature name object . rest) + (let* ((sig (geiser-autodoc `(,name)))) + (if (null? sig) '() (car sig)))) + + ;; TODO: Divine some way to support this functionality + + (define (geiser-symbol-location symbol . rest) + '(("file") ("line"))) + + (define (geiser-symbol-documentation symbol . rest) + (let* ((sig (find-signatures symbol))) + `(("signature" ,@(car sig)) + ("docstring" . "")))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; File and Buffer Operations +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (define geiser-chicken-load-paths (make-parameter '("" "."))) + + (define (geiser-find-file file . rest) + (when file + (let ((paths (geiser-chicken-load-paths))) + (define (try-find file paths) + (cond + ((null? paths) #f) + ((file-exists? (string-append (car paths) file)) + (string-append (car paths) file)) + (else (try-find file (cdr paths))))) + (try-find file paths)))) + + (define (geiser-add-to-load-path directory . rest) + (let* ((directory (if (symbol? directory) + (symbol->string directory) + directory)) + (directory (if (not (equal? #\/ (string-ref directory (- (string-length directory))))) + (string-append directory "/") + directory))) + (call-with-result #f + (lambda () + (when (directory-exists? directory) + (geiser-chicken-load-paths (cons directory (geiser-chicken-load-paths)))))))) + + (define (geiser-compile-file file . rest) + #f) + + ;; TODO: Support compiling regions + + (define (geiser-compile form module . rest) + (error "Chicken does not support compiling regions")) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Modules +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + ;; Should return: + ;; '(("modules" . sub-modules) ("procs" . procedures) ("syntax" . macros) ("vars" . variables)) + (define (geiser-module-exports module-name . rest) + '()) + + ;; Returns the path for the file in which an egg or module was defined + + (define (geiser-module-path module-name . rest) + #f) + + ;; Returns: + ;; `(("file" . ,(module-path name)) ("line")) + + (define (geiser-module-location name . rest) + #f) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Misc +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (define (geiser-macroexpand form . rest) + (with-output-to-string + (lambda () + (write (expand form))))) + +;; End module + ) |