RASP mode
rasp-mode
is an Emacs major mode for RASP, a language for
understanding transformers. It supports syntax highlighing,
commenting, indentation and simple auto-completion.
I originally wrote rasp-mode
because I wanted to learn how to write
an Emacs major mode (also, I found the paper on RASP pretty
cool). It's simple, but it works!
Since this website is powered by Emacs's org-mode
, I can highlight
rasp code blocks with rasp-mode
. Here is an example:
def _with_bos_selector_width(s) { s = s or select(indices, 0, ==); return round((1/aggregate(s, indicator(indices == 0)))) - 1; }
Since it is short, here is the entire source code of rasp-mode
:
;;; rasp.el --- A Major Mode for writing RASP -*- lexical-binding: t; -*- ;; Copyright (C) 2021 Arthur Amalvy ;; Author: Arthur Amalvy ;; URL: https://gitlab.com/Aethor/rasp-mode ;; Package-Requires: ((emacs "24.3")) ;; Keywords: languages ;; Version: 0.0.1 ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see <https://www.gnu.org/licenses/>. ;;; Commentary: ;; ;; `rasp-mode' is a major mode for RASP (Restricted Access Sequence ;; Processing Language), a language for understanding transformers. ;;; Code: ;;; Customisation (defgroup rasp () "Major mode for editing RASP files." :group 'languages) (defcustom rasp-mode-indent-offset 4 "Number of indentation spaces in `rasp-mode'." :type 'integer :group 'rasp) ;;; auto-mode-alist ;;;###autoload (add-to-list 'auto-mode-alist '("\\.rasp\\'" . rasp-mode)) ;;; Constant definitions (defconst rasp-keywords '("not" "and" "or" "def" "return" "if" "else" "for" "True" "False") "Keywords for `rasp-mode'.") (defconst rasp-builtins '("select" "aggregate" "selector_width" "zip" "len" "range" "indicator" "round") "Builtins functions for `rasp-mode'.") (defconst rasp-constants '("tokens" "indices" "length") "Constants for `rasp-mode'.") (defconst rasp--function-regex (rx line-start (0+ space) "def" (1+ space) (group (1+ (or word ?_)))) "Match a rasp function. Match group 1 can be used to retrieve the name of the function.") (defconst rasp--variable-declaration-regex (rx line-start (0+ space) (group (1+ (or word ?_))) (0+ space) ?=) "Match a rasp variable declaration. Match group 1 can be used to retrieve the name of the variable.") ;;; Font lock (defconst rasp-font-lock-keywords (append ;; keywords (mapcar (lambda (k) (cons (rx symbol-start (literal k) symbol-end) font-lock-keyword-face)) rasp-keywords) ;; builtins functions (mapcar (lambda (k) (cons (rx symbol-start (literal k) symbol-end) font-lock-builtin-face)) rasp-builtins) ;; constants (mapcar (lambda (k) (cons (rx symbol-start (literal k) symbol-end) font-lock-constant-face)) rasp-constants) `( ;; function names (,rasp--function-regex (1 font-lock-function-name-face)) ;; variable declaration (,rasp--variable-declaration-regex (1 font-lock-variable-name-face)))) "Fontification for `rasp-mode'.") ;;; Syntax table (defvar rasp-mode-syntax-table (let ((table (make-syntax-table))) (modify-syntax-entry ?# "<" table) (modify-syntax-entry ?\n ">" table) table) "Syntax table for `rasp-mode'.") ;;; Indentation (defun rasp--at-an-unfinished-statement-p () "Check if the current line is an unfinished statement." (interactive) (and ;; beginning or end of function (not (looking-at "^.*[{}][ \t]*\\(#.*\\)?$")) ;; statement ending in ; (not (looking-at "^.*;[ \t]*\\(#.*\\)?$")) ;; empty line (not (looking-at "^[\t ]*\\(#.*\\)?$")))) (defun rasp-indent-line () "Indent current line of rasp code." (interactive) (beginning-of-line) (let ((line-indent (current-indentation)) (is-indented nil)) (save-excursion ;; look backward to figure indentation. When exiting the while, ;; `line-indent' will be set to the needed indent level (except ;; for some special cases handled at the end of the function). (while (not is-indented) (forward-line -1) (beginning-of-line) (cond ;; we see the beginning of a function: indent to beginning of ;; function + 1 since we are in its body ((looking-at "^.*{[ \t]*\\(#.*\\)?$") (setq line-indent (+ (current-indentation) rasp-mode-indent-offset)) (setq is-indented t)) ;; we see the end of function or the beginning of the file: ;; indent to the indentation of the closing brace or 0 ((or (looking-at "^.*}[ \t]*\\(#.*\\)?$") (bobp)) (setq line-indent (current-indentation)) (setq is-indented t))))) (cond ;; exception: we were at the closing brace of a function. In that ;; case, we must remove 1 to the indent level (we saw the ;; beginning of the function earlier, but the closing brace should ;; be indented at one less level as the function's body) ((looking-at "^.*}[ \t]*\\(#.*\\)?$") (indent-line-to (- line-indent rasp-mode-indent-offset))) ;; exception: unfinished statement ((save-excursion (forward-line -1) (rasp--at-an-unfinished-statement-p)) (indent-line-to (+ line-indent rasp-mode-indent-offset))) ;; standard case (t (indent-line-to line-indent))))) ;;; Completion (defun rasp--buffer-matchs (regexp &optional group) "Return all matchs of regex REGEXP in current buffer. If GROUP is specified, get all matchs for regexp group GROUP." (let ((group (or group 0)) (matchs '())) (save-excursion (goto-char 1) (while (search-forward-regexp regexp nil t 1) (push (match-string-no-properties group) matchs))) matchs)) (defun rasp-buffer-functions () "Return the names of all rasp functions in the current buffer." (rasp--buffer-matchs rasp--function-regex 1)) (defun rasp-buffer-variables () "Return all rasp variables declared in the current buffer." (rasp--buffer-matchs rasp--variable-declaration-regex 1)) (defun rasp-completion-at-point () "Completion function for `rasp-mode'. Supports rasp builtins, constants, keywords, in-buffer declared variables and functions." (let ((bounds (bounds-of-thing-at-point 'word))) (when bounds (list (car bounds) (cdr bounds) (append (rasp-buffer-variables) (rasp-buffer-functions) rasp-builtins rasp-constants rasp-keywords))))) ;;; Menu Bar (defvar rasp-mode-map (make-sparse-keymap) "Keymap for RASP mode.") (easy-menu-define rasp-menu rasp-mode-map "`rasp-mode' menu." `("RASP" ["Indent Line" rasp-indent-line])) ;;; Mode declaration ;;;###autoload (define-derived-mode rasp-mode prog-mode "Rasp" "Major mode for editing RASP code. \\{rasp-mode-map}" :group 'rasp ;; font lock (setq-local font-lock-defaults '(rasp-font-lock-keywords nil nil nil)) ;; comments (setq-local comment-start "#") (setq-local comment-start-skip "#+ *") ;; indentation (setq-local indent-line-function #'rasp-indent-line) ;; completion (add-hook 'completion-at-point-functions #'rasp-completion-at-point nil 'local)) (provide 'rasp) ;;; rasp.el ends here