Твик-текст: вложенность Lisp превышает `max-lisp-eval-глубина

Программа должна переформатировать строку, как показано ниже.

Пример: (game-print '(ЭТО ПРЕДЛОЖЕНИЕ。 ЧТО ОБ ЭТОМ? ВЕРОЯТНО.))

Это предложение. Как насчет этого? Наверное.

Но что-то не так (вложенность Lisp превышает `max-lisp-eval-глубина), и я не знаю почему. Этот фрагмент кода на самом деле взят из книги "Земля лиспа" на стр. 97. Оригинальный код написан обычным шрифтом. Я хочу переписать это в elisp. Последние два аргумента в твик-тексте означают капитан и буквальный.

(defun tweak-text (lst caps lit)
  (when lst
    (let ((item (car lst))
          (rest (cdr lst)))
      (cond ((eql item ?\ ) (cons item (tweak-text rest caps lit)))
            ((member item '(?\! ?\? ?\.)) (cons item (tweak-text rest t lit)))
            ((eql item ?\") (tweak-text rest caps (not lit)))
            (lit (cons item (tweak-text rest nil lit)))
            (caps (cons (upcase item) (tweak-text rest nil lit)))
            (t (cons (downcase item) (tweak-text rest nil nil)))))))

(defun game-print (lst)
  (print (coerce (tweak-text (coerce (prin1-to-string lst) 'list) t nil) 'string)))

(game-print '(not only does this sentence have a "comma," it also mentions the "iPad."))

Оригинальный код, написанный на общем языке.

(defun tweak-text (lst caps lit)
  (when lst
    (let ((item (car lst))
          (rest (cdr lst)))
      (cond ((eql item #\space) (cons item (tweak-text rest caps lit)))
            ((member item '(#\! #\? #\.)) (cons item (tweak-text rest t lit)))
            ((eql item #\") (tweak-text rest caps (not lit)))
            (lit (cons item (tweak-text rest nil lit)))
            (caps (cons (char-upcase item) (tweak-text rest nil lit)))
            (t (cons (char-downcase item) (tweak-text rest nil nil)))))))

(defun game-print (lst)
    (princ (coerce (tweak-text (coerce (string-trim "() " (prin1-to-string lst)) 'list) t nil) 'string))
    (fresh-line))

5 ответов

Решение

Вы действительно достигли предела "max-lisp-eval-глубина" при повторном обращении к тексту настройки. Я не вижу ничего плохого в том, как работает код (я не проверял, выполняет ли он то, что вы намереваетесь сделать).

Вы можете настроить / увеличить предел 'max-lisp-eval-глубина'. Документация для этой переменной гласит, что вы можете повысить ее, если уверены, что не собираетесь использовать свободное место в стеке. Ограничение консервативно установлено на моей машине 541. При увеличении до 600 приведенное выше определение функции будет работать с вводом, который вы дали в качестве примера.

В обоих случаях у вас есть нетерминальные рекурсии, поэтому вы используете пространство стека O(length(lst)). Очевидно, что системы могут ограничить пространство стека, которое вы можете использовать, и вы действительно достигли этого предела в emacs. (Теперь в emacs вы можете увеличить предел, изменив max-lisp-eval-глубина, но это не решит фундаментальную проблему).

Решение состоит в том, чтобы использовать итерацию вместо рекурсии.

Но сначала напишите в emacs:

(defun character (x)
  "common-lisp: return the character designated by X."
  (etypecase x
    (integer x)
    (string (aref x 0))
    (symbol (aref (symbol-name x) 0))))

(defun string-trim (character-bag string-designator)
  "common-lisp: returns a substring of string, with all characters in \
character-bag stripped off the beginning and end."
  (unless (sequencep character-bag)
    (signal 'type-error  "expected a sequence for `character-bag'."))
  (let* ((string (string* string-designator))
         (margin (format "[%s]*" (regexp-quote
                                  (if (stringp character-bag)
                                      character-bag
                                      (map 'string 'identity character-bag)))))
         (trimer (format "\\`%s\\(\\(.\\|\n\\)*?\\)%s\\'" margin margin)))
    (replace-regexp-in-string  trimer "\\1" string)))

(require 'cl)

так что вы можете написать одну функцию для CL и elisp:

(defun tweak-text (list caps lit)
  (let ((result '()))
    (dolist (item list (nreverse result))
      (cond ((find item " !?.")          (push item result))
            ((eql item (character "\"")) (setf lit (not lit)))
            (lit                         (push item result)
                                         (setf caps nil))
            (caps                        (push (char-upcase item) result)
                                         (setf caps nil))
            (t                           (push (char-downcase item) result)
                                         (setf caps nil
                                               lit nil))))))

(defun game-print (list)
  (princ (coerce (tweak-text (coerce (string-trim "() " (prin1-to-string list)) 'list)
                             t nil)
                 'string))
  (terpri))

Затем:

(game-print '(not only does this sentence have a "comma," it also mentions the "iPad."))

в Emacs:

prints:   Not only does this sentence have a comma, it also mentions the iPad.
returns:  t

в Common Lisp:

prints:   Not only does this sentence have a comma, it also mentions the iPad.
returns:  nil

Теперь, в общем, нет смысла использовать списки для обработки строк, и в emacs lisp, и в Common Lisp есть мощные примитивы для прямой обработки последовательностей и строк.

Обратите внимание, что elisp (к сожалению) не оптимизирует для хвостовой рекурсии, так что это оказывается очень неэффективным способом написания этой функции.

Как уже упоминал Паскаль Бургиньон, лучше использовать строки без наведения их на списки и обратно, ниже я расскажу об этом. Обратите внимание, что он немного отличается тем, что литеральные строки проверяются на пунктуацию, и если они, как кажется, имеют пунктуацию, что может привести к тому, что следующая буква будет в верхнем регистре, то она также будет в верхнем регистре. Я не уверен, что это недостаток, поэтому я не позаботился об этой разнице.

(defun tweak-text (source)
  (let ((i 0) (separator "") (cap t) current)
    (with-output-to-string
      (dolist (i source)
        (setq current
              (concat separator 
                      (etypecase i
                        (string i)
                        (symbol (downcase (symbol-name i)))))
              separator " ")
        (let (current-char)
          (dotimes (j (length current))
            (setq current-char (aref current j))
            (cond
             ((position current-char " \t\n\r"))
             (cap (setq cap nil
                        current-char (upcase current-char)))
             ((position current-char ".?!")
              (setq cap t)))
            (princ (char-to-string current-char))))))))

(tweak-text '(not only does this sentence have a "comma," it also mentions the "iPad."))
"Not only does this sentence have a comma, it also mentions the iPad."

Я думаю, что вы должны написать что-то вроде этого:

(defun tweak-text-wrapper (&rest args)
  (let ((max-lisp-eval-depth 9001)) ; as much as you want
    (apply tweak-text args)))
Другие вопросы по тегам