;;;; A syntax highlighting and indentation mode for the altarica language

(defvar altarica-mode-hook nil)
(add-to-list 'auto-mode-alist '("\\.alt\\'" . altarica-mode))

;; define keyword classes
(setq altarica-struct-regexp
      (regexp-opt '("node" "edon" "domain" "const" "in" "out" "flow" "state"
                    "assert" "event" "trans" "extern" "init" "param" "param_set"
                    "sort" "sub" "struct" "tcurts" "sync")
                  'words))
(setq altarica-builtin-regexp
      (regexp-opt '("and" "or" "not" "imply" "max" "min" "mod" "sig"
                    "if" "then" "else" "case" "esac" "law" "constant"
                    "exponential" "dirac")
                  'words))
(setq altarica-type-regexp (regexp-opt '("int" "integer" "bool") 'words))
(setq altarica-bconstants-regexp (regexp-opt '("true" "false") 'words))
(setq altarica-nconstants-regexp "[[:digit:]]+\\(.[[:digit:]]+\\)?")

(setq altarica-font-lock-keywords
      `((,altarica-struct-regexp     . font-lock-keyword-face)
        (,altarica-builtin-regexp    . font-lock-builtin-face)
        (,altarica-type-regexp       . font-lock-type-face)
        (,altarica-bconstants-regexp . font-lock-constant-face)
        (,altarica-nconstants-regexp . font-lock-constant-face)
        ;; highlight transition symbols
        (,"\\(|-[^-]*->\\)" . font-lock-function-name-face)
        ;; bold assignments and question marks
        (,"\\(\\?\\|:=\\)" (1 '(:weight bold)))
        ;; highlight types in domain declaration
        (,"domain[ \t]+\\([[:alnum:]_]+\\)" (1 font-lock-type-face))
        ;; highlight types in node declaration
        (,"node[ \t]+\\([[:alnum:]_]+\\)" (1 font-lock-type-face))
        ;; highlight types in state/sub declaration
        (,(concat "^[ \t]*\\(sub\\|state\\)?"
                  "[ \t]*[[:alnum:]_]+[ \t]*:"
                  "[ \t]*\\([[:alnum:]_]+\\)[ \t]*;")
         (2 font-lock-type-face))
         ;; highlight types in flow declaration
         (,(concat "[ \t]*\\(flow\\)?"
                   "[ \t]*[[:alnum:]_]+[ \t]*:"
                   "[ \t]*\\([[:alnum:]_]+\\)[ \t]*:"
                   "[ \t]*\\(in\\|out\\)[ \t]*;")
          (2 font-lock-type-face))))

(defvar altarica-basic-offset 2)

(defun altarica-indent-line ()
  "Indent current line as altarica code"
  (interactive)
  ;; without this indent-line-to moves the point to beginning of line
  (let ((altarica-kw-main
         "^[ \t]*\\(node\\|domain\\|const\\|edon\\)\\([^a-zA-Z_0-9].*\\)?$")
        (altarica-kw-secondary
         (concat "^[ \t]*\\(flow\\|state\\|assert\\|"
                 "event\\|trans\\|init\\|extern\\|sub\\|sync\\)"))
        (indent-unit altarica-basic-offset)
        next-indent)
    (save-excursion
      (beginning-of-line)
      (setq next-indent
            (cond ((bobp) 0)
                  ((looking-at altarica-kw-main) 0)
                  ((looking-at altarica-kw-secondary) indent-unit)
                  ((looking-at "^[ \t]*|-") (* 3 indent-unit))
                  (t nil)))
      (if (not next-indent)
          (progn
            (while (not next-indent)
              (forward-line -1)
              (setq next-indent
                    (cond ((bobp) 0)
                          ;; previous line is primary keyword
                          ((looking-at altarica-kw-main) indent-unit)
                          ;; previous line is secondary keyword
                          ((looking-at altarica-kw-secondary) (* 2 indent-unit))
                          ;; previous line ends with semicolon
                          ((looking-at ".*;[ \t]*$") (* 2 indent-unit))
                          ;; previous line ends with "->"
                          ((looking-at ".*->[ \t]*$") (* 4 indent-unit))
                          ;; previous line contains "->"
                          ((re-search-forward "->[ \t]*"
                                              (line-end-position)
                                              t)
                           (current-column))
                          ;; previous line contains "|-"
                          ((looking-at "^[ \t]*|-") (* 4 indent-unit))
                          ;; previous line ends with "case { "
                          ((re-search-forward "case[ \t]*{[ \t]*$"
                                              (line-end-position)
                                              t)
                           (+ 2 (- (match-beginning 0) 
                                   (line-beginning-position))))
                          ;; previous line contains "case { "
                          ((re-search-forward "case[ \t]*{[ \t]*"
                                              (line-end-position)
                                              t)
                           (current-column))
                          ;; else look at previous previous line
                          (t nil)))))))
    (setq offset (max 0 (- (current-column) (current-indentation))))
    (indent-line-to next-indent)
    (move-to-column (+ (current-indentation) offset))))

;; command to comment/uncomment text
(defun altarica-comment-dwim (arg)
  "Comment or uncomment current line or region in a smart way.
For detail, see `comment-dwim'."
  (interactive "*P")
  (require 'newcomment)
  (let ((comment-start "//") (comment-end ""))
    (comment-dwim arg)))

;; Syntax table magic ... yadda yadda abracadabra ... COMMENTS !!
(defvar altarica-syntax-table nil "Syntax table for `altarica-mode'.")
(setq altarica-syntax-table
      (let ((table (make-syntax-table)))
        ;; make underscore a word char
        (modify-syntax-entry ?_ "w" table)
        ;; detect c++ comments
        (modify-syntax-entry ?/ ". 124" table)
        (modify-syntax-entry ?* ". 23b" table)
        (modify-syntax-entry ?\n ">" table)
        table))

;; define the mode
(define-derived-mode altarica-mode fundamental-mode
  "Major mode for editing altarica"
  (set-syntax-table altarica-syntax-table)

  (setq font-lock-defaults '(altarica-font-lock-keywords))
  (setq indent-line-function 'altarica-indent-line)
  (setq mode-name "altarica mode")

  ;; modify the keymap
  (define-key altarica-mode-map [remap comment-dwim] 'altarica-comment-dwim)

  ;; clear memory
  (setq altarica-struct-regexp nil)
  (setq altarica-func-regexp nil)
  (setq altarica-builtin-regexp nil)
  (setq altarica-type-regexp nil)
  (setq altarica-constants-regexp nil)
  (setq altarica-symbols-regexp nil)
)
