This took me quite a bit more time than I originally estimated, and the code is a bit too long to post it all here, so I posted it to Patebin: http://pastebin.com/Cw82x11i
It is not entirely complete though and it can use some more work, so if anyone will have suggestions or contributions, I might re-arrange this as a Git repository somewhere / repost this to Emacs wiki.
Few important points:
- No attempt has been made to cater for matrices with delimiters other than spaces.
- I didn't try to parse complex numbers either.
- The treatment of non-numerical entries is different than the one in your example (to be honest, I wouldn't really know how to parse it in exactly the way you want. My guess is that the semicolon is the Matlab / Octave row delimiter, but if I try to make it more generic, it's really hard to wrap my head around it. Also, my guess is that the ellipsis is the Matlab / Octave way of telling the interpreter that the statement continues on the next line, but, again, trying to make this more generic would be really hard. Instead, I'm just treating whatever non-numeric value I encounter as if it was a whole number.
- Finally, I had to give up the
align-regexp
because it was too complicated to try to make it align exactly using the rule you seem to have in mind.
Here's what it would look like:
;; before
A = [-15 9 33.34;...
1.0 0.99 1;...
13000 2 11 ];
;; after
A = [ -15 9 33.34 ;...
1.0 0.99 1 ;...
13000 2 11 ];
PS. You can adjust the space between the columns by changing the value of the spacer
variable.
OK, I've also made a little refinement to the code where it can now ask for the string to fill in between the columns.
(defun my/string-to-number (line re)
(let ((matched (string-match re line)))
(if matched
(list (match-string 0 line)
(substring line (length (match-string 0 line))))
(list nil line))))
(defun my/string-to-double (line)
(my/string-to-number
line
"\\s-*[+-]?[0-9]+\\(?:\\.[0-9]+\\(?:[eE][+-]?[0-9]+\\)?\\)?"))
(defun my/string-to-int (line)
(my/string-to-number line "\\s-*[+-]?[0-9]+"))
(defun my/vector-transpose (vec)
(cl-coerce
(cl-loop for i below (length (aref vec 0))
collect (cl-coerce
(cl-loop for j below (length vec)
collect (aref (aref vec j) i))
'vector))
'vector))
(defun my/align-metric (col num-parser)
(cl-loop with max-left = 0
with max-right = 0
with decimal = 0
for cell across col
for nump = (car (funcall num-parser cell))
for has-decimals = (cl-position ?\. cell) do
(if nump
(if has-decimals
(progn
(setf decimal 1)
(when (> has-decimals max-left)
(setf max-left has-decimals))
(when (> (1- (- (length cell) has-decimals))
max-right)
(setf max-right (1- (- (length cell) has-decimals)))))
(when (> (length cell) max-left)
(setf max-left (length cell))))
(when (> (length cell) max-left)
(setf max-left (length cell))))
finally (cl-return (list max-left decimal max-right))))
(defun my/print-matrix (rows metrics num-parser prefix spacer)
(cl-loop with first-line = t
for i upfrom 0
for row across rows do
(unless first-line (insert prefix))
(setf first-line nil)
(cl-loop with first-row = t
for cell across row
for metric in metrics
for has-decimals =
(and (cl-position ?\. cell)
(car (funcall num-parser cell)))
do
(unless first-row (insert spacer))
(setf first-row nil)
(cl-destructuring-bind (left decimal right) metric
(if has-decimals
(cl-destructuring-bind (whole fraction)
(split-string cell "\\.")
(insert (make-string (- left (length whole)) ?\ )
whole
"."
fraction
(make-string (- right (length fraction)) ?\ )))
(insert (make-string (- left (length cell)) ?\ )
cell
(make-string (1+ right) ?\ )))))
(unless (= i (1- (length rows)))
(insert "\n"))))
(defun my/read-rows (beg end)
(cl-coerce
(cl-loop for line in (split-string
(buffer-substring-no-properties beg end) "\n")
collect
(cl-coerce
(nreverse
(cl-loop with result = nil
with remaining = line do
(cl-destructuring-bind (num remainder)
(funcall num-parser remaining)
(if num
(progn
(push (org-trim num) result)
(setf remaining remainder))
(push (org-trim remaining) result)
(cl-return result)))))
'vector))
'vector))
(defvar my/parsers '((:double . my/string-to-double)
(:int . my/string-to-int)))
(defun my/align-matrix (parser &optional spacer)
(interactive
(let ((sym (intern
(completing-read
"Parse numbers using: "
(mapcar 'car my/parsers)
nil nil nil t ":double")))
(spacer (if current-prefix-arg
(read-string "Interleave with: ")
" ")))
(list sym spacer)))
(unless spacer (setf spacer " "))
(let ((num-parser
(or (cdr (assoc parser my/parsers))
(and (functionp parser) parser)
'my/string-to-double))
beg end)
(if (region-active-p)
(setf beg (region-beginning)
end (region-end))
(setf end (1- (search-forward-regexp "\\s)" nil t))
beg (1+ (progn (backward-sexp) (point)))))
(goto-char beg)
(let* ((prefix (make-string (current-column) ?\ ))
(rows (my/read-rows beg end))
(cols (my/vector-transpose rows))
(metrics
(cl-loop for col across cols
collect (my/align-metric col num-parser))))
(delete-region beg end)
(my/print-matrix rows metrics num-parser prefix spacer))))