`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)
"#+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