(defgroup mkfile nil "editiong mode for Plan9 mkfile" :group 'tools :prefix "mkfile-") (defcustom mkfile-mode-hook nil "*Hook called by `mkfile-mode'" :type 'hook :group 'mkfile) (defconst mkfile-font-lock-keywords `(("^<|?.+$" . font-lock-preprocessor-face) ("^\\(\\w+\\)\\s-*=" 1 font-lock-variable-name-face) ("\\$\\(\\w+\\)" 1 font-lock-variable-name-face) ("\\${\\(\\w+\\)\\(:[^%=]*\\(%\\)?[^%=]*=[^%=]*\\(%\\)?[^%=]*\\)?}" (1 font-lock-variable-name-face) (3 font-lock-keyword-face) (4 font-lock-keyword-face)) (mkfile-match-bquote . font-lock-string-face) (mkfile-match-builtin-vars 1 font-lock-keyword-face t) (mkfile-match-stem 1 font-lock-keyword-face t) (mkfile-match-stemn 1 font-lock-keyword-face t) (mkfile-match-wild-card-in-meta-rule . font-lock-keyword-face) (mkfile-match-rule-attr 0 font-lock-keyword-face t) (mkfile-match-unbalance-bquote . font-lock-warning-face) (mkfile-match-invalid-rule-attr . font-lock-warning-face) )) (defconst mkfile-builtin-vars '("alltarget" "newprereq" "newmember" "nproc" "pid" "prereq" "target")) (defconst mkfile-buitin-var-pat (format "\\<\\$\\(%s\\)\\>" (regexp-opt mkfile-builtin-vars))) (defun mkfile-at-action-line-p () "Return t if the current line is action line." (save-excursion (save-match-data (beginning-of-line) (looking-at "\\s-")))) (defun mkfile-parse-rule-line () "Parse the current line as rule and return a list of positioins of beginning/end of targets/attributes/prerequisites. If the current line is not a rule, nil is returned. " (save-excursion (save-match-data (beginning-of-line) (and (looking-at "\\S-") (let ((state '()) (result (list (point)))) (while (not (eolp)) (case (char-after) ((?\\) (forward-char)) ((?\') (if (char-equal (car state) ?\') (setq state (cdr state)) (setq state (cons ?\' state)))) ((?\` ?\$) (forward-char) (when (char-equal (char-after) ?\{) (setq state (cons ?\} state)))) ((?\}) (when (char-equal (car state) ?\}) (setq state (cdr state)))) ((?:) (when (null state) (let ((p (point))) (setq result (cons (1+ p) (cons p result))))))) (forward-char)) (reverse (cons (point) result))))))) (defun mkfile-at-rule-line-p () "Return t if the current line is a rule" (and (member (length (mkfile-parse-rule-line)) '(4 6)) t)) (defun mkfile-in-meta-rule-body-p () "Return t if the current line is recipe for non-regexp meta rule." (save-excursion (and (mkfile-search-backward-rule) (save-match-data (let ((ps (mkfile-parse-rule-line))) (string-match "%" (buffer-substring-no-properties (nth 0 ps) (nth 1 ps)))))))) (defun mkfile-in-regexp-meta-rule-body-p () "Return t if the current line is recipe for regexp meta rule." (save-excursion (and (mkfile-search-backward-rule) (save-match-data (let ((ps (mkfile-parse-rule-line))) (string-match "[^P]*R" (buffer-substring-no-properties (nth 2 ps) (nth 3 ps)))))))) (defun mkfile-search-backward-rule () "Move to rule for current recipe. If the current line is not recipe, do nothing." (catch 'exit (while (and (mkfile-at-action-line-p) (not (mkfile-at-rule-line-p))) (if (bobp) (throw 'exit nil) (forward-line -1))) t)) (defun mkfile-rule-attr () "Parse the current line and return the attribute part. If the current line is not rule, nil is returned." (let ((ps (mkfile-parse-rule-line))) (and (= (length ps) 6) (let ((m (nth 2 ps)) (n (nth 3 ps))) (cons (buffer-substring-no-properties m n) (list m n)))))) (defun mkfile-match-builtin-vars (limit) (and (re-search-forward mkfile-buitin-var-pat limit t) (mkfile-at-action-line-p))) (defun mkfile-match-wild-card-in-meta-rule (limit) (and (re-search-forward "[%&]" limit t) (mkfile-at-rule-line-p))) (defun mkfile-match-rule-attr (limit) (and (re-search-forward ":.*:" limit t) (let ((str&match (mkfile-rule-attr))) (and str&match (let ((s (car str&match)) (m (cdr str&match))) (when (and (string-match "^[ \tDENnQRUV]*\\(P.+\\)?$" s) (> (- (match-end 0) (match-beginning 0)) 0)) (set-match-data m) t)))))) (defun mkfile-match-invalid-rule-attr (limit) (and (re-search-forward ":.*:" limit t) (let ((str&match (mkfile-rule-attr))) (when str&match (set-match-data (cdr str&match)) t)))) (defun mkfile-match-stem (limit) (and (re-search-forward "\\$\\(stem\\)\\>" limit t) (mkfile-in-meta-rule-body-p))) (defun mkfile-match-stemn (limit) (and (re-search-forward "\\$\\(stem[0-9]+\\)\\>" limit t) (mkfile-in-regexp-meta-rule-body-p))) (defun mkfile-parse-bquote (limit) (and (re-search-forward "`" limit t) (catch 'exit (let ((beg (match-beginning 0)) state) (if (not (char-equal (char-after) ?\{)) (setq state '(?\`)) (setq state '(?\})) (forward-char)) (while (< (point) limit) (let ((c (char-after))) (case c ((?\\) (forward-char)) ((?\' ?\" ?\} ?\`) (if (char-equal (car state) c) (setq state (cdr state)) (setq state (cons c state))))) (when (null state) (throw 'exit (cons t (list beg (1+ (point)))))) (forward-char))) (cons nil (list beg (point))))))) (defun mkfile-match-bquote (limit) (let ((stat&match (mkfile-parse-bquote limit))) (when (car stat&match) (set-match-data (cdr stat&match)) t))) (defun mkfile-match-unbalance-bquote (limit) (let ((stat&match (mkfile-parse-bquote limit))) (unless (car stat&match) (set-match-data (cdr stat&match)) t))) (defconst mkfile-syntax-table (let ((table (make-syntax-table))) (modify-syntax-entry ?\' "\"" table) (modify-syntax-entry ?\\ "\\" table) (modify-syntax-entry ?& "_" table) (modify-syntax-entry ?% "_" table) (modify-syntax-entry ?# "<" table) (modify-syntax-entry ?\n ">" table) table)) (defun mkfile-mode () (interactive) (kill-all-local-variables) (set-syntax-table mkfile-syntax-table) (setq font-lock-defaults '((mkfile-font-lock-keywords) nil)) (setq major-mode 'mkfile-mode) (setq mode-name "Plan9 mkfile") (run-mode-hooks 'mkfile-mode-hook) ) (provide 'mk-mode)