;;; geiser-chicken.el --- Chicken's implementation of the geiser protocols -*- lexical-binding: t; -*- ;; Copyright (C) 2014, 2015, 2019, 2020, 2021 Daniel Leslie ;; Based on geiser-guile.el by Jose Antonio Ortega Ruiz ;; Author: Daniel Leslie ;; Maintainer: Daniel Leslie ;; Keywords: languages, chicken, scheme, geiser ;; Homepage: https://gitlab.com/emacs-geiser/chicken ;; Package-Requires: ((emacs "24.4") (geiser "0.19")) ;; SPDX-License-Identifier: BSD-3-Clause ;; Version: 0.17 ;; This file is NOT part of GNU Emacs. ;;; Commentary: ;; This package extends the `geiser' core package to support ;; Chicken. ;;; Code: (require 'geiser-impl) (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)) (defconst geiser-chicken-builtin-keywords '("assume" "compiler-typecase" "cond-expand" "condition-case" "declare" "define-constant" "define-inline" "define-interface" "define-record" "define-specialization" "define-type" "dotimes" "ecase" "fluid-let" "foreign-lambda" "foreign-lambda*" "foreign-primitive" "foreign-safe-lambda" "foreign-safe-lambda*" "functor" "handle-exceptions" "let-location" "let-optionals" "let-optionals*" "letrec-values" "module" "regex-case" "select" "use" "with-input-from-pipe")) ;;; Customization: (defgroup geiser-chicken nil "Customization for Geiser's Chicken flavour." :group 'geiser) (geiser-custom--defcustom geiser-chicken-binary (cond ((eq system-type 'windows-nt) '("csi.exe" "-:c")) ((eq system-type 'darwin) "csi") ((executable-find "chicken-csi") "chicken-csi") (t "csi")) "Name to use to call the Chicken executable when starting a REPL." :type '(choice string (repeat string)) :group 'geiser-chicken) (geiser-custom--defcustom geiser-chicken-load-path nil "A list of paths to be added to Chicken's load path when it's started." :type '(repeat file) :group 'geiser-chicken) (geiser-custom--defcustom geiser-chicken-init-file "~/.chicken-geiser" "Initialization file with user code for the Chicken REPL. If all you want is to load ~/.csirc, set `geiser-chicken-load-init-file-p' instead." :type 'string :group 'geiser-chicken) (geiser-custom--defcustom geiser-chicken-load-init-file-p nil "Whether to load ~/.chicken when starting Chicken. Note that, due to peculiarities in the way Chicken loads its init file, using `geiser-chicken-init-file' is not equivalent to setting this variable to t." :type 'boolean :group 'geiser-chicken) (geiser-custom--defcustom geiser-chicken-extra-keywords nil "Extra keywords highlighted in Chicken scheme buffers." :type '(repeat string) :group 'geiser-chicken) (geiser-custom--defcustom geiser-chicken-case-sensitive-p t "Non-nil means keyword highlighting is case-sensitive." :type 'boolean :group 'geiser-chicken) (geiser-custom--defcustom geiser-chicken-match-limit 20 "The limit on the number of matching symbols that Chicken will provide to Geiser." :type 'integer :group 'geiser-chicken) ;;; REPL support: (defvar geiser-chicken-scheme-dir (expand-file-name "src" (file-name-directory load-file-name)) "Directory where the Chicken scheme geiser modules are installed.") (defun geiser-chicken--binary () "Return path to chicken executable." (if (listp geiser-chicken-binary) (car geiser-chicken-binary) geiser-chicken-binary)) (defun geiser-chicken--parameters () "Return a list with all parameters needed to start Chicken. This function uses `geiser-chicken-init-file' if it exists." (let ((init-file (and (stringp geiser-chicken-init-file) (expand-file-name geiser-chicken-init-file))) (n-flags (and (not geiser-chicken-load-init-file-p) '("-n")))) `(,@(and (listp geiser-chicken-binary) (cdr geiser-chicken-binary)) ,@n-flags "-include-path" ,geiser-chicken-scheme-dir ,@(apply 'append (mapcar (lambda (p) (list "-include-path" p)) geiser-chicken-load-path)) ,@(and init-file (file-readable-p init-file) (list init-file))))) (defconst geiser-chicken--prompt-regexp "#[^;]*;[^:0-9]*:?[0-9]+> ") ;;; Evaluation support: (defun geiser-chicken--geiser-procedure (proc &rest args) "Transform PROC in string for a scheme procedure using ARGS." (cl-case proc ((eval compile) (let ((form (mapconcat 'identity (cdr args) " ")) (module (if (car args) (concat "'" (car args)) "#f"))) (format "(geiser#geiser-eval %s '%s)" module form))) ((load-file compile-file) (format "(geiser#geiser-load-file %s)" (car args))) ((no-values) "(geiser#geiser-no-values)") (t (let ((form (mapconcat 'identity args " "))) (format "(geiser#geiser-%s %s)" proc form))))) (defconst geiser-chicken--module-re "( *module +\\(([^)]+)\\|[^ ]+\\)") (defconst geiser-chicken--define-library-re "( *define-library +\\(([^)]+)\\)") (defun geiser-chicken--get-module (&optional module) "Find current buffer's module using MODULE as a hint." (cond ((null module) (save-excursion (geiser-syntax--pop-to-top) (if (or (re-search-backward geiser-chicken--module-re nil t) (re-search-backward geiser-chicken--define-library-re nil t) (re-search-forward geiser-chicken--module-re nil t) (re-search-forward geiser-chicken--define-library-re nil t)) (geiser-chicken--get-module (match-string-no-properties 1)) :f))) ((listp module) module) ((stringp module) (condition-case nil (car (geiser-syntax--read-from-string module)) (error :f))) (t :f))) (defun geiser-chicken--module-cmd (module fmt &optional def) "Use FMT to format a change to MODULE, with default DEF." (when module (let* ((module (geiser-chicken--get-module module)) (module (cond ((or (null module) (eq module :f)) def) (t (format "%s" module))))) (and module (format fmt module))))) (defun geiser-chicken--import-command (module) "Format a sexp to use MODULE." (geiser-chicken--module-cmd module "(use %s)")) (defun geiser-chicken--enter-command (module) "Format a REPL command to enter MODULE." (geiser-chicken--module-cmd module ",m %s" module)) (defun geiser-chicken--exit-command () "Format a REPL command to quit." ",q") (defun geiser-chicken--symbol-begin (module) "Find beginning of symbol, in the context of MODULE." (if module (max (save-excursion (beginning-of-line) (point)) (save-excursion (skip-syntax-backward "^(>") (1- (point)))) (save-excursion (skip-syntax-backward "^'-()>") (point)))) ;;; Error display (defun geiser-chicken--display-error (_module key msg) "Display an error with key KEY and message MSG." (newline) (when (stringp msg) (save-excursion (insert msg)) (geiser-edit--buttonize-files)) (and (not key) msg (not (zerop (length msg))))) ;;; Trying to ascertain whether a buffer is Chicken Scheme: (defconst geiser-chicken--guess-re (regexp-opt '("csi" "chicken" "csc"))) (defun geiser-chicken--guess () "Try to detect whether we're in a Chicken scheme buffer." (save-excursion (goto-char (point-min)) (re-search-forward geiser-chicken--guess-re nil t))) (defun geiser-chicken--external-help (id _module) "Load chicken doc for ID into a buffer." (let* ((version (geiser-chicken--version (geiser-chicken--binary))) ;; C4 documentation is no longer available online. (cdoc (if (version< version "6.0.0") "cdoc5" "cdoc"))) (browse-url (format "http://api.call-cc.org/%s?q=%s&query-name=Look+up" cdoc (url-hexify-string (symbol-name id)))))) ;;; Keywords and syntax (defun geiser-chicken--keywords () "Return Chicken-specific scheme keywords." (append (geiser-syntax--simple-keywords geiser-chicken-extra-keywords) (geiser-syntax--simple-keywords geiser-chicken-builtin-keywords))) (geiser-syntax--scheme-indent (assume 1) (compiler-typecase 1) (cond-expand 0) (condition-case 1) (cut 1) (cute 1) (declare 0) (dotimes 1) (ecase 1) (fluid-let 1) (foreign-lambda 2) (foreign-lambda* 2) (foreign-primitive 2) (foreign-safe-lambda 2) (foreign-safe-lambda* 2) (functor 3) (handle-exceptions 2) (import 0) (let-location 1) (let-optionals 2) (let-optionals* 2) (letrec-values 1) (module 2) (regex-case 1) (select 1) (set! 1) (use 0) (with-input-from-pipe 1) (with-output-to-pipe 1)) ;;; REPL startup (defconst geiser-chicken-minimum-version "4.8.0.0") (defun geiser-chicken--version (binary) "Find Chicken's version using BINARY." (cl-destructuring-bind (program . args) (append (if (listp binary) binary (list binary)) '("-e" "(display \ (or (handle-exceptions exn \ #f \ (eval `(begin (import chicken.platform) \ (chicken-version)))) \ (chicken-version)))")) (with-temp-buffer (apply #'call-process program nil '(t t) t args) (buffer-string)))) (defun connect-to-chicken () "Start a Chicken REPL connected to a remote process." (interactive) (geiser-connect 'chicken)) (defun geiser-chicken4--compile-or-load (force-load) "Compile or load Geiser support code in Chiken4, forcibly if FORCE-LOAD is t." (let ((target (expand-file-name "geiser/chicken4.so" geiser-chicken-scheme-dir)) (source (expand-file-name "geiser/chicken4.scm" geiser-chicken-scheme-dir)) (force-load (or force-load (eq system-type 'windows-nt))) (suppression-prefix "(define geiser-stdout (current-output-port))(current-output-port (make-output-port (lambda a #f) (lambda a #f)))") (suppression-postfix "(current-output-port geiser-stdout)")) (let ((load-sequence (cond (force-load (format "(load \"%s\")\n(import geiser)\n" source)) ((file-exists-p target) (format "%s(load \"%s\")(import geiser)%s\n" suppression-prefix target suppression-postfix)) (t (format "%s(use utils)(compile-file \"%s\" options: '(\"-O3\" \"-s\") output-file: \"%s\" load: #t)(import geiser)%s\n" suppression-prefix source target suppression-postfix))))) (geiser-eval--send/wait load-sequence)))) (defun geiser-chicken-load () "Load Geiser support code in modern Chicken versions." (let* ((source (expand-file-name "geiser/chicken.scm" geiser-chicken-scheme-dir)) (buffer " *geiser-chicken-load*") (contents (with-current-buffer (get-buffer-create buffer t) (insert-file-contents-literally source) (prog1 (buffer-string) (kill-buffer buffer))))) (geiser-eval--send/wait (format "(display '((result . t) (output . f))) %s" contents)))) (defun geiser-chicken--startup (_remote) "Startup function." (compilation-setup t) (cond ((version< (geiser-chicken--version geiser-chicken-binary) "5.0.0") (geiser-chicken4--compile-or-load t)) (t (geiser-chicken-load)))) ;;; Implementation definition: (define-geiser-implementation chicken (unsupported-procedures '(callers callees generic-methods)) (binary geiser-chicken--binary) (arglist geiser-chicken--parameters) (version-command geiser-chicken--version) (minimum-version geiser-chicken-minimum-version) (repl-startup geiser-chicken--startup) (prompt-regexp geiser-chicken--prompt-regexp) (debugger-prompt-regexp nil) (enter-debugger nil) (marshall-procedure geiser-chicken--geiser-procedure) (find-module geiser-chicken--get-module) (enter-command geiser-chicken--enter-command) (exit-command geiser-chicken--exit-command) (import-command geiser-chicken--import-command) (find-symbol-begin geiser-chicken--symbol-begin) (display-error geiser-chicken--display-error) (external-help geiser-chicken--external-help) (check-buffer geiser-chicken--guess) (keywords geiser-chicken--keywords) (case-sensitive geiser-chicken-case-sensitive-p)) ;;;###autoload (geiser-activate-implementation 'chicken) ;;;###autoload (geiser-implementation-extension 'chicken "release-info") ;;;###autoload (geiser-implementation-extension 'chicken "meta") ;;;###autoload (geiser-implementation-extension 'chicken "setup") ;;;###autoload (autoload 'run-chicken "geiser-chicken" "Start a Geiser Chicken REPL." t) ;;;###autoload (autoload 'switch-to-chicken "geiser-chicken" "Start a Geiser Chicken REPL, or switch to a running one." t) ;;;###autoload (autoload 'connect-to-chicken "geiser-chicken" "Connect to a remote Geiser Chicken REPL." t) (provide 'geiser-chicken) ;;; geiser-chicken.el ends here