;; jai-mode.el - very basic jai mode (require 'cl) (require 'rx) (require 'js) (require 'compile) (defconst jai-mode-syntax-table (let ((table (make-syntax-table))) (modify-syntax-entry ?\" "\"" table) (modify-syntax-entry ?\\ "\\" table) ;; additional symbols (modify-syntax-entry ?_ "w" table) (modify-syntax-entry ?' "." table) (modify-syntax-entry ?: "." table) (modify-syntax-entry ?+ "." table) (modify-syntax-entry ?- "." table) (modify-syntax-entry ?% "." table) (modify-syntax-entry ?& "." table) (modify-syntax-entry ?| "." table) (modify-syntax-entry ?^ "." table) (modify-syntax-entry ?! "." table) (modify-syntax-entry ?$ "/" table) (modify-syntax-entry ?= "." table) (modify-syntax-entry ?< "." table) (modify-syntax-entry ?> "." table) (modify-syntax-entry ?? "." table) ;; Modify some syntax entries to allow nested block comments (modify-syntax-entry ?/ ". 124b" table) (modify-syntax-entry ?* ". 23n" table) (modify-syntax-entry ?\n "> b" table) (modify-syntax-entry ?\^m "> b" table) table)) (defconst jai-builtins '("cast" "it" "type_info" "size_of")) (defconst jai-keywords '("if" "ifx" "else" "then" "while" "for" "switch" "case" "struct" "enum" "return" "new" "remove" "continue" "break" "defer" "inline" "no_inline" "using" "SOA")) (defconst jai-constants '("null" "true" "false")) (defconst jai-typenames '("int" "u64" "u32" "u16" "u8" "s64" "s32" "s16" "s8" "float" "float32" "float64" "string" "bool")) (defun jai-wrap-word-rx (s) (concat "\\<" s "\\>")) (defun jai-keywords-rx (keywords) "build keyword regexp" (jai-wrap-word-rx (regexp-opt keywords t))) (defconst jai-hat-type-rx (rx (group (and "^" (1+ word))))) (defconst jai-dollar-type-rx (rx (group "$" (or (1+ word) (opt "$"))))) (defconst jai-number-rx (rx (and symbol-start (or (and (+ digit) (opt (and (any "eE") (opt (any "-+")) (+ digit)))) (and "0" (any "xX") (+ hex-digit))) (opt (and (any "_" "A-Z" "a-z") (* (any "_" "A-Z" "a-z" "0-9")))) symbol-end))) (defconst jai-font-lock-defaults `( ;; Keywords (,(jai-keywords-rx jai-keywords) 1 font-lock-keyword-face) ;; single quote characters ("\\('[[:word:]]\\)\\>" 1 font-lock-constant-face) ;; Variables (,(jai-keywords-rx jai-builtins) 1 font-lock-variable-name-face) ;; Constants (,(jai-keywords-rx jai-constants) 1 font-lock-constant-face) ;; Hash directives ("#\\w+" . font-lock-preprocessor-face) ;; At directives ("@\\w+" . font-lock-preprocessor-face) ;; Strings ("\\\".*\\\"" . font-lock-string-face) ;; Numbers (,(jai-wrap-word-rx jai-number-rx) . font-lock-constant-face) ;; Types (,(jai-keywords-rx jai-typenames) 1 font-lock-type-face) (,jai-hat-type-rx 1 font-lock-type-face) (,jai-dollar-type-rx 1 font-lock-type-face) ("---" . font-lock-constant-face) )) ;; add setq-local for older emacs versions (unless (fboundp 'setq-local) (defmacro setq-local (var val) `(set (make-local-variable ',var) ,val))) (defconst jai--defun-rx "\(.*\).*\{") (defmacro jai-paren-level () `(car (syntax-ppss))) (defun jai-line-is-defun () "return t if current line begins a procedure" (interactive) (save-excursion (beginning-of-line) (let (found) (while (and (not (eolp)) (not found)) (if (looking-at jai--defun-rx) (setq found t) (forward-char 1))) found))) (defun jai-beginning-of-defun (&optional count) "Go to line on which current function starts." (interactive) (let ((orig-level (jai-paren-level))) (while (and (not (jai-line-is-defun)) (not (bobp)) (> orig-level 0)) (setq orig-level (jai-paren-level)) (while (>= (jai-paren-level) orig-level) (skip-chars-backward "^{") (backward-char)))) (if (jai-line-is-defun) (beginning-of-line))) (defun jai-end-of-defun () "Go to line on which current function ends." (interactive) (let ((orig-level (jai-paren-level))) (when (> orig-level 0) (jai-beginning-of-defun) (end-of-line) (setq orig-level (jai-paren-level)) (skip-chars-forward "^}") (while (>= (jai-paren-level) orig-level) (skip-chars-forward "^}") (forward-char))))) (defalias 'jai-parent-mode (if (fboundp 'prog-mode) 'prog-mode 'fundamental-mode)) ;; imenu hookup (add-hook 'jai-mode-hook (lambda () (setq imenu-generic-expression '( ("type" "^\\(.*:*.*\\) : " 1) ("function" "^\\(.*\\) :: " 1) ("struct" "^\\(.*\\) *:: *\\(struct\\)\\(.*\\){" 1) ) ) ) ) ;; NOTE: taken from the scala-indent package and modified for Jai. ;; Still uses the js-indent-line as a base, which will have to be ;; replaced when the language is more mature. (defun jai--indent-on-parentheses () (when (and (= (char-syntax (char-before)) ?\)) (= (save-excursion (back-to-indentation) (point)) (1- (point)))) (js-indent-line))) (defun jai--add-self-insert-hooks () (add-hook 'post-self-insert-hook 'jai--indent-on-parentheses) ) ;;;###autoload (define-derived-mode jai-mode jai-parent-mode "Jai" :syntax-table jai-mode-syntax-table :group 'jai (setq bidi-paragraph-direction 'left-to-right) (setq-local require-final-newline mode-require-final-newline) (setq-local parse-sexp-ignore-comments t) (setq-local comment-start-skip "\\(//+\\|/\\*+\\)\\s *") (setq-local comment-start "/*") (setq-local comment-end "*/") (setq-local indent-line-function 'js-indent-line) (setq-local font-lock-defaults '(jai-font-lock-defaults)) (setq-local beginning-of-defun-function 'jai-beginning-of-defun) (setq-local end-of-defun-function 'jai-end-of-defun) ;; add indent functionality to some characters (jai--add-self-insert-hooks) (font-lock-fontify-buffer)) ;;;###autoload (add-to-list 'auto-mode-alist '("\\.jai\\'" . jai-mode)) (defconst jai--error-regexp "^\\([^ :]+\\):\\([0-9]+\\),\\([0-9]+\\):") (push `(jai ,jai--error-regexp 1 2 3 2) compilation-error-regexp-alist-alist) (push 'jai compilation-error-regexp-alist) (provide 'jai-mode)