LLGPL Clarification

John Foderaro jkf@franz.com
Sat, 01 Jun 2002 08:16:35 -0700


 As I find myself wanting to restate the points I've already made
I realize that this discussion has gone full circle.  There's
no point in repeating myself, that's what mail archives are for.

 There seem to be two ways to read the LLGPL and LGPL.  Which is
correct?  To me when I understand something it makes things clearer
rather than more confused.  Those people who believe that the
use of a large Lisp macro in a LLGPL'ed library someone creates
a derived work end in with a world full of contraditions.  This alone
should offer a clue that this is not the correct interpretation.

 For example Bob Rogers points out this macro in pxml and claims
that to use it would create a derivative work since it's over 10 
lines long.

    (defmacro next-char (tokenbuf read-sequence-func)
      `(let ((cur (tokenbuf-cur ,tokenbuf))
	     (tb (tokenbuf-data ,tokenbuf)))
	 (if* (>= cur (tokenbuf-max ,tokenbuf))
	    then				;; fill buffer
		 (if* (or (not (tokenbuf-stream ,tokenbuf))
			  (zerop (setf (tokenbuf-max ,tokenbuf)
				   (if* ,read-sequence-func
				      then (funcall ,read-sequence-func tb
						    (tokenbuf-stream ,tokenbuf))
				      else (read-sequence tb (tokenbuf-stream ,tokenbuf))))))
		    then (setq cur nil)	;; eof
		    else (setq cur 0)))
	 (if* cur
	    then (prog1
		     (let ((cc (schar tb cur)))
		       (if (and (tokenbuf-stream ,tokenbuf) (eq #\return cc)) #\newline cc))
		   (setf (tokenbuf-cur ,tokenbuf) (1+ cur))))))




ok what if I wrote it like this:


    (defmacro next-char (tokenbuf read-sequence-func)
      `(let ((cur (tokenbuf-cur ,tokenbuf))
	     (tb (tokenbuf-data ,tokenbuf)))
	 (if* (>= cur (tokenbuf-max ,tokenbuf)) then (if* (or (not (tokenbuf-stream ,tokenbuf)) (zerop (setf (tokenbuf-max ,tokenbuf) (if* ,read-sequence-func then (funcall ,read-sequence-func tb (tokenbuf-stream ,tokenbuf)) else (read-sequence tb (tokenbuf-stream ,tokenbuf))))))
		    then (setq cur nil)	;; eof
		    else (setq cur 0)))
	 (if* cur then (prog1 (let ((cc (schar tb cur))) (if (and (tokenbuf-stream ,tokenbuf) (eq #\return cc)) #\newline cc)) (setf (tokenbuf-cur ,tokenbuf) (1+ cur)))))) 


 now it's 6 lines along.  Will using it now magically NOT create
a derivitive work?   And if you answer is yes (and it has to be
based on how you've read the LLGPL) then how crazy is that?


What if I rewrite the macro as a small macro and a big function


(defmacro next-char (tokenbuf read-sequence-func)
  (next-char-function tokenbuf read-sequence-func))
  
(defun next-char-function (tokenbuf read-sequence-func)
      `(let ((cur (tokenbuf-cur ,tokenbuf))
	     (tb (tokenbuf-data ,tokenbuf)))
	 (if* (>= cur (tokenbuf-max ,tokenbuf))
	    then				;; fill buffer
		 (if* (or (not (tokenbuf-stream ,tokenbuf))
			  (zerop (setf (tokenbuf-max ,tokenbuf)
				   (if* ,read-sequence-func
				      then (funcall ,read-sequence-func tb
						    (tokenbuf-stream ,tokenbuf))
				      else (read-sequence tb (tokenbuf-stream ,tokenbuf))))))
		    then (setq cur nil)	;; eof
		    else (setq cur 0)))
	 (if* cur
	    then (prog1
		     (let ((cc (schar tb cur)))
		       (if (and (tokenbuf-stream ,tokenbuf) (eq #\return cc)) #\newline cc))
		   (setf (tokenbuf-cur ,tokenbuf) (1+ cur))))))



Now the macro is only 2 lines long.    Bob Rogers and Marc Santoro would 
have to admit that using this macro is not a derivative work.

So now that I've got the virus-killing macro construction
what if I write a macro called defmacro-split that expands into
a tiny macro and a big function.


(defmacro-split  next-char (tokenbuf read-sequence-func)
      `(let ((cur (tokenbuf-cur ,tokenbuf))
	     (tb (tokenbuf-data ,tokenbuf)))
	 (if* (>= cur (tokenbuf-max ,tokenbuf))
	    then				;; fill buffer
		 (if* (or (not (tokenbuf-stream ,tokenbuf))
			  (zerop (setf (tokenbuf-max ,tokenbuf)
				   (if* ,read-sequence-func
				      then (funcall ,read-sequence-func tb
						    (tokenbuf-stream ,tokenbuf))
				      else (read-sequence tb (tokenbuf-stream ,tokenbuf))))))
		    then (setq cur nil)	;; eof
		    else (setq cur 0)))
	 (if* cur
	    then (prog1
		     (let ((cc (schar tb cur)))
		       (if (and (tokenbuf-stream ,tokenbuf) (eq #\return cc)) #\newline cc))
		   (setf (tokenbuf-cur ,tokenbuf) (1+ cur))))))
 

It this now non-viral?  What I if reimplement defmacro to do what
defmacro-split does

  (defmacro   next-char (tokenbuf read-sequence-func)
      `(let ((cur (tokenbuf-cur ,tokenbuf))
	     (tb (tokenbuf-data ,tokenbuf)))
	 (if* (>= cur (tokenbuf-max ,tokenbuf))
	    then				;; fill buffer
		 (if* (or (not (tokenbuf-stream ,tokenbuf))
			  (zerop (setf (tokenbuf-max ,tokenbuf)
				   (if* ,read-sequence-func
				      then (funcall ,read-sequence-func tb
						    (tokenbuf-stream ,tokenbuf))
				      else (read-sequence tb (tokenbuf-stream ,tokenbuf))))))
		    then (setq cur nil)	;; eof
		    else (setq cur 0)))
	 (if* cur
	    then (prog1
		     (let ((cc (schar tb cur)))
		       (if (and (tokenbuf-stream ,tokenbuf) (eq #\return cc)) #\newline cc))
		   (setf (tokenbuf-cur ,tokenbuf) (1+ cur))))))



Is this, your original example of a viral macro, now non-viral?
How can it be both viral and non-viral at the same time?

The whole argument about derivative work (which was designed to protect
C programmers) makes no sense in the Lisp world.  When you assume
something that makes no sense you quickly end up with contraditions
as I've show above.


Often people come across documents and try to guess the intent
of the authors.  In the case of the LLGPL I'm  a co-author so
no guessing is required.  I've spelled out exactly in the previous
letters the intent of the LLGPL and I can state again that
under the LLGPL the result of macro expansion does NOT result
in LLGPLed code.  

You may say that while the intentions of the authors aren't questioned
did those intentions make it to down on paper correctly?
To really understand the LLGPL you have to have a good knowledge
of the C language and the Lisp language.   I and many others (including 
a lawyer specializing in software issues) believe the LLGPL expresses 
those intentions. Those are the facts.  Neither I nor anyone else 
offers a warranty that the LLGPL expresses those intentions.  
If you are uncomfortable with the LLGPL see a lawyer (or a more
experienced programmer) for consultation or avoid LLGPL'ed code.


And that's all I've got to say on this issue.