Question

The program should reformat the string like below.

Example: (game-print '(THIS IS A SENTENCE。 WHAT ABOUT THIS? PROBABLY.))

This is a sentence. What about ths? Probably.

But something is wrong( Lisp nesting exceeds `max-lisp-eval-depth) and i don't know why. This piece of code is actually from the book "Land of lisp" in page 97. The original code is written in common lisp. I want to rewrite it in elisp. The last two argument in tweak-text means captain and literal.

(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."))

The orignal code written in common lisp.

(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))
Was it helpful?

Solution

You are indeed hitting the 'max-lisp-eval-depth' limit when recursing in tweak-text. I don't see anything wrong with the way the code is(I didn't check if its doing what you intend it to do).

You can configure/raise the 'max-lisp-eval-depth' limit. The documentation for that variable states that you can raise it as long as you are confident that you are not going to trip into running out of stack space. The limit is conservatively set to 541 on my machine. Raising it to 600 got the function definition above to work on the input you gave as example.

OTHER TIPS

In both cases, you have non-terminal recursions, so you're using O(length(lst)) stack space. Obviously, systems may limit the stack space you can use, and you do indeed reach this limit in emacs. (Now then in emacs, you can increase the limit by changing max-lisp-eval-depth, but this won't solve the fundamental problem).

The solution is to use iteration instead of recursion.

But first, write in 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)

so that you can write a single function for both CL and 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))

Then:

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

in emacs:

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

in Common Lisp:

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

Now, in general there's little point of using lists to process strings, both emacs lisp and Common Lisp have powerful primitives to deal with sequences and strings directly.

Note that elisp (sadly) does not optimise for tail-recursion, so that turns out to be a very inefficient way to write this function.

As Pascal Bourguignon already mentioned it, using strings w/o coercing them to lists and back would be a better approach, below is my take at it. Note that it is slightly different in that literal strings are verified for punctuation, and if they appear to have punctuation such as would cause it otherwise to have the next letter upper-cased, then it would be upper cased too. I'm not sure this is a disadvantage, this is why I didn't take care of this difference.

(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."

I think you should write something like this:

(defun tweak-text-wrapper (&rest args)
  (let ((max-lisp-eval-depth 9001)) ; as much as you want
    (apply tweak-text args)))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top