Search code examples
common-lispclisp

Stack overflow in GNU CLISP (& not in SBCL)


I was getting stack overflow with the following code, then I tried it in SBCL and it worked. Wondering what causes the difference there.

Specifically: While I do plan to move to SBCL at some point, can this be made to work in CLISP?

(defvar *objs* nil)                                 ; [1]

(defun parents (obj) (gethash :parents obj))

(defun obj (&rest parents)                          ; [2]
  (let ((obj (make-hash-table)))
    (push obj *objs*)
    (setf (parents obj) parents)
    obj))

(defun (setf parents) (val obj)                     ; [3]
  (prog1 (setf (gethash :parents obj) val)
         (make-precedence obj)))

(defun make-precedence (obj)                        ; [4]
  (setf (gethash :preclist obj) (precedence obj))
  (dolist (x *objs*)
    (if (member obj (gethash :preclist x))
        (setf (gethash :preclist x) (precedence x)))))

(defun precedence (obj)                             ; [5]
  (delete-duplicates (traverse obj)))

(defun traverse (x)                                 ; [6]
  (cons x (mapcan #'traverse (gethash :parents x))))

;; [1] We'll store a list of objects we create in *obj*.
;; [2] Function to create an object, called like (setf scoundrel (obj)).
;; [3] Set an objects (multiple) parents & rebuild precedence list for all affected objs.
;; [4] Rebuild precedence list for obj, then for all affected objs.
;; [5] Returns a list of object and all its ancestors in precedence order as we define it.
;;     (Can read it like (-> obj traverse delete-duplicates) if it helps)
;; [6] Cons an object to all its parents recursively; depth first search.
;;     I pulled this out of labels in precedence above it for clarity & testability.
;; Source: PG's ANSI Common Lisp, Chapter 17, "Example: Objects".

Example - SBCL

(setf scoundrel (obj))
; #<HASH-TABLE :TEST EQL :COUNT 2 {1001A01893}>
(setf sc2 (obj scoundrel))
; #<HASH-TABLE :TEST EQL :COUNT 2 {1001A1F153}>
*objs*
; (#<HASH-TABLE :TEST EQL :COUNT 2 {1001A1F153}>
;  #<HASH-TABLE :TEST EQL :COUNT 2 {1001A01893}>)
(parents scoundrel)
; NIL
; T
(parents sc2)
; (#<HASH-TABLE :TEST EQL :COUNT 2 {1001A01893}>)
; T

Example - GNU CLISP

(setf scoundrel (obj))
;; - Lisp stack overflow. RESET
*objs*
;; - Lisp stack overflow. RESET

It might be worth mentioning that I haven't studied the dual interpreted and compiled nature of lisp a lot yet. So far I've simply been using it as an interpreted language; by pasting the above functions into the clisp repl.

So I suspect that compiling all these functions could be one thing to consider. I note we can compile and compile-file, I can't see an operator which compiles all user defined functions though.


Solution

  • GNU CLISP prints the contents of the hash table by default. In your case, it contains circular structures.

    • Either set *PRINT-CIRCLE* to T, to enable printing circular structures without stack overflow.
    > (setq *print-circle* t)
    T
    > *objs*
    (#1=#S(HASH-TABLE :TEST FASTHASH-EQL (:PRECLIST . (#1#)) (:PARENTS . NIL)))
    
    • Or set *PRINT-ARRAY* and *PRINT-READABLY* to NIL, to disable printing the contents of the hash table.
    > (setq *print-circle* nil *print-array* nil *print-readably* nil)
    NIL
    > *objs*
    (#<HASH-TABLE :TEST FASTHASH-EQL :COUNT 2 #x000335098D40>)