[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[nongnu] elpa/geiser-stklos fc33f45 01/30: Initial Commit
From: |
Philip Kaludercic |
Subject: |
[nongnu] elpa/geiser-stklos fc33f45 01/30: Initial Commit |
Date: |
Sun, 1 Aug 2021 18:32:39 -0400 (EDT) |
branch: elpa/geiser-stklos
commit fc33f459d8a396b09771b5f9e1b09ee6b46253b6
Author: Jeronimo Pellegrini <j_p@aleph0.info>
Commit: Jeronimo Pellegrini <j_p@aleph0.info>
Initial Commit
---
README.md | 29 ++++++
geiser-stklos.el | 309 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
geiser.stk | 250 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 588 insertions(+)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4d820b1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,29 @@
+# Geiser for STklos
+
+See the Geiser anual for usage.
+
+# Unsupported features
+
+* finding the definition of a symbol (no support in STklos)
+* seeing callees and callers of a procedure (no support in STklos)
+* looking up symbols in the manual (would need to download the index from
STklos manual and parse the DOM of its index; a bit too much, maybe someday...)
+
+# Bugs
+
+See the issue tracker in Gitlab.
+
+# About the implementation:
+
+The following functions were defined on the STklos side:
+
+* `geiser:eval`
+* `geiser:load-file`
+* `geiser:add-to-load-path`
+* `geiser:macroexpand`
+* `geiser:no-values`
+* `geiser:symbol-documentation`
+* `geiser:module-exports`
+* `geiser:module-completions`
+* `geiser:completions`
+
+They have comments that help understand how they work.
diff --git a/geiser-stklos.el b/geiser-stklos.el
new file mode 100644
index 0000000..1be3862
--- /dev/null
+++ b/geiser-stklos.el
@@ -0,0 +1,309 @@
+;;; geiser-stklos.el -- STklos Scheme implementation of the geiser protocols
+
+;; Author: Jeronimo Pellegrini <j_p@aleph0.info>
+;; Maintainer:
+;; Keywords: languages, stklos, scheme, geiser
+;; Homepage:
+;; Package-Requires: ((emacs "24.4") (geiser-core "1.0"))
+;; SPDX-License-Identifier: BSD-3-Clause
+;; Version: 1.0
+
+
+;;; Code:
+
+(require 'geiser-connection)
+(require 'geiser-syntax)
+(require 'geiser-custom)
+(require 'geiser-base)
+(require 'geiser-eval)
+(require 'geiser-edit)
+(require 'geiser-log)
+(require 'geiser)
+
+(require 'compile)
+(require 'info-look)
+
+(eval-when-compile (require 'cl-lib))
+
+
+
+;;; Customization:
+
+(defgroup geiser-stklos
+ nil
+ "Customization for Geiser's STklos Scheme flavour."
+ :group 'geiser)
+
+(geiser-custom--defcustom geiser-stklos-binary
+ "stklos"
+ "Name to use to call the STklos executable when starting a REPL."
+ :type '(choice string (repeat string))
+ :group 'geiser-stklos)
+
+
+(geiser-custom--defcustom geiser-stklos-extra-command-line-parameters
+ '()
+ "Additional parameters to supply to the STklos binary."
+ :type '(repeat string)
+ :group 'geiser-stklos)
+
+(geiser-custom--defcustom geiser-stklos-extra-keywords
+ nil
+ "Extra keywords highlighted in STklos scheme buffers."
+ :type '(repeat string)
+ :group 'geiser-stklos)
+
+;; FIXME: should ask STklos,
+;; (read-case-sensitive) returns the proper value, but
+;; this should be done during REPL startup.
+;; And the value can be changed later, because read-case-sensitive
+;; is a parameter object!
+(geiser-custom--defcustom geiser-stklos-case-sensitive
+ t
+ "Non-nil means keyword highlighting is case-sensitive. You need
+to restart Geiser in order for it to see you've changed this
+option."
+ :type 'boolean
+ :group 'geiser-stklos)
+
+
+
+;;; REPL support:
+
+(defvar geiser-stklos-scheme-dir
+ (expand-file-name "" (file-name-directory load-file-name))
+ "Directory where the STklos scheme geiser modules are installed.")
+
+;; returns the name of the executable.
+(defun geiser-stklos--binary ()
+ (if (listp geiser-stklos-binary)
+ (car geiser-stklos-binary)
+ geiser-stklos-binary))
+
+;; a list of strings to be passed to STklos
+(defun geiser-stklos--parameters ()
+ "Return a list with all parameters needed to start STklos Scheme.
+This function uses `geiser-stklos-init-file' if it exists."
+ `(,@geiser-stklos-extra-command-line-parameters
+ "-i" ;; do not use ANSI color codes
+ "-n" ;; do not use the line editor
+ "-l" ,(expand-file-name "geiser.stk" geiser-stklos-scheme-dir)))
+
+;; STklos' prompt is "MODULE> ". The regexp is "[^>]*> ".
+;; Not perfect, because if a module has a ">" sign
+;; in its name, things break...
+(defconst geiser-stklos--prompt-regexp "[^>]*> ")
+
+
+;;; Evaluation support:
+
+;; Translates symbols into Scheme procedure calls from
+;; geiser.stk :
+(defun geiser-stklos--geiser-procedure (proc &rest args)
+ (cl-case proc
+ ((eval compile)
+ (let ((form (mapconcat 'identity (cdr args) " "))
+ (module (cond ((string-equal "'()" (car args))
+ "'()")
+ ((and (car args))
+ (concat "'" (car args)))
+ (t
+ "#f"))))
+ (format "(geiser:eval %s '%s)" module form)))
+ ((load-file compile-file)
+ (format "(geiser:load-file %s)" (car args)))
+ ((no-values)
+ "(geiser:no-values)")
+ (t
+ (let ((form (mapconcat 'identity args " ")))
+ (format "(geiser:%s %s)" proc form)))))
+
+;;; Modules
+
+;; Regular expression used to try to guess which module
+;; the current file is associated to.
+(defconst geiser-stklos--module-re
+ "(define-module +\\([^) ]+\\)")
+
+
+;; from the start point, which must be an opening
+;; ( or [, find its closing match and return its
+;; position, or the end of buffer position if a
+;; closing match is not found.
+(defun find-close-par (&optional start-point)
+ (interactive)
+ (let ((start (if (null start-point)
+ (point)
+ start-point))
+ (opening '( ?\[ ?\( ))
+ (closing '( ?\] ?\) )))
+ (when (not (member (char-after start)
+ opening))
+ (error "find-close-par: not ( or ["))
+ (let ((stack (list (char-after start)))
+ (p (+ 1 start)))
+ (while (not (or (= p (point-max))
+ (null stack)))
+ (let ((c (char-after p)))
+ (cond ((member c closing)
+ (pop stack))
+ ((member c opening)
+ (push c stack))))
+ (incf p))
+ p)))
+
+;; find which module should be used for the position where the
+;; cursor is.
+;; if the user is editing text inside a module definition -- which is
+;; between "(define-module " and its closing parenthesis, then
+;; the current module should be taken as that one, so defines and sets
+;; will be done inside that module.
+(defun geiser-stklos--get-module (&optional module)
+ (cond ((null module)
+ (let ((here (point)))
+ (save-excursion
+ ;; goto end of line, so if we are already exacly on the module
+ ;; definition, we'll be able to find it searching backwards:
+ (end-of-line)
+ ;; module defined BEFORE point:
+ (let ((module-begin (re-search-backward geiser-stklos--module-re
nil t)))
+ (if module-begin
+ ;; and we're not AFTER it was closed:
+ (let ((module-end (find-close-par module-begin)))
+ (if (< here module-end)
+ (geiser-stklos--get-module
(match-string-no-properties 1))
+ :f))
+ :f)))))
+ ((symbolp module) (geiser-stklos--get-module (symbol-name module))) ;
try again, as string
+ ((listp module) module)
+ ((stringp module)
+ (condition-case e
+ (car (geiser-syntax--read-from-string module))
+ (progn (message (format "error -> %s" e))
+ (error :f))))
+ (t :f)))
+
+
+;; string sent to STklos to tell it to enter a module.
+(defun geiser-stklos--enter-command (module)
+ (format "(select-module %s)" module))
+
+
+;; Finds the beginning of the symbol around point.
+(defun geiser-stklos--symbol-begin (module)
+ (if module
+ (max (save-excursion (beginning-of-line) (point))
+ (save-excursion (skip-syntax-backward "^(>") (1- (point))))
+ (save-excursion (skip-syntax-backward "^'-()>") (point))))
+
+
+;; string sent to STklos to tell it to load a module.
+(defun geiser-stklos--import-command (module)
+ (format "(require \"%s\")" module))
+
+;; string sent to STklos to tell it to exit.
+;; (this could also be ",q"...)
+(defun geiser-stklos--exit-command () "(exit 0)")
+
+
+
+;;; Error display
+
+(defun geiser-stklos--display-error (module key msg)
+ (newline)
+ (when (stringp msg)
+ (save-excursion (insert msg))
+ (geiser-edit--buttonize-files))
+ (and (not key) msg (not (zerop (length msg)))))
+
+
+;;; Guessing wether a buffer is a STklos REPL buffer
+
+;; The function (geiser-stklos--guess) tries to
+;; ascertain whether a buffer is STklos Scheme.
+;; This will possibly fail:
+;;
+;; - with false negative, if the buffer is running STklos
+;; but th euser is in not in the stklos module, AND
+;; the user was not in the stklos module recently, so
+;; there are no "stklos" strings in the buffer.
+;;
+;; - with false positive, if the buffer is not a STklos buffer,
+;; but there is a string "stklos>" there. I see no way
+;; to prevent this.
+(defconst geiser-stklos--guess-re
+ (regexp-opt '("stklos>")))
+
+(defun geiser-stklos--guess ()
+ (save-excursion
+ (goto-char (point-min))
+ (re-search-forward geiser-stklos--guess-re nil t)))
+
+;;; REPL startup
+
+;; Minimum version of STklos supported. If a less recent version
+;; is used, Geiser will refuse to start.
+(defconst geiser-stklos-minimum-version "1.40")
+
+;; this function obtains the version of the STklos binary
+;; available.
+(defun geiser-stklos--version (binary)
+ ;; use SRFI-176!!!
+ (cadr (assoc 'version
+ (read (shell-command-to-string
+ (concat binary
+ " -e \"(write (version-alist))\"" ))))))
+
+
+;; Function ran at startup
+(defun geiser-stklos--startup (remote)
+ (let ((geiser-log-verbose-p t))
+ (compilation-setup t)))
+
+
+;; These are symbols that we want to be highlighted in STklos code.
+(defconst geiser-stklos-builtin-keywords
+ '("assume"
+ "fluid-let"
+ "dotimes"
+ "macro-expand"
+ "define-struct"
+ "call/ec"
+ "with-handler" ))
+
+;; The symbols that are to be highlighted as keywords, besides
+;; the standard Scheme ones
+(defun geiser-stklos--keywords ()
+ (append (geiser-syntax--simple-keywords geiser-stklos-extra-keywords)
+ (geiser-syntax--simple-keywords geiser-stklos-builtin-keywords)))
+
+
+;;; Implementation definition:
+
+(define-geiser-implementation stklos
+ (binary geiser-stklos--binary) ; ok
+ (arglist geiser-stklos--parameters) ; ok
+ (version-command geiser-stklos--version) ; ok
+ (minimum-version geiser-stklos-minimum-version) ; ok
+ (repl-startup geiser-stklos--startup) ; ok
+ (prompt-regexp geiser-stklos--prompt-regexp) ; ok
+ (debugger-prompt-regexp nil) ;; no debugger
+ (enter-debugger nil) ;; no debugger
+ (marshall-procedure geiser-stklos--geiser-procedure)
+ (find-module geiser-stklos--get-module)
+ (enter-command geiser-stklos--enter-command) ; ok
+ (exit-command geiser-stklos--exit-command) ; ok
+ (import-command geiser-stklos--import-command) ; ok
+ (find-symbol-begin geiser-stklos--symbol-begin) ; ok
+ (display-error geiser-stklos--display-error)
+ ;; (external-help geiser-stklos--manual-look-up) ;; cannot easily search by
keyword
+ (check-buffer geiser-stklos--guess)
+ (keywords geiser-stklos--keywords) ; ok
+ (case-sensitive geiser-stklos-case-sensitive) ; ok
+ )
+
+(geiser-impl--add-to-alist 'regexp "\\.scm$" 'stklos t)
+(geiser-impl--add-to-alist 'regexp "\\.stk$" 'stklos t)
+
+
+(provide 'geiser-stklos)
diff --git a/geiser.stk b/geiser.stk
new file mode 100644
index 0000000..7610fdc
--- /dev/null
+++ b/geiser.stk
@@ -0,0 +1,250 @@
+;;; geiser.stk -- STklos Scheme implementation of the geiser protocols
+
+;; Author: Jeronimo Pellegrini <j_p@aleph0.info>
+;; Maintainer:
+;; Keywords: languages, stklos, scheme, geiser
+;; Homepage:
+;; Package-Requires: ((emacs "24.4") (geiser-core "1.0"))
+;; SPDX-License-Identifier: BSD-3-Clause
+;; Version: 1.0
+
+;; executes thunk, with all its output (standar and error) redirected
+;; to a string.
+(define (with-all-output-to-string thunk)
+ (let ((out (open-output-string)))
+ (with-error-to-port out
+ (lambda ()
+ (with-output-to-port out
+ thunk)))
+ (close-output-port out)
+ (get-output-string out)))
+
+
+;; call-with-result wraps output from geiser functions.
+;; The result is an assoc list with the keys:
+;;
+;; - result: the CAR is a list with the values returned
+;; - output: the CDR is a string with the output
+;;
+;; Example:
+;;
+;; (call-with-result (lambda () (display "OK") 42))
+;; => ((result "42") (output . "OK"))
+;;
+;; (call-with-result (lambda () (display "OK") (values 1 2 3)))
+;; => ((result "1" "2" "3") (output . "OK"))
+;;
+(define (call-with-result thunk)
+ (let* ((result (if #f #f))
+ (output (if #f #f)))
+
+ (set! output
+ (with-handler (lambda (exn)
+ (with-output-to-string
+ (lambda () (write (error-object-message 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-all-output-to-string (lambda () (write
v)))) result))
+ ((eq? result (if #f #t))
+ ;; '())
+ (list output))
+ (else
+ (list (with-all-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 (write-to-log form)
+ (values))
+
+;; evaluates form inside a module.
+;; the result is in the same format as call-with-result.
+;;
+;; Example:
+;; (geiser:eval #f '(begin (display "OK") (values 1 2 3)))
+;; => ((result "1" "2" "3") (output . "OK"))
+;;
+(define (geiser:eval module-name form . rest)
+
+ ;; All calls start at toplevel
+ (let ((module (or (and (symbol? module-name )
+ (find-module module-name))
+ (find-module 'stklos))))
+ (let ((thunk (guard
+ (err
+ (else
+ (write `((error (key . ,(error-object-message err)))))))
+ (lambda () (eval form module)))))
+
+ (write-to-log form)
+ (call-with-result 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
+ (lambda ()
+ (when found-file
+ (load found-file))))))
+
+;; Geiser calls this function to add a string to STklos'
+;; load path
+(define (geiser:add-to-load-path path)
+ (load-path (cons path (load-path))))
+
+;; Geiser will call this procedure when it wants to
+;; show the macro-expansion of a form.
+(define (geiser:macroexpand form . rest)
+ (format "~S" (macro-expand form)))
+
+
+
+;; do not use string-index, because the native STklos version
+;; is different from that in SRFI-13, and we can't tell in advance
+;; what's the correct way to call it...
+(define (name-match-with-start? prefix name i)
+ (cond ((< (string-length name) (string-length prefix)) #f)
+ ((>= i (string-length prefix)) #t)
+ ((and (< i (string-length prefix))
+ (eq? (string-ref prefix i)
+ (string-ref name i)))
+ (name-match-with-start? prefix name (+ 1 i)))
+ (else #f)))
+(define (name-match? prefix name) (name-match-with-start? prefix name 0))
+
+;; Geiser calls this procedure when it wants to complete
+;; a module's name for the user. We check if prefix is
+;; the prefix of a known module.
+;;
+;; Issue: we should be able to complete the names of
+;; arbitrary modules, including those which were not
+;; loaded yet, but that would be a bit too complex.
+(define (geiser:module-completions prefix . rest)
+ (filter (lambda (name) (name-match? prefix name))
+ (map symbol->string
+ (map module-name (all-modules)))))
+
+;; symbol completion. not sure if completing with all the symbols
+;; from the current module is OK, but it's a start...
+(define (geiser:completions prefix)
+ (filter (lambda (name) (name-match? prefix name))
+ (map symbol->string
+ (module-symbols (current-module)))))
+
+;; used in module-exports...
+(define-syntax push!
+ (syntax-rules ()
+ ((push! el lst)
+ (set! lst (cons el lst)))))
+
+;; given a module name (either symbol or string), return a list of
+;; symbols exported by the module. This list will be used as an assoc
+;; list, and the keys are:
+;;
+;; - "modules" - the CDR is a list of module names, each one wrapped in a list
+;; - "procs" - the CDR is a list of procedure names, each one wrapped in a list
+;; - "syntax" - the CDR is a list of macro names, each one wrapped in a list
+;; - "vars" - the CDR is a list of variable names, each one wrapped in a list
+;;
+;; Example:
+;;
+;; (define-module x
+;; (export p q v s)
+;; (define v 100)
+;; (define (p) 200)
+;; (define (q) 300)
+;; (define-syntax s
+;; (syntax-rules ()
+;; ((s x) (- x)))))
+;;
+;;
+;; (geiser:module-exports 'x)
+;; => (list ("modules") ("procs" (p) (q)) ("syntax" (s)) ("vars" (v)))
+;;
+(define (geiser:module-exports module-name . rest)
+ (cond ((string? module-name)
+ (geiser:module-exports (string->symbol module-name)))
+ ((symbol? module-name)
+ (let ((module (find-module module-name)))
+ (let ((exports (module-exports module)))
+ (let ((procs '())
+ (syntaxes '())
+ (vars '()))
+ (for-each (lambda (name)
+ ;; STklos does not recognize macro names, but we can
+ ;; tell that, if it is exported, and does not have
a value,
+ ;; then it is syntax!
+ (let ((obj (symbol-value name module
:geiser-stklos--it-is-syntax)))
+ (cond ((procedure? obj)
+ (push! (list name) procs))
+ ((eq? obj :geiser-stklos--it-is-syntax)
+ (push! (list name) syntaxes))
+ (else
+ (push! (list name) vars)))))
+ exports)
+ `(list ("modules") ("procs" . ,procs) ("syntax" . ,syntaxes)
("vars" . ,vars))))))
+ (else '())))
+
+
+;; returns the documentation for a symbol
+(define (geiser:symbol-documentation name)
+ (with-output-to-string
+ (lambda () (help (eval (eval name))))))
+
+;; used for autodoc. returns the documentation for a symbol.
+(define (geiser:autodoc names . rest)
+ (cond ((null? names) '())
+ ((not (list? names))
+ (geiser:autodoc (list names)))
+ ((symbol? (car names))
+ (with-output-to-string
+ (lambda () (help (car names)))))
+ (else "")))
+
+;; The no-values identity
+(define (geiser:no-values)
+ (values))
+
+(define geiser-stklos-load-paths (make-parameter '("" ".")))
+
+(define (geiser-find-file file . rest)
+ (when file
+ (let ((paths (geiser-stklos-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
+ (lambda ()
+ (when (directory-exists? directory)
+ (geiser-stklos-load-paths (cons directory
(geiser-stklos-load-paths))))))))
+
+(define (geiser-compile-file file . rest)
+ #f)
+
+(define (geiser-compile form module . rest)
+ (error "STklos does not support compiling regions"))
- [nongnu] elpa/geiser-stklos a02d2a7 13/30: Merge branch 'master' of gitlab.com:emacs-geiser/stklos into master, (continued)
- [nongnu] elpa/geiser-stklos a02d2a7 13/30: Merge branch 'master' of gitlab.com:emacs-geiser/stklos into master, Philip Kaludercic, 2021/08/01
- [nongnu] elpa/geiser-stklos 52acf7c 24/30: Fix fatal thinko (docstring / const value mixup), Philip Kaludercic, 2021/08/01
- [nongnu] elpa/geiser-stklos be482a0 30/30: Fix tests and describe them in README, Philip Kaludercic, 2021/08/01
- [nongnu] elpa/geiser-stklos eb2b13f 02/30: Geiser functions are now in a STklos module, Philip Kaludercic, 2021/08/01
- [nongnu] elpa/geiser-stklos 2fe100a 12/30: Update README, Philip Kaludercic, 2021/08/01
- [nongnu] elpa/geiser-stklos 2733115 18/30: Add missing end marker in .el file, Philip Kaludercic, 2021/08/01
- [nongnu] elpa/geiser-stklos 033b585 16/30: Fix headers for inclusion in MELPA, Philip Kaludercic, 2021/08/01
- [nongnu] elpa/geiser-stklos d4aa8df 19/30: Some changes to headers, Philip Kaludercic, 2021/08/01
- [nongnu] elpa/geiser-stklos 47cbf16 26/30: Fixing docstrings and style issues, Philip Kaludercic, 2021/08/01
- [nongnu] elpa/geiser-stklos 0e3a057 28/30: Enhance documentation in Commentary section, Philip Kaludercic, 2021/08/01
- [nongnu] elpa/geiser-stklos fc33f45 01/30: Initial Commit,
Philip Kaludercic <=
- [nongnu] elpa/geiser-stklos 38dba89 09/30: Minimum version of STklos supported is 1.50, Philip Kaludercic, 2021/08/01
- [nongnu] elpa/geiser-stklos 1f2a6e6 08/30: Update README.md - mention autodoc issue, Philip Kaludercic, 2021/08/01
- [nongnu] elpa/geiser-stklos 80b05d0 03/30: Disable autodoc, Philip Kaludercic, 2021/08/01
- [nongnu] elpa/geiser-stklos 4ca3c84 23/30: Version 1.3, Philip Kaludercic, 2021/08/01