From: Joerg Hoehle
Subject: checking implementation defined and implementation dependent behaviour
Date: 
Message-ID: <uslal3li2.fsf@users.sourceforge.net>
Hi,

CLHS invites implementations to document the gaps it explicitly leaves
as "implementation defined" or "implementation dependent" behaviour.

Some times ago in this group, I talked about testing this. Now I've
come up with a few testcases. I've put the file that follows online
into the clisp CVS repository at
http://clisp.cvs.sourceforge.net/clisp/clisp/tests/unportable.tst

I also added a section about gaps (omissions?) in the CLHS,
and another about whether or not errors are actually signaled.
Depending on any of this is inherently unportable.

My findings surprised me. Did you expect (LOOP REPEAT 3.5 ...) to
exhibit differences among implementations?
Please see for yourself the examples in the file below.

I'd be happy if other cases were shown, if some documentation were
completed or if part of this were added to some implementation's
regressions tests.

Ideally, one should go through CLHS side by side with each
implementation's documentation and see whether the latter defines each
occurrence of "impl. defined" and "impl. dependent" from CLHS.  I know
clisp's impnotes are quite complete in that regard, but I've not
checked any other implementation's documentation.

Ideally, results in the file (as a regression suite) should be added
only when an implementation documents a specific behaviour. I don't
consider it as something that should pin down observed behaviour in
the particular versions of cmucl (19c), sbcl (1.0.3.35), clisp (2.41)
and CormanLisp (2.0) I happened to use.

File copyright: Franz/Allegro's LLLGPL (ok with you?)
;; -*- Lisp -*-
;; Test unportable code of which some implementations specify the behaviour
;; These fall into 3 categories in ANSI-CL:
;; - "implementation defined",
;; - "implementation dependent" or
;; - "undefined consequences"
;; - none of the above, simply a gap in the spec
;; Use #-(or ...) to avoid implementations where the unportable form
;; may drop the bomb

;;;;
;;;;  Implementation-defined
;;;;

;; 13.1.4.6 & 13.6
#+(and clisp unicode)
(digit-char-p #\KHMER_DIGIT_ZERO) ; #\U17e0
#+(and clisp unicode) 0

;; In CLISP, there are unicode digits beside 0-9 a-z yet they form no numbers
#+(and clisp unicode)
(type-of (read-from-string (string #\KHMER_DIGIT_ZERO)))
#+(and clisp unicode) SYMBOL


;;;;
;;;;  Implementation-dependent
;;;;

;; 6.1.1.4 Whether initial value forms of for/as variables include
;; lexical environment of all loop variables or only preceding ones.
;; clisp changed its behaviour across versions

#-(or clisp)
(let ((vars '(1 2))) (loop for vars on vars collect (length vars)))
#+(or cmu sbcl cormanlisp) (2 1)

#-(or clisp)
(let ((vars '(1 2 3))) (loop for i from 0 below 5 for vars on vars collect (length vars)))
#+(or cmu sbcl cormanlisp) (3 2 1)

;; Whether the iteration constructs establish a new binding of var on
;; each iteration or whether it establishes a binding for var once at
;; the beginning and then assigns it on any subsequent iterations

;; Actually, CLISP does not guarantee the (3 3 3) or (2 2 2) result,
;; it just guarantees that it won't be (2 1 0), but rather (x x x),
;; because a single binding is assigned on each iteration.

(let (a)
  (dotimes (i 3) (push (lambda () i) a))
  (loop for x in a collect (funcall x)))
#+(or clisp cmu sbcl cormanlisp) (3 3 3)
#+(or) (2 1 0)

(let (a)
  (dolist (i '(0 1 2)) (push (lambda () i) a))
  (loop for x in a collect (funcall x)))
#+(or clisp) (2 2 2)
#+(or cmu sbcl) (2 1 0)
#+(or cormanlisp) (nil nil nil)


#|
;; 1.4.1.5 Coerce designator to function once or every time
(defun add-some (x) ; TODO not reproducible past first run
  (defun add-some (x) (+ x 2))
  (+ x 1))
add-some
;; ffi callback: coerce immediately, IIRC don't even accept name

#-(or)
(mapcar 'add-some '(1 2 3 4))
#+(or clisp cormanlisp) (2 4 5 6)
|#

#|
;; 3.2.5 error handler for type error in compile[-file]
(with-simple-restart (error "test handler")
  (compile '(macrolet ((foo () (error "at compile-time")))
              (foo))))
|#

;; 5.1.1.2 setf expander vs. setf function of standardized accessors
(fboundp '(setf car))
#+(or clisp) NIL
#+(or cmu sbcl cormanlisp) T


;;;;
;;;; Undefined Consequences
;;;;

;; 2.4.8.3
#-(or) (read-from-string "#3()")
#+(or CLISP) ERROR
#+(or sbcl cmu cormanlisp) #(nil nil nil)

;; 5.2
;; "The consequences are undefined if an attempt is made to transfer
;; control to an exit point whose dynamic extent has ended."


;;;;
;;;; Simply unspecified
;;;;

;; CLHS has a note on PROG where it is "explained" as (BLOCK (LET (TAGBODY ...))
;; whereas Mario Latendresse's paper on list comprehensions shows a macroexpansion
;; with the nesting (LET (BLOCK (TAGBODY ...))) -- what implementation was used?
;; Note that notes in ANSI-CL are not a binding part of the standard.

(block nil (prog ((x (return :outer-let))) (return :never)) (return :clhs))
#-cormanlisp :clhs
#+cormanlisp :outer-let

(dolist (i '(1 2 . 3) i))
ERROR

(loop for i in '(1 2 . 3) count t)      ; for comparison, well-defined
ERROR                                   ; 6.1.2.1.2 via ENDP

;;;;
;;;; Portable code, but care to depend on this?
;;;;

;; 6.1.9
;; "The clause repeat n is roughly equivalent to a clause such as
;; for downfrom (- n 1) to 0"
;; BTW, Iterate differs and uses ceiling instead.
(loop repeat 3.5 count t)
#+(or cmu) 3
#+(or clisp sbcl cormanlisp) 4

(loop for i downfrom (- 3.5 1) to 0 count t) ; for comparison, well-defined
3

;; "the concept [of length] does not make sense for dotted lists",
;; says ANSI-CL issue DOTTED-LIST-ARGUMENTS
(length '(1 2 . 3))                     ; dotted list is not a sequence
ERROR                                   ; how annoying

(list-length '(1 2 . 3))
ERROR                                   ; agreed


;; Paul Dietz' ANSI testsuite (part of gcl) checks some border cases
;; http://cvs.savannah.gnu.org/viewcvs/gcl/ansi-tests/beyond-ansi/?root=gcl


Regards,
	Jorg Hohle
Telekom/T-Systems Technology Center

From: Juho Snellman
Subject: Re: checking implementation defined and implementation dependent behaviour
Date: 
Message-ID: <slrnf3530n.k6p.jsnell@sbz-30.cs.Helsinki.FI>
Joerg Hoehle <······@users.sourceforge.net> wrote:
> ;; Whether the iteration constructs establish a new binding of var on
> ;; each iteration or whether it establishes a binding for var once at
> ;; the beginning and then assigns it on any subsequent iterations
>
> ;; Actually, CLISP does not guarantee the (3 3 3) or (2 2 2) result,
> ;; it just guarantees that it won't be (2 1 0), but rather (x x x),
> ;; because a single binding is assigned on each iteration.
>
> (let (a)
>   (dotimes (i 3) (push (lambda () i) a))
>   (loop for x in a collect (funcall x)))
> #+(or clisp cmu sbcl cormanlisp) (3 3 3)
> #+(or) (2 1 0)

CMUCL 19d was changed to return (2 1 0) for this. I think it was due
to pedantically reading the following part of the spec:

  dotimes evaluates count-form, which should produce an integer. If
  count-form is zero or negative, the body is not executed. dotimes
  then executes the body once for each integer from 0 up to but not
  including the value of count-form

as requiring the iteration count to always be constant, whether or not
the iteration variable is changed. That is, the interpretation is that
the following should return 5, not 1:

  (let ((x 0))
    (dotimes (i 5 x)
      (incf x)
      (setf i 5)))

For which the most reasonable, though not only, implementation is to 
rebind the variable on every iteration.

-- 
Juho Snellman
From: Tobias C. Rittweiler
Subject: Re: checking implementation defined and implementation dependent behaviour
Date: 
Message-ID: <87mz0qjj3n.fsf@freebits.de>
Joerg Hoehle <······@users.sourceforge.net> writes:


> ;;;;
> ;;;; Simply unspecified
> ;;;;
>
> [...]
>
> (dolist (i '(1 2 . 3) i))
> ERROR
>
> (loop for i in '(1 2 . 3) count t)      ; for comparison, well-defined
> ERROR                                   ; 6.1.2.1.2 via ENDP

You're right that the case is not really specified for DOLIST, but FWIW
let me point you to [System Class List], where it's stated that

  ``[d]otted lists and circular lists are also lists, but usually the
  unqualified term ``list'' within this specification means proper
  list.''

(Which is, philosophically, also in conformance with [CLHS 17.1.1].)

Now it's the question what is meant with "unqualified term"---is a
qualified term equivalent to a hyperlinked term in the HyperSpec? In
that case, the DOLIST entry would refer to the type LIST (as it's
hyperlinked to the glossary) and not the unqualified term. 

OTOH, it may be worthwhile to look for what's exactly within the ANSI
standard document.

[System Class List] - http://www.lispworks.com/documentation/HyperSpec/Body/t_list.htm
[CLHS 17.1.1]       - http://www.lispworks.com/documentation/HyperSpec/Body/17_aa.htm



> ;; Paul Dietz' ANSI testsuite (part of gcl) checks some border cases
> ;; http://cvs.savannah.gnu.org/viewcvs/gcl/ansi-tests/beyond-ansi/?root=gcl

What's the status of this project? 

Furthermore, there were quite some occasions where I encountered what
I'd say is an edge case of the standard (or a case where the reading of
the spec might be a bit tricky), and I'd have liked to report the case
to all implementators to let them see if they get it right.

Unfortunately there is currently no medium to get into contact to all
implementators at once to discuss such things. I'd really like a
platform (mailinglist, say) where I know that responsible people of all
Common Lisp implementations are listening on.

  -T.