technoblogy / ulisp

A version of the Lisp programming language for ATmega-based Arduino boards.
http://www.ulisp.com/
MIT License
377 stars 46 forks source link

Printing recursive objects can't be interrupted #54

Open dragoncoder047 opened 2 years ago

dragoncoder047 commented 2 years ago
(defvar x '(a))
(setf (cdr x) x)
(print x) ;; prints (a a a a a a a a ... forever until it crashes

There should be a way to mark objects during printing and then clear the mark, so that instead of printing infinitely, the above would print (a <recursive>) or something similar.

My idea for a solution:

void markobject (object *obj, bool target = true) {

Then supersub can mark each object after it prints it, and then markobject(obj, false) to clear the mark before returning. If it hits a marked object it prints <recursive> and returns without going any farther.

technoblogy commented 2 years ago

In fact, you don't need the print because after the setf it returns:

(a a a a a a ...

I've tried it on LispWorks and the same thing happens, so this isn't unique to uLisp. I think at least it should allow you to escape with "~".

I'm not convinced that your proposed solution would work, because structures with shared sublists need not be recursive.

By the way, you raise a number of interesting points, and it's a shame that you don't discuss them on the uLisp forum as I think they would be of interest to other uLisp users. Would you like me to set you up with a login?

technoblogy commented 2 years ago

structures with shared sublists need not be recursive

Here's an example:

> (defvar a '(x y z))
a

15862> (defvar b (cons a a))
b

15858> b
((x y z) x y z)

15858> (setf (car (car b)) 'w)
w

15858> b
((w y z) w y z)
dragoncoder047 commented 2 years ago

I'm not convinced that your proposed solution would work, because structures with shared sublists need not be recursive.

Most Lisps don't choke on the setf, because all that does is create an object that points to itself. In moemory that is totally okay, and I have heard people use it to create a circular linked list (by nconc'ing a list onto itself). It fails on the print because the printer isn't smart enough to recognize the reference cycle. (I tried GNU Clisp, and it fails in the same way.) Consider what Python does when it gets to an object that points to itself:

x = list()
x.append(x)
print(x) # prints [[...]] -- the ... indicating it has detected a recursive structure.

AFAIK, the way Python does that is by keeping track of which objects in the pointer structure it has already printed, and when it detects a reference cycle, it prints ... instead. markobject does this already anyway; it checks to see if it is marked already and aborts if it is. print can do the same by marking each object after it is printed, and then unmarking everything when it is done.

If it isn't recursive, nothing bad will ever happen (even in the current uLisp), because there are no reference cycles.

By the way, you raise a number of interesting points, and it's a shame that you don't discuss them on the uLisp forum as I think they would be of interest to other uLisp users. Would you like me to set you up with a login?

No. I'll set one up myself when I feel it would be necessary.

dragoncoder047 commented 2 years ago

perhaps another example would help:

(defvar x '(1 (2 nil) 3))
(setf (car (cdr (cdr x))) x)
; now x is (1 (2 x) 3)
(print x) ; prints (1 (2 (1 (2 (1 (2 (1 (2 ... forever, never gets to the 3
x = [1, [2, None], 3]
x[1][1] = x
print(x) # prints [1, [2, [...]], 3]
technoblogy commented 2 years ago

Yes, I understand, and I agree it would be nice to provide this if possible, but I think it goes a bit beyond what's needed for uLisp. I doubt if any uLisp user will be relying on circular lists.

dragoncoder047 commented 7 months ago
(defvar x '(a))
(setf (cdr x) x)
(print x) ;; prints (a a a a a a a a ... forever

I just tried this and it just goes forever, no crash, but the printing can't be interrupted with ~. Maybe this should be fixed?

Interestingly enough, (setf (car x) x) (so that the printer won't take the tail-optimized path) only gets to (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((Guru Meditation Error: Stack canary watchpoint triggered (loopTask). I didn't realize I had such little stack space!

technoblogy commented 7 months ago

I just tried this and it just goes forever, no crash, but the printing can't be interrupted with ~.

Yes, fair enough, I could call testescape() within printobject(), although someone would have to go out of their way to have this problem.

Interestingly enough, (setf (car x) x) (so that the printer won't take the tail-optimized path) only gets to ...

Harder to solve that one.

Thanks!

dragoncoder047 commented 7 months ago

Harder to solve that one.

Yeah, I had the same problem where I tried to implement a cyclic object detector that works be able to allow the printer to automatically print it out as #0=(#0# . nil) and not crash. I could tail optimize the cdr, but car pointers caused infinite recursion just like the way uLisp is now.

Do you have any ideas/experience on detecting shared structure (for this and for also printing objects like (#0=(123) #0#) that have shared structure but are not self-referential)?

technoblogy commented 7 months ago

LispWorks prints it as:

> (defvar y '(a))
Y

> (setf (car y) y)
((((#))))

Not sure how to detect that.

dragoncoder047 commented 7 months ago

LispWorks prints it as:

   > (defvar y '(a))
   Y

   > (setf (car y) y)
   ((((#))))

Try setting your *print-level* to nil to make it print out the entire structure and not bail if it is nested deeply. If it prints forever or crashes, then that means LispWorks doesn't have any kind of cycle detection.

*print-level* controls how many levels deep a nested object will print. If it is false, then no control is exercised. Otherwise, it is an integer indicating the maximum level to be printed. An object to be printed is at level 0; its components (as of a list or vector) are at level 1; and so on. If an object to be recursively printed has components and is at a level equal to or greater than the value of *print-level*, then the object is printed as #.