Files
verilog-align-ports/verilog-align.el

482 lines
18 KiB
EmacsLisp

;;; verilog-align.el --- Align SystemVerilog port declarations -*- lexical-binding: t; -*-
;;; Commentary:
;; Align SystemVerilog port declarations, instantiations, and signal declarations.
;;; Code:
(require 'cl-lib)
(require 'subr-x)
(defun verilog-align--line-port-p (pos)
(save-excursion
(goto-char pos)
(let ((line (buffer-substring-no-properties
(line-beginning-position)
(line-end-position))))
(string-match-p "^\\s-*\\(input\\|output\\|inout\\)\\b" line))))
(defun verilog-align--line-inst-port-p (pos)
(save-excursion
(goto-char pos)
(let ((line (buffer-substring-no-properties
(line-beginning-position)
(line-end-position))))
(string-match-p
"^\\s-*\\.\\(\\\\[^[:space:]]+\\|[A-Za-z_][A-Za-z0-9_$]*\\)\\b"
line))))
(defun verilog-align--line-decl-p (pos)
(save-excursion
(goto-char pos)
(let ((line (buffer-substring-no-properties
(line-beginning-position)
(line-end-position))))
(and (string-match-p
"^\\s-*\\(logic\\|wire\\|reg\\)\\b"
line)
(verilog-align--parse-decl-line line)))))
(defun verilog-align--split-comment (line)
(let ((pos (string-match "//" line)))
(if pos
(cons (substring line 0 pos) (substring line pos))
(cons line nil))))
(defun verilog-align--parse-line (line)
(let* ((split (verilog-align--split-comment line))
(code (string-trim-right (car split)))
(comment (cdr split)))
(when (string-match
"^\\(\\s-*\\)\\(input\\|output\\|inout\\)\\b\\s-*\\(.*\\)$"
code)
(let* ((indent (match-string 1 code))
(dir (match-string 2 code))
(rest (string-trim (match-string 3 code)))
(name nil)
(comma nil)
(type "")
(range "")
(before ""))
(if (string-match
"\\(\\\\[^[:space:]]+\\|[A-Za-z_][A-Za-z0-9_$]*\\)\\(\\s-*,\\)?\\s-*$"
rest)
(progn
(setq name (match-string 1 rest))
(setq comma (when (match-string 2 rest) ","))
(setq before
(string-trim-right (substring rest 0 (match-beginning 1)))))
(setq name rest)
(setq comma nil)
(setq before ""))
(setq before (string-trim-right before))
(if (and (not (string-empty-p before))
(string-match "\\(\\[[^][]+\\]\\)\\s-*$" before))
(progn
(setq range (match-string 1 before))
(setq type
(string-trim-right (substring before 0 (match-beginning 1)))))
(setq type (string-trim before))
(setq range ""))
(list :indent indent
:dir dir
:type type
:range range
:name name
:comma comma
:comment comment)))))
(defun verilog-align--bounds ()
(save-excursion
(and (verilog-align--line-port-p (line-beginning-position))
(let ((start (line-beginning-position))
(end nil))
(while (and (not (bobp))
(save-excursion
(forward-line -1)
(verilog-align--line-port-p
(line-beginning-position))))
(forward-line -1)
(setq start (line-beginning-position)))
(goto-char start)
(while (and (not (eobp))
(verilog-align--line-port-p
(line-beginning-position)))
(forward-line 1))
(setq end (line-beginning-position))
(cons start end)))))
(defun verilog-align--inst-bounds ()
(save-excursion
(and (verilog-align--line-inst-port-p (line-beginning-position))
(let ((start (line-beginning-position))
(end nil))
(while (and (not (bobp))
(save-excursion
(forward-line -1)
(verilog-align--line-inst-port-p
(line-beginning-position))))
(forward-line -1)
(setq start (line-beginning-position)))
(goto-char start)
(while (and (not (eobp))
(verilog-align--line-inst-port-p
(line-beginning-position)))
(forward-line 1))
(setq end (line-beginning-position))
(cons start end)))))
(defun verilog-align--decl-bounds ()
(save-excursion
(and (verilog-align--line-decl-p (line-beginning-position))
(let ((start (line-beginning-position))
(end nil))
(while (and (not (bobp))
(save-excursion
(forward-line -1)
(verilog-align--line-decl-p
(line-beginning-position))))
(forward-line -1)
(setq start (line-beginning-position)))
(goto-char start)
(while (and (not (eobp))
(verilog-align--line-decl-p
(line-beginning-position)))
(forward-line 1))
(setq end (line-beginning-position))
(cons start end)))))
(defun verilog-align--collect (start end)
(let (entries)
(save-excursion
(goto-char start)
(while (< (point) end)
(let* ((line (buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))
(entry (verilog-align--parse-line line)))
(when entry
(push entry entries)))
(forward-line 1)))
(nreverse entries)))
(defun verilog-align--find-paren-end (text start)
(let ((depth 0)
(idx start)
(len (length text))
(end nil))
(while (and (< idx len) (not end))
(let ((ch (aref text idx)))
(cond
((eq ch ?\() (setq depth (1+ depth)))
((eq ch ?\))
(setq depth (1- depth))
(when (eq depth 0)
(setq end (1+ idx))))))
(setq idx (1+ idx)))
end))
(defun verilog-align--parse-inst-line (line)
(let* ((split (verilog-align--split-comment line))
(code (string-trim-right (car split)))
(comment (cdr split)))
(when (string-match
"^\\(\\s-*\\)\\.\\(\\\\[^[:space:]]+\\|[A-Za-z_][A-Za-z0-9_$]*\\)\\(.*\\)$"
code)
(let* ((indent (match-string 1 code))
(name (match-string 2 code))
(rest (match-string 3 code))
(rest (if rest (string-trim-right rest) ""))
(pos (string-match "\\S-" rest))
(has-conn nil)
(conn "")
(comma nil))
(if (and pos (< pos (length rest)) (eq (aref rest pos) ?\())
(let* ((end (verilog-align--find-paren-end rest pos)))
(if end
(progn
(setq has-conn t)
(setq conn (string-trim (substring rest (1+ pos) (1- end))))
(let ((after (string-trim (substring rest end))))
(when (and (> (length after) 0)
(eq (aref after 0) ?,))
(setq comma ","))))
(let ((after (string-trim rest)))
(when (and (> (length after) 0)
(string-match-p "," after))
(setq comma ",")))))
(let ((after (string-trim rest)))
(when (and (> (length after) 0)
(string-match-p "," after))
(setq comma ","))))
(list :indent indent
:name name
:has-conn has-conn
:conn conn
:comma comma
:comment comment)))))
(defun verilog-align--parse-decl-line (line)
(let* ((split (verilog-align--split-comment line))
(code (string-trim-right (car split)))
(comment (cdr split)))
(when (string-match
"^\\(\\s-*\\)\\(logic\\|wire\\|reg\\)\\b\\s-*\\(.*\\)$"
code)
(let* ((indent (match-string 1 code))
(kind (match-string 2 code))
(rest (string-trim (match-string 3 code)))
(name nil)
(tail nil)
(type-rest "")
(range "")
(before ""))
(when (string-match
"\\(\\\\[^[:space:]]+\\|[A-Za-z_][A-Za-z0-9_$]*\\)\\s-*\\(?:\\(=[^;]*\\)\\)?;\\s-*$"
rest)
(setq name (match-string 1 rest))
(setq tail (when (match-string 2 rest)
(string-trim (match-string 2 rest))))
(setq before
(string-trim-right (substring rest 0 (match-beginning 1))))
(setq before (string-trim before))
(unless (string-match-p "," before)
(if (and (not (string-empty-p before))
(string-match "\\(\\[[^][]+\\]\\)\\s-*$" before))
(progn
(setq range (match-string 1 before))
(setq type-rest
(string-trim-right
(substring before 0 (match-beginning 1)))))
(setq type-rest before)
(setq range ""))
(let ((type (if (string-empty-p type-rest)
kind
(concat kind " " type-rest))))
(list :indent indent
:type type
:range range
:name name
:tail tail
:comment comment))))))))
(defun verilog-align--inst-collect (start end)
(let (entries)
(save-excursion
(goto-char start)
(while (< (point) end)
(let* ((line (buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))
(entry (verilog-align--parse-inst-line line)))
(when entry
(push entry entries)))
(forward-line 1)))
(nreverse entries)))
(defun verilog-align--decl-collect (start end)
(let (entries)
(save-excursion
(goto-char start)
(while (< (point) end)
(let* ((line (buffer-substring-no-properties
(line-beginning-position)
(line-end-position)))
(entry (verilog-align--parse-decl-line line)))
(when entry
(push entry entries)))
(forward-line 1)))
(nreverse entries)))
(defun verilog-align--max-lengths (entries)
(let ((max-dir 0)
(max-type 0)
(max-range 0)
(max-name 0)
(has-comment nil))
(dolist (entry entries)
(setq max-dir (max max-dir (length (plist-get entry :dir))))
(setq max-type (max max-type (length (plist-get entry :type))))
(setq max-range (max max-range (length (plist-get entry :range))))
(let ((name (concat (plist-get entry :name)
(or (plist-get entry :comma) ""))))
(setq max-name (max max-name (length name))))
(when (plist-get entry :comment)
(setq has-comment t)))
(list max-dir max-type max-range max-name has-comment)))
(defun verilog-align--pad (max-len len)
(make-string (+ 1 (- max-len len)) ?\s))
(defun verilog-align--format-lines (entries)
(let* ((base-indent (plist-get (car entries) :indent))
(maxes (verilog-align--max-lengths entries))
(max-dir (nth 0 maxes))
(max-type (nth 1 maxes))
(max-range (nth 2 maxes))
(max-name (nth 3 maxes))
(has-comment (nth 4 maxes))
(has-type (> max-type 0))
(has-range (> max-range 0)))
(mapcar
(lambda (entry)
(let* ((dir (plist-get entry :dir))
(type (plist-get entry :type))
(range (plist-get entry :range))
(name (concat (plist-get entry :name)
(or (plist-get entry :comma) "")))
(comment (plist-get entry :comment))
(dir-pad (verilog-align--pad max-dir (length dir)))
(type-pad (when has-type
(verilog-align--pad max-type (length type))))
(range-pad (when has-range
(verilog-align--pad max-range (length range))))
(name-pad (when (and has-comment comment)
(verilog-align--pad max-name (length name)))))
(concat base-indent
dir
dir-pad
(when has-type type)
(when has-type type-pad)
(when has-range range)
(when has-range range-pad)
name
(when (and has-comment comment) name-pad)
(or comment ""))))
entries)))
(defun verilog-align--inst-format-lines (entries)
(let* ((base-indent (plist-get (car entries) :indent))
(max-name 0)
(has-comment nil)
left-parts
(max-left 0))
(dolist (entry entries)
(let ((name-len (+ 1 (length (plist-get entry :name)))))
(setq max-name (max max-name name-len)))
(when (plist-get entry :comment)
(setq has-comment t)))
(dolist (entry entries)
(let* ((name (plist-get entry :name))
(name-field (concat "." name))
(has-conn (plist-get entry :has-conn))
(conn (plist-get entry :conn))
(comma (plist-get entry :comma))
(pad (when has-conn
(verilog-align--pad max-name (length name-field))))
(left (concat base-indent
name-field
(when has-conn pad)
(when has-conn "(")
(when has-conn conn)
(when has-conn ")")
(when comma ","))))
(push left left-parts)
(setq max-left (max max-left (length left)))))
(setq left-parts (nreverse left-parts))
(cl-mapcar
(lambda (entry left)
(let ((comment (plist-get entry :comment)))
(if (and has-comment comment)
(concat left
(verilog-align--pad max-left (length left))
comment)
left)))
entries left-parts)))
(defun verilog-align--decl-format-lines (entries)
(let* ((base-indent (plist-get (car entries) :indent))
(max-type 0)
(max-range 0)
(has-comment nil)
left-parts
(max-left 0))
(dolist (entry entries)
(setq max-type (max max-type (length (plist-get entry :type))))
(setq max-range (max max-range (length (plist-get entry :range))))
(when (plist-get entry :comment)
(setq has-comment t)))
(let ((has-range (> max-range 0)))
(dolist (entry entries)
(let* ((type (plist-get entry :type))
(range (plist-get entry :range))
(name (plist-get entry :name))
(tail (plist-get entry :tail))
(type-pad (verilog-align--pad max-type (length type)))
(range-pad (when has-range
(verilog-align--pad max-range (length range))))
(left (concat base-indent
type
type-pad
(when has-range range)
(when has-range range-pad)
name
(when tail (concat " " tail))
";")))
(push left left-parts)
(setq max-left (max max-left (length left)))))
(setq left-parts (nreverse left-parts))
(cl-mapcar
(lambda (entry left)
(let ((comment (plist-get entry :comment)))
(if (and has-comment comment)
(concat left
(verilog-align--pad max-left (length left))
comment)
left)))
entries left-parts))))
(defun verilog-align--apply (start lines)
(save-excursion
(goto-char start)
(dolist (line lines)
(delete-region (line-beginning-position) (line-end-position))
(insert line)
(forward-line 1))))
;;;###autoload
(defun verilog-align-ports ()
"Align SystemVerilog port declarations around point."
(interactive)
(let ((bounds (verilog-align--bounds)))
(and bounds
(let* ((start (car bounds))
(end (cdr bounds))
(entries (verilog-align--collect start end)))
(and entries
(let ((lines (verilog-align--format-lines entries)))
(verilog-align--apply start lines)
t))))))
;;;###autoload
(defun verilog-align-instantiation ()
"Align SystemVerilog named port connections around point."
(interactive)
(let ((bounds (verilog-align--inst-bounds)))
(and bounds
(let* ((start (car bounds))
(end (cdr bounds))
(entries (verilog-align--inst-collect start end)))
(and entries
(let ((lines (verilog-align--inst-format-lines entries)))
(verilog-align--apply start lines)
t))))))
;;;###autoload
(defun verilog-align-declarations ()
"Align SystemVerilog signal declarations around point."
(interactive)
(let ((bounds (verilog-align--decl-bounds)))
(and bounds
(let* ((start (car bounds))
(end (cdr bounds))
(entries (verilog-align--decl-collect start end)))
(and entries
(let ((lines (verilog-align--decl-format-lines entries)))
(verilog-align--apply start lines)
t))))))
(provide 'verilog-align)
;;; verilog-align.el ends here