0

If I put the code below into a new buffer and eval the code I get an wrong type argument error. It seems string-trim is called with argument nil. how come? I feel there are some fundamentals about elisp that I do not know.

(progn (goto-char (point-min))
       (while (re-search-forward
               (rx (group (+? nonl)) "///" (group (+ nonl)))
               nil t)
         (message "%s" (cons (string-trim (match-string 0))
                             (cons (string-trim (match-string 1))
                                   (string-trim (match-string 2)))))))
2
  • @shynur: I felt that edit was weird, so I've reverted it. If it was just formatting I'd have left it, but the body of the loop had been changed to something which wasn't practical code.
    – phils
    Commented Apr 27, 2023 at 14:50
  • @phils: It wasn't practical code, but it did save time spent analyzing the code. Well, there's no harm in rejecting it. The tag of the question is what really needs to be changed, IMO. (But I don't which tag is suitable.)
    – shynur
    Commented Apr 27, 2023 at 15:23

2 Answers 2

2

match-string does return nil in some situations (refer to the docstring), but any match for your particular regexp has to include subgroups 1 and 2, which means that something is clobbering the match data along the way. In this instance it's string-trim (which involves regexp matching and consequently clobbers the match data), so you will need to capture the matched values you need before trimming any of them.

The "fundamental" thing here is that every single search that Emacs performs modifies the match data, and whether or not the match data is preserved/restored between searches generally depends on whether or not save-match-data is being used around the code which results in a search happening. So if you're not in control of all the code being called in between your search and your request for the match data then you need to either be certain that the code in question preserves the match data (which may be documented), or otherwise take steps to preserve or capture it yourself before calling that code.

However I think this looks like a bug (or at least an inconsistency) in Emacs: string-trim-right uses string-match-p but string-trim-left uses string-match and so the match data is (only) clobbered there (and string-trim calls them both, hence your issue).

I think it's pretty unexpected for the left and right variants to behave differently to one another in this respect, so I suggest that you M-x report-emacs-bug

4
  • If I am writing a multi-threaded program, is using a lock the only way?
    – shynur
    Commented Apr 28, 2023 at 5:15
  • Is that a question about this answer? If so, I don't understand it. If not, post a question.
    – phils
    Commented Apr 28, 2023 at 5:19
  • Perhaps it's another question, so I may ask later. |||| “I don't understand it.” --- Your proposed solution is to use the function save-match-data, but what if there are other threads running by OP’s Emacs? Scheduling can happen between re-search-* and save-match-data, leading to unexpected situations. I think OP should also be reminded to use a lock variable, but none of the library functions that come with Emacs seem to do so. I wonder it may not be necessary.
    – shynur
    Commented Apr 28, 2023 at 5:38
  • 1
    (elisp)Threads says "Each thread also has its own current buffer and its own match data."
    – phils
    Commented Apr 28, 2023 at 7:25
0

As already pointed out in the answer by phils the reason for the experienced issue is a naive expectation that regex match data won't be changed/corrupted by code run between execution of the search and extraction of the match data.

Check out the documentation C-h o save-match-data to read:

NOTE: The convention in Elisp is that any function, except for a few exceptions like car/assoc/+/goto-char, can clobber the match data, so ‘save-match-data’ should normally be used to save your match data

You can avoid the experienced problem using the save-match-data wrapper as follows

(progn
  (goto-char (point-min))
  (while (re-search-forward 
         (rx (group (+? nonl)) "///" (group (+ nonl))) nil t)
    (message "%s" 
      (cons (save-match-data (string-trim (match-string 0)))
            (cons (save-match-data (string-trim (match-string 1)))
                  (string-trim (match-string 2)))))))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.