Signal an error when a circular macro expansion happens
authorNicolas Goaziou <n.goaziou@gmail.com>
Tue, 30 Oct 2012 08:24:55 +0000 (09:24 +0100)
committerNicolas Goaziou <n.goaziou@gmail.com>
Tue, 30 Oct 2012 08:24:55 +0000 (09:24 +0100)
* lisp/org.el (org-macro-replace-all): Signal an error when a circular
  macro expansion happens.
(org-macro-initialize-templates): Fix docstring.
* testing/lisp/test-org.el: Add test.

lisp/org.el
testing/lisp/test-org.el

index 63c4323..9ba39a7 100644 (file)
@@ -20927,28 +20927,40 @@ TEMPLATES is an alist of templates used for expansion.  See
 `org-macro-templates' for a buffer-local default value."
   (save-excursion
     (goto-char (point-min))
-    (while (re-search-forward "{{{[-A-Za-z0-9_]" nil t)
-      (let ((object (org-element-context)))
-        (when (eq (org-element-type object) 'macro)
-          (let ((value (org-macro-expand object templates)))
-            (when value
-              (delete-region
-               (org-element-property :begin object)
-               ;; Preserve white spaces after the macro.
-               (progn (goto-char (org-element-property :end object))
-                      (skip-chars-backward " \t")
-                      (point)))
-              ;; Leave point before replacement in case of recursive
-              ;; expansions.
-              (save-excursion (insert value)))))))))
+    (let (record)
+      (while (re-search-forward "{{{[-A-Za-z0-9_]" nil t)
+       (let ((object (org-element-context)))
+         (when (eq (org-element-type object) 'macro)
+           (let* ((value (org-macro-expand object templates))
+                  (begin (org-element-property :begin object))
+                  (signature (list begin
+                                   object
+                                   (org-element-property :args object))))
+             ;; Avoid circular dependencies by checking if the same
+             ;; macro with the same arguments is expanded at the same
+             ;; position twice.
+             (if (member signature record)
+                 (error "Circular macro expansion: %s"
+                        (org-element-property :key object))
+               (when value
+                 (push signature record)
+                 (delete-region
+                  begin
+                  ;; Preserve white spaces after the macro.
+                  (progn (goto-char (org-element-property :end object))
+                         (skip-chars-backward " \t")
+                         (point)))
+                 ;; Leave point before replacement in case of recursive
+                 ;; expansions.
+                 (save-excursion (insert value)))))))))))
 
 (defun org-macro-initialize-templates ()
   "Collect macro templates defined in current buffer.
 Templates are stored in buffer-local variable
 `org-macro-templates'.  In addition to buffer-defined macros, the
-function installs the following ones: \"property\", \"date\",
-\"time\". and, if appropriate, \"input-file\" and
-\"modification-time\"."
+function installs the following ones: \"property\",
+\"time\". and, if the buffer is associated to a file,
+\"input-file\" and \"modification-time\"."
   (let ((case-fold-search t)
        (set-template
         (lambda (cell)
index 58a8b30..b0a073a 100644 (file)
@@ -410,7 +410,13 @@ http://article.gmane.org/gmane.emacs.orgmode/21459/"
        "#+MACRO: in inner\n#+MACRO: out {{{in}}} outer\n{{{out}}}"
       (progn (org-macro-initialize-templates)
             (org-macro-replace-all org-macro-templates)
-            (buffer-string))))))
+            (buffer-string)))))
+  ;; Error out when macro expansion is circular.
+  (should-error
+   (org-test-with-temp-text
+       "#+MACRO: mac1 {{{mac2}}}\n#+MACRO: mac2 {{{mac1}}}\n{{{mac1}}}"
+     (org-macro-initialize-templates)
+     (org-macro-replace-all org-macro-templates))))
 
 
 \f