diff --git a/README.md b/README.md index c24e3a1..a7626ef 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ Align SystemVerilog ANSI-style port declarations in a contiguous block around po - Aligns direction, type, range, name, and trailing `//` comments. - Works on the contiguous block of port declarations above and below point. - Stops at the first non-port line (including blank lines). +- Aligns named port connections in module instantiations via `verilog-align-ports-instantiation`. ## Installation @@ -20,6 +21,11 @@ Align SystemVerilog ANSI-style port declarations in a contiguous block around po 1. Place point on a line that declares a port (input/output/inout). 2. Run `M-x verilog-align-ports`. +For module instantiations: + +1. Place point on a named port connection line (e.g., `.clk_i (clk)` or `.full_o,`). +2. Run `M-x verilog-align-ports-instantiation`. + Example input: ```systemverilog diff --git a/verilog-align-ports-test.el b/verilog-align-ports-test.el index 4c03dc0..c204dfc 100644 --- a/verilog-align-ports-test.el +++ b/verilog-align-ports-test.el @@ -66,6 +66,80 @@ (verilog-align-ports) (should (string= (buffer-string) expected))))) +(ert-deftest verilog-align-ports-instantiation-aligns-block () + (let* ((input (concat + (mapconcat + #'identity + '("fifo_sync #(" + " .FifoWriteDepth(FifoWriteDepth)," + " .DataWidth(DataWidth)," + " .DataCountWidth(WrDataCountW)," + " .RdDataCountWidth(RdDataCountW)" + ") dut (" + " .rst_i (rst), //rst" + " .clk_i (clk)," + " .wr_en_i (wr_en_i)," + " .d_i (d_i), // di" + " .full_o, // full" + " .data_count_o (wr_data_count_o)," + " .rd_en_i (rd_en_i)," + " .d_o () , // do" + " .empty_o , // empty" + " .valid_o (valid_o)" + ");") + "\n") + "\n")) + (expected (concat + (mapconcat + #'identity + (list + "fifo_sync #(" + " .FifoWriteDepth(FifoWriteDepth)," + " .DataWidth(DataWidth)," + " .DataCountWidth(WrDataCountW)," + " .RdDataCountWidth(RdDataCountW)" + ") dut (" + (concat " .rst_i" (make-string 8 ?\s) "(rst)," + (make-string 13 ?\s) "//rst") + (concat " .clk_i" (make-string 8 ?\s) "(clk),") + (concat " .wr_en_i" (make-string 6 ?\s) "(wr_en_i),") + (concat " .d_i" (make-string 10 ?\s) "(d_i)," + (make-string 13 ?\s) "// di") + (concat " .full_o," (make-string 25 ?\s) "// full") + (concat " .data_count_o" (make-string 1 ?\s) + "(wr_data_count_o),") + (concat " .rd_en_i" (make-string 6 ?\s) "(rd_en_i),") + (concat " .d_o" (make-string 10 ?\s) "()," + (make-string 16 ?\s) "// do") + (concat " .empty_o," (make-string 24 ?\s) "// empty") + (concat " .valid_o" (make-string 6 ?\s) "(valid_o)") + ");") + "\n") + "\n"))) + (with-temp-buffer + (insert input) + (goto-char (point-min)) + (search-forward ".clk_i") + (beginning-of-line) + (should (verilog-align-ports-instantiation)) + (should (string= (buffer-string) expected))))) + +(ert-deftest verilog-align-ports-instantiation-returns-nil-outside () + (with-temp-buffer + (insert (concat + (mapconcat + #'identity + '("fifo_sync #() dut (" + " .rst_i (rst)," + " .clk_i (clk)" + ");") + "\n") + "\n")) + (goto-char (point-min)) + (search-forward "dut (") + (beginning-of-line) + (should (not (verilog-align-ports-instantiation))))) + (provide 'verilog-align-ports-test) ;;; verilog-align-ports-test.el ends here diff --git a/verilog-align-ports.el b/verilog-align-ports.el index 0e248e2..a3186d5 100644 --- a/verilog-align-ports.el +++ b/verilog-align-ports.el @@ -16,6 +16,16 @@ (line-end-position)))) (string-match-p "^\\s-*\\(input\\|output\\|inout\\)\\b" line)))) +(defun verilog-align-ports--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-ports--split-comment (line) (let ((pos (string-match "//" line))) (if pos @@ -82,6 +92,26 @@ (verilog-align-ports--line-port-p (line-beginning-position))) (forward-line 1)) + (setq end (line-beginning-position)) + (cons start end))))) + +(defun verilog-align-ports--inst-bounds () + (save-excursion + (and (verilog-align-ports--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-ports--line-inst-port-p + (line-beginning-position)))) + (forward-line -1) + (setq start (line-beginning-position))) + (goto-char start) + (while (and (not (eobp)) + (verilog-align-ports--line-inst-port-p + (line-beginning-position))) + (forward-line 1)) (setq end (line-beginning-position)) (cons start end))))) @@ -99,6 +129,76 @@ (forward-line 1))) (nreverse entries))) +(defun verilog-align-ports--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-ports--parse-inst-line (line) + (let* ((split (verilog-align-ports--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-ports--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-ports--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-ports--parse-inst-line line))) + (when entry + (push entry entries))) + (forward-line 1))) + (nreverse entries))) + (defun verilog-align-ports--max-lengths (entries) (let ((max-dir 0) (max-type 0) @@ -153,9 +253,48 @@ (when has-range range-pad) name (when (and has-comment comment) name-pad) - (or comment "")))) + (or comment "")))) entries))) +(defun verilog-align-ports--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-ports--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-ports--pad max-left (length left)) + comment) + left))) + entries left-parts))) + (defun verilog-align-ports--apply (start lines) (save-excursion (goto-char start) @@ -169,12 +308,26 @@ "Align SystemVerilog port declarations around point." (interactive) (let ((bounds (verilog-align-ports--bounds))) + (and bounds + (let* ((start (car bounds)) + (end (cdr bounds)) + (entries (verilog-align-ports--collect start end))) + (and entries + (let ((lines (verilog-align-ports--format-lines entries))) + (verilog-align-ports--apply start lines) + t)))))) + +;;;###autoload +(defun verilog-align-ports-instantiation () + "Align SystemVerilog named port connections around point." + (interactive) + (let ((bounds (verilog-align-ports--inst-bounds))) (and bounds (let* ((start (car bounds)) (end (cdr bounds)) - (entries (verilog-align-ports--collect start end))) + (entries (verilog-align-ports--inst-collect start end))) (and entries - (let ((lines (verilog-align-ports--format-lines entries))) + (let ((lines (verilog-align-ports--inst-format-lines entries))) (verilog-align-ports--apply start lines) t))))))