如何在elisp中捕获分割字符串的结果?

3

我是一名从事elisp开发的IT技术人员,目前遇到一个问题:我有一个字符串,它表示一个项目列表。这个字符串看起来像这样:

"apple orange 'tasty things' 'my lunch' zucchini 'my dinner'"

我正在尝试将其分割为

("apple" "orange" "tasty things" "my lunch" "zucchini" "my dinner")
这是一个常见的问题。我解决它的障碍与正则表达式无关,而是与elisp的具体细节有关。
我想做的是像这样运行一个循环:
``` (while (< (length my-string) 0) do-work) ```
其中do-work是:
- 将正则表达式\('[^']*?'\|[[:alnum:]]+)\([[:space:]]*\(.+\)应用于my-string - 将\1添加到我的结果列表中 - 将my-string重新绑定为\2 然而,我无法弄清楚如何让split-stringreplace-regexp-in-string这样做。
我该如何将此字符串拆分为可用的值?
(或者:“我还没有找到哪个内置的emacs函数可以实现这个?”)
4个回答

5

有一个类似的方式,但是没有正则表达式:

(defun parse-quotes (string)
  (let ((i 0) result current quotep escapedp word)
    (while (< i (length string))
      (setq current (aref string i))
      (cond
       ((and (char-equal current ?\ )
             (not quotep))
        (when word (push word result))
        (setq word nil escapedp nil))
       ((and (char-equal current ?\')
             (not escapedp) 
             (not quotep))
        (setq quotep t escapedp nil))
       ((and (char-equal current ?\')
             (not escapedp))
        (push word result)
        (setq quotep nil word nil escapedp nil))
       ((char-equal current ?\\)
        (when escapedp (push current word))
        (setq escapedp (not escapedp)))
       (t (setq escapedp nil)
        (push current word)))
      (incf i))
    (when quotep
      (error (format "Unbalanced quotes at %d"
                     (- (length string) (length word)))))
    (when word (push result word))
    (mapcar (lambda (x) (coerce (reverse x) 'string))
            (reverse result))))

(parse-quotes "apple orange 'tasty things' 'my lunch' zucchini 'my dinner'")
("apple" "orange" "tasty things" "my lunch" "zucchini" "my dinner")

(parse-quotes "apple orange 'tasty thing\\'s' 'my lunch' zucchini 'my dinner'")
("apple" "orange" "tasty thing's" "my lunch" "zucchini" "my dinner")

(parse-quotes "apple orange 'tasty things' 'my lunch zucchini 'my dinner'")
;; Debugger entered--Lisp error: (error "Unbalanced quotes at 52")

奖励:它还允许使用“\”转义引号,并且会在引号不平衡时报告错误(已到达字符串的末尾,但未找到匹配打开引号的引号)。


哦,嘿,现在这个东西被正确地“解析”了。这个答案很有教育意义。 :) - Brighid McDonnell
提高了我的知识,并且是唯一完全符合答案规范的答案 - 所以接受了。 谢谢。 :D - Brighid McDonnell
三年后再来点个赞,我在整个互联网上都找不到其他的解决方案:D 有没有什么包可以提供这个功能?真的应该有。编辑:显然split-string-and-unquote基本上可以做到这一点,但我无法使其同时处理单引号...嗯。 - Commander Coriander Salamander

3

以下是使用临时缓冲区实现算法的简单方法。我不知道是否有办法使用replace-regexp-in-stringsplit-string来完成。

(defun my-split (string)
  (with-temp-buffer
    (insert string " ")     ;; insert the string in a temporary buffer
    (goto-char (point-min)) ;; go back to the beginning of the buffer
    (let ((result nil))
      ;; search for the regexp (and just return nil if nothing is found)
      (while (re-search-forward "\\('[^']*?'\\|[[:alnum:]]+\\)\\([[:space:]]*\\(.+\\)\\)" nil t)
        ;; (match-string 1) is "\1"
        ;; append it after the current list
        (setq result (append result (list (match-string 1))))
        ;; go back to the beginning of the second part
        (goto-char (match-beginning 2)))
      result)))

例子:

(my-split "apple orange 'tasty things' 'my lunch' zucchini 'my dinner'")
  ==> ("apple" "orange" "'tasty things'" "'my lunch'" "zucchini" "'my dinner'")

3
你可能会对split-string-and-unquote感兴趣。

0
如果您经常操作字符串,建议通过软件包管理器安装s.el库,它引入了大量的字符串实用函数,并提供了一致的API。对于此任务,您需要使用函数s-match,其可选的第三个参数接受起始位置。然后,您需要一个正确的正则表达式,请尝试:
(concat "\\b[a-z]+\\b" "\\|" "'[a-z ]+'")

\| 表示匹配组成单词的字母序列(\b 表示单词边界),或者引号内的字母序列和空格。然后使用 loop

;; let s = given string, r = regex
(loop for start = 0 then (+ start (length match))
      for match = (car (s-match r s start))
      while match 
      collect match)

出于教育目的,我还使用递归函数实现了相同的功能:

;; labels is Common Lisp's local function definition macro
(labels
    ((i
      (start result)
      ;; s-match searches from start
      (let ((match (car (s-match r s start))))
        (if match
            ;; recursive call
            (i (+ start (length match))
               (cons match result))
          ;; push/nreverse idiom
          (nreverse result)))))
  ;; recursive helper function
  (i 0 '()))

由于Emacs缺乏尾调用优化,在大型列表上执行它可能会导致堆栈溢出。因此,您可以使用do宏重写它:

(do* ((start 0)
      (match (car (s-match r s start)) (car (s-match r s start)))
      (result '()))
    ((not match) (reverse result))
  (push match result)
  (incf start (length match)))

虽然这很有帮助,但也要注意 s.el 是一个不随 emacs 附带的包。更好的答案,尤其是被采纳的答案,可以在代码复杂度相等或更低的情况下完成任务,而无需涉及第三方包。您提出的答案在多个方面上更为复杂。 - Brighid McDonnell
s-match + loop 宏并不复杂。请查看更新的答案,我尝试澄清了。 - Mirzhan Irkegulov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接