;;; valeri-mode.el --- a major-mode for editing Valeri programs -*- lexical-binding: t -*- (require 'comint) (defcustom valeri-default-application "valeri" "Default application to run in Valeri process." :type 'string :group 'valeri) (defcustom valeri-default-command-switches (list) "Command switches for `valeri-default-application'. Should be a list of strings." :type '(repeat string) :group 'valeri) (defcustom valeri-prompt-regexp "\\(valeri>[\t ]+\\)+" "Regexp which matches the Valeri program's prompt." :type 'regexp :group 'valeri) (defcustom valeri-traceback-line-re ;; This regexp skips prompt and meaningless "stdin:N:" prefix when looking ;; for actual file-line locations. "^\\(?:[\t ]*\\|.*>[\t ]+\\)\\(?:[^\n\t ]+:[0-9]+:[\t ]*\\)*\\(?:\\([^\n\t ]+\\):\\([0-9]+\\):\\)" "Regular expression that describes tracebacks and errors." :type 'regexp :group 'valeri) (defvar valeri-process-init-code (mapconcat 'identity '("") " ")) (defvar valeri-process nil "The active Valeri process") (defvar valeri-process-buffer nil "Buffer used for communication with the Valeri process.") (defvar valeri--repl-buffer-p nil "Buffer-local flag saying if this is a Valeri REPL buffer.") (make-variable-buffer-local 'valeri--repl-buffer-p) (defvar valeri-eoe-indicator "valeri-eoe") (defun valeri-get-create-process () "Return active Valeri process creating one if necessary." (valeri-start-process) valeri-process) (defun valeri-send-string (buffer body) "Pass BODY to the Valeri process in BUFFER." (let ((escaped-body (format "\x1b[200~%s\x1b[201~" body)) (eoe-string (format "\n(println \"%s\")\n" valeri-eoe-indicator))) (mapconcat #'identity (butlast (split-string (mapconcat #'org-trim (org-babel-comint-with-output (buffer valeri-eoe-indicator t escaped-body) (insert (org-babel-chomp escaped-body) eoe-string) (comint-send-input nil t)) "\n") "[\r\n]")) "\n") ) ) ;;;###autoload (define-derived-mode valeri-mode lisp-mode "Valeri" "Major mode for editing Valeri code." :group 'valeri ) ;;;###autoload (defalias 'run-valeri #'valeri-start-process) ;;;###autoload (defun valeri-start-process (&optional name program startfile &rest switches) "Start a Valeri process named NAME, running PROGRAM. PROGRAM defaults to NAME, which defaults to `valeri-default-application'. When called interactively, switch to the process buffer." (interactive) (setq name (or name valeri-default-application)) (setq program (or program valeri-default-application)) ;; don't re-initialize if there already is a Valeri process (unless (comint-check-proc (format "*%s*" name)) (setq valeri-process-buffer (apply #'make-comint name program startfile (or switches valeri-default-command-switches))) (setq valeri-process (get-buffer-process valeri-process-buffer)) (set-process-query-on-exit-flag valeri-process nil) (with-current-buffer valeri-process-buffer ;; enable error highlighting in stack traces (require 'compile) (setq valeri--repl-buffer-p t) (make-local-variable 'compilation-error-regexp-alist) (setq compilation-error-regexp-alist (cons (list valeri-traceback-line-re 1 2) compilation-error-regexp-alist)) (compilation-shell-minor-mode 1) (setq-local comint-prompt-regexp valeri-prompt-regexp) ;; Don't send initialization code until seeing the prompt to ensure that ;; the interpreter is ready. ;; (while (not (valeri-prompt-line)) ;; (accept-process-output (get-buffer-process (current-buffer))) ;; (goto-char (point-max))) ;; (valeri-send-string valeri-process-init-code) )) ;; when called interactively, switch to process buffer (if (called-interactively-p 'any) (switch-to-buffer valeri-process-buffer))) (defun valeri-prompt-line () (save-excursion (save-match-data (forward-line 0) (if (looking-at comint-prompt-regexp) (match-end 0))))) (defun valeri-comint-end-of-output-p (output) "Return non-nil if OUTPUT ends with input prompt." (string-match ;; XXX: It seems on macOS an extra carriage return is attached ;; at the end of output, this handles that too. (concat "\r?\n?" ;; Remove initial caret from calculated regexp (replace-regexp-in-string (rx string-start ?^) "" valeri-prompt-regexp) (rx eos)) output)) (provide 'valeri-mode) ;;; valeri-mode.el ends here