From: Dave Roberts
Subject: Error checking style question
Date: 
Message-ID: <1pxwc.46033$pt3.24800@attbi_s03>
I'm working on a set of macros for defining various versions of state
machines. The macro form works by naming various states and then defining
transitions between those states. An error can occur if the user mis-names
a state or transition such that they don't match when they should.

What is the best way to handle something like this? Should I try to detect
it when the macro runs, or just let it fly and have the compiler or runtime
fail later, after the expansion? The problem is, it's sometimes quite
difficult to understand what is going on at runtime (or even compile time)
when there is a problem with a form with heavy macrology.

If I was to try to detect the problem, how would I signal it? Is there a
standard error that a macro should signal to the compiler for this?

-- 
Dave Roberts, ·············@re-move.droberts.com
Slowly but surely, the programming world is finding Lisp...
http://www.findinglisp.com/blog

From: Rainer Joswig
Subject: Re: Error checking style question
Date: 
Message-ID: <c366f098.0406060123.34955dad@posting.google.com>
Dave Roberts <·············@re-move.droberts.com> wrote in message news:<·····················@attbi_s03>...
> I'm working on a set of macros for defining various versions of state
> machines. The macro form works by naming various states and then defining
> transitions between those states. An error can occur if the user mis-names
> a state or transition such that they don't match when they should.
> 
> What is the best way to handle something like this? Should I try to detect
> it when the macro runs, or just let it fly and have the compiler or runtime
> fail later, after the expansion? The problem is, it's sometimes quite
> difficult to understand what is going on at runtime (or even compile time)
> when there is a problem with a form with heavy macrology.

Do you really NEED heavy macrology? For what? 

> 
> If I was to try to detect the problem, how would I signal it? Is there a
> standard error that a macro should signal to the compiler for this?
From: Dave Roberts
Subject: Re: Error checking style question
Date: 
Message-ID: <SXIwc.14539$Sw.2075@attbi_s51>
Rainer Joswig wrote:

> Do you really NEED heavy macrology? For what?

To develop a mini-language for state machine creation. Need is sort of in
the eye of the beholder, but it's sort of beside the point in this case.

The real question is, what's the best way to handle errors of various types
in macros that can be detected at compile time? Should the macro try to do
it, or should it just handle the expansion as best it can and leave the
error for either the compiler or runtime?

-- 
Dave Roberts, ·············@re-move.droberts.com
Slowly but surely, the programming world is finding Lisp...
http://www.findinglisp.com/blog
From: Barry Margolin
Subject: Re: Error checking style question
Date: 
Message-ID: <barmar-FC86E8.15263206062004@comcast.dca.giganews.com>
In article <···················@attbi_s51>,
 Dave Roberts <·············@re-move.droberts.com> wrote:

> The real question is, what's the best way to handle errors of various types
> in macros that can be detected at compile time? Should the macro try to do
> it, or should it just handle the expansion as best it can and leave the
> error for either the compiler or runtime?

In general, I think it's best to catch errors as early as possible.  If 
they can be detected at compile time, they're essentially like syntax 
errors, so should be reported then like any other syntax error.

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
From: Kenny Tilton
Subject: Re: Error checking style question
Date: 
Message-ID: <phEwc.108831$Nn4.23668621@twister.nyc.rr.com>
Dave Roberts wrote:
> I'm working on a set of macros for defining various versions of state
> machines. The macro form works by naming various states and then defining
> transitions between those states. An error can occur if the user mis-names
> a state or transition such that they don't match when they should.
> 
> What is the best way to handle something like this? Should I try to detect
> it when the macro runs,

Nah, keep the flexibility Lisp offers to coders by not worrying if stuff 
exists until you run it.

  or just let it fly and have the compiler or runtime
> fail later, after the expansion? The problem is, it's sometimes quite
> difficult to understand what is going on at runtime (or even compile time)
> when there is a problem with a form with heavy macrology.

Die by the macro, live by the macro. It took me way too long to figure 
out a simple fix to what I am guessing[1] is the problem you see with 
heavy macrology. A Cell rule looks like this:

   (make-instance
     :visible (c? (find id ;; closed over lexical
                    (shape-ids
                      (car (md-value (fm^ :shape))))))

That expands into an anonymous lambda function. On a backtrace, I would 
just see a bunch of Cell internals leading to some lambda. In some cases 
such as calculating widget geometry where /every/ rule uses the same 
geometric slots for their calculations, it was often hard to figure out 
which rule was breaking. (Instances themselves were rule-generated and I 
might only know it is some text field somewhere in the GUI hierarchy.) I 
finally figured out a two-minute fix:

   (defmacro c? (&body body)
     `(make-c-dependent
        :code ',body ;; <====== too easy?
        :value-state :unevaluated
        :rule (c-lambda ,@body)))

So the above (c? ...) expands approximately into:

  (cells::make-c-dependent
    :code '((find id (shape-ids (car (md-value (fm^ :shape))))))
     :value-state :unevaluated
   :rule (lambda (c &aux (self (c-model c)))
           (find id (shape-ids (car (md-value (fm^ :shape)))))))

And now the code can be tracked down by inspecting the cell instance 
which shows up in the c-calculate-and-set call frame.

This may not be much help to your specific problem, but the larger point 
is that you might be able to macro your way out of the situation.

kenny


[1] A little more code showing "the problem" would help.

-- 
Home? http://tilton-technology.com
Cells? http://www.common-lisp.net/project/cells/
Cello? http://www.common-lisp.net/project/cello/
Why Lisp? http://alu.cliki.net/RtL%20Highlight%20Film
Your Project Here! http://alu.cliki.net/Industry%20Application
From: Dave Roberts
Subject: Re: Error checking style question
Date: 
Message-ID: <ehJwc.51778$3x.49109@attbi_s54>
Kenny Tilton wrote:

> This may not be much help to your specific problem, but the larger point
> is that you might be able to macro your way out of the situation.

Got it. That probably doesn't apply as much to my specific type of error,
but I take the larger point and I see where that would be a big help.

> [1] A little more code showing "the problem" would help.

Sure, no problemo. Here's a macro that defines syntax for a simple
automaton. Basically, it expands to a defun and some machinery for getting
input and transitioning to the next state.

(defmacro define-automaton (name states &key (stop 'stop))
  (let ((event-func (gensym "func")))
    `(defun ,name (,event-func)
      (tagbody
         ,@(reduce 
            #'append 
            (mapcar (lambda (state)
                      (let ((state-name (car state))
                            (transitions (cdr state)))
                        (list state-name 
                              `(case (funcall ,event-func)
                                ,@(mapcar (lambda (trans)
                                            (let ((match (car trans))
                                                  (next (cadr trans))
                                                  (actions (cddr trans)))
                                              (cons match
                                                    (append actions
                                                            `((go ,next))))))
                                          transitions))
                              `(go ,state-name))))
                    states))
         ,stop))))

I would use this to define an automaton like so:

(define-automaton an-auto
    ((state1 ('a stop)
            ('b state2))
     (state2 ('a state1)
             ('b foo))))

This expands into a TAGBODY form with GO forms transitioning between states,
like so:

(DEFUN AN-AUTO (#:|func3372|)
  (TAGBODY
   STATE1
    (CASE (FUNCALL #:|func3372|) ((QUOTE A) (GO STOP)) ((QUOTE B) (GO
STATE2)))
    (GO STATE1)
   STATE2
    (CASE (FUNCALL #:|func3372|) ((QUOTE A) (GO STATE1)) ((QUOTE B) (GO
FOO)))
    (GO STATE2)
   STOP))

You would then call AN-AUTO with a function for getting input. For instance,
a great way to test is:
(an-auto #'read)

Then you just type symbols into the reader and you can make the machine do
what you want. Another nice way for doing test cases would be to POP items
out of a list.

Now, the automaton above contains an error. There is no state "foo." It may
look like there is an error with "stop" as a state, but that's a special
state that stops that automaton and returns to the caller. When you compile
this, you end up with a compile-time error under SBCL:

; in: LAMBDA NIL
;     (GO FOO)
; 
; caught ERROR:
;   attempt to GO to nonexistent tag: FOO

;     (FUNCALL #:|func3375|)
; --> SB-C::%FUNCALL THE 
; ==>
;   (SB-KERNEL:%COERCE-CALLABLE-TO-FUN FUNCTION)
; 
; note: unable to
;         optimize away possible call to FDEFINITION at runtime
;       due to type uncertainty:
;         The first argument is a (OR FUNCTION SYMBOL), not a FUNCTION.
; compilation unit finished
;   caught 1 ERROR condition
;   printed 2 notes

In this case, I'd probably figure things out; the automaton is simple enough
and I wrote the macro, so no biggie. But this piqued my general interest in
error reporting style for macros. I could, for instance, detect that a
given state is not present when I'm performing the expansion and provide a
better error message than what the compiler would.

-- 
Dave Roberts, ·············@re-move.droberts.com
Slowly but surely, the programming world is finding Lisp...
http://www.findinglisp.com/blog
From: Kenny Tilton
Subject: Re: Error checking style question
Date: 
Message-ID: <jcMwc.108861$Nn4.23813587@twister.nyc.rr.com>
Dave Roberts wrote:
> Kenny Tilton wrote:
> 
> 
>>This may not be much help to your specific problem, but the larger point
>>is that you might be able to macro your way out of the situation.
> 
> 
> Got it. That probably doesn't apply as much to my specific type of error,
> but I take the larger point and I see where that would be a big help.
> 
> 
>>[1] A little more code showing "the problem" would help.
> 
> 
> Sure, no problemo. Here's a macro that defines syntax for a simple
> automaton. Basically, it expands to a defun and some machinery for getting
> input and transitioning to the next state.
> 
> (defmacro define-automaton (name states &key (stop 'stop))
>   (let ((event-func (gensym "func")))
>     `(defun ,name (,event-func)
>       (tagbody
>          ,@(reduce 
>             #'append 
>             (mapcar (lambda (state)
>                       (let ((state-name (car state))
>                             (transitions (cdr state)))
>                         (list state-name 
>                               `(case (funcall ,event-func)
>                                 ,@(mapcar (lambda (trans)
>                                             (let ((match (car trans))
>                                                   (next (cadr trans))
>                                                   (actions (cddr trans)))
>                                               (cons match
>                                                     (append actions
>                                                             `((go ,next))))))
>                                           transitions))
>                               `(go ,state-name))))
>                     states))
>          ,stop))))
> 
> I would use this to define an automaton like so:
> 
> (define-automaton an-auto
>     ((state1 ('a stop)
>             ('b state2))
>      (state2 ('a state1)
>              ('b foo))))
> 
> This expands into a TAGBODY form with GO forms transitioning between states,
> like so:
> 
> (DEFUN AN-AUTO (#:|func3372|)
>   (TAGBODY
>    STATE1
>     (CASE (FUNCALL #:|func3372|) ((QUOTE A) (GO STOP)) ((QUOTE B) (GO
> STATE2)))
>     (GO STATE1)
>    STATE2
>     (CASE (FUNCALL #:|func3372|) ((QUOTE A) (GO STATE1)) ((QUOTE B) (GO
> FOO)))
>     (GO STATE2)
>    STOP))
> 
> You would then call AN-AUTO with a function for getting input. For instance,
> a great way to test is:
> (an-auto #'read)
> 
> Then you just type symbols into the reader and you can make the machine do
> what you want. Another nice way for doing test cases would be to POP items
> out of a list.
> 
> Now, the automaton above contains an error. There is no state "foo." It may
> look like there is an error with "stop" as a state, but that's a special
> state that stops that automaton and returns to the caller. When you compile
> this, you end up with a compile-time error under SBCL:
> 
> ; in: LAMBDA NIL
> ;     (GO FOO)
> ; 
> ; caught ERROR:
> ;   attempt to GO to nonexistent tag: FOO
> 
> ;     (FUNCALL #:|func3375|)
> ; --> SB-C::%FUNCALL THE 
> ; ==>
> ;   (SB-KERNEL:%COERCE-CALLABLE-TO-FUN FUNCTION)
> ; 
> ; note: unable to
> ;         optimize away possible call to FDEFINITION at runtime
> ;       due to type uncertainty:
> ;         The first argument is a (OR FUNCTION SYMBOL), not a FUNCTION.
> ; compilation unit finished
> ;   caught 1 ERROR condition
> ;   printed 2 notes
> 
> In this case, I'd probably figure things out; the automaton is simple enough
> and I wrote the macro, so no biggie. But this piqued my general interest in
> error reporting style for macros. I could, for instance, detect that a
> given state is not present when I'm performing the expansion and provide a
> better error message than what the compiler would.
>   

My state machines sit in loops:

   (loop with state = 'initial
       for input = (next-input)
       for token = (tokenize input)
       do (setf state
              (ecase state
                 (initial (if (boring token)
                             'initial
                             (Start-new token input)))
                  ....

So I have the option of computing the next state, and I have the luxury 
of coding the state transitions as I get to them. ie, start-new above 
can return states not yet coded for, yet I will still be able to run the 
software and test the cases I have gotten to.

But your approach is fine, too.

kenny




-- 
Home? http://tilton-technology.com
Cells? http://www.common-lisp.net/project/cells/
Cello? http://www.common-lisp.net/project/cello/
Why Lisp? http://alu.cliki.net/RtL%20Highlight%20Film
Your Project Here! http://alu.cliki.net/Industry%20Application
From: Vasilis Margioulas
Subject: Re: Error checking style question
Date: 
Message-ID: <a1ce0e2f.0406070527.5ad006d0@posting.google.com>
Dave Roberts <·············@re-move.droberts.com> wrote in message news:<····················@attbi_s54>...
> 
> In this case, I'd probably figure things out; the automaton is simple enough
> and I wrote the macro, so no biggie. But this piqued my general interest in
> error reporting style for macros. I could, for instance, detect that a
> given state is not present when I'm performing the expansion and provide a
> better error message than what the compiler would.

Maybe this help:

(defun get-state-names (states)
  (cons 'stop (mapcar #'car states)))

(defun get-next-states (states)
  (remove-duplicates
   (mapcan #'(lambda (state) 
	       (mapcar #'cadr (cdr state)))
	   states)))

(defun mismatch-states (states)
  (let ((states (get-state-names states))
	(next-states (get-next-states states)))
    (remove-if #'(lambda (x) (member x states)) next-states)))

(define-condition automaton-parse-error (error)
  ((automaton-name :initform nil :initarg :automaton-name)))

(define-condition state-mismatch-error (automaton-parse-error)
  ((mismatch-states :initform nil :initarg :mismatch-states))
  (:report (lambda (condition stream)
	     (with-slots (automaton-name mismatch-states) condition
	       (format stream "ERRORS in ~A:~%~{State ~A not defined~%~}"
		       automaton-name mismatch-states)))))

(defun check-syntax (name states)
  (let ((mismatch-states (mismatch-states states)))
    (when mismatch-states
      (signal (make-condition 'state-mismatch-error
			      :automaton-name name
			      :mismatch-states mismatch-states)))))


(defmacro define-automaton (name states &key (stop 'stop))
  (handler-case 
      (let ((event-func (gensym "func")))
	(check-syntax name states)
	
	`(defun ,name (,event-func)
	  ...
	  ...))    
    (automaton-parse-error (c) (format t "~A" c))))


CL-USER> (define-automaton an-auto
	     ((state1 ('a stop)
		      ('b state2))
	      (state2 ('a state1)
		      ('b foo))))
ERRORS in AN-AUTO:
State FOO not defined
NIL
CL-USER>
From: Dave Roberts
Subject: Re: Error checking style question
Date: 
Message-ID: <yJ%wc.12843$HG.2214@attbi_s53>
Vasilis Margioulas wrote:
> CL-USER> (define-automaton an-auto
> ((state1 ('a stop)
> ('b state2))
> (state2 ('a state1)
> ('b foo))))
> ERRORS in AN-AUTO:
> State FOO not defined
> NIL
> CL-USER>

Yea, that's exactly what I was thinking about. The question is, do people
actually ever go to that much trouble in a macro, or do they just suffer
through? It's sounding like most of the time people just suffer through,
which is fine. ;-)

-- 
Dave Roberts, ·············@re-move.droberts.com
Slowly but surely, the programming world is finding Lisp...
http://www.findinglisp.com/blog
From: Joe Marshall
Subject: Re: Error checking style question
Date: 
Message-ID: <u0xntjm8.fsf@ccs.neu.edu>
Dave Roberts <·············@re-move.droberts.com> writes:

> Yea, that's exactly what I was thinking about. The question is, do people
> actually ever go to that much trouble in a macro, or do they just suffer
> through? 

It depends on the macro.  For some macros I use, the expansion is
trivial enough that I just rely on the expanded code.  For others,
there are warnings and/or error messages.  I'm more likely to write
robustified macros if other people are going to use them.
From: Thomas F. Burdick
Subject: Re: Error checking style question
Date: 
Message-ID: <xcvvfi3epcy.fsf@famine.OCF.Berkeley.EDU>
Dave Roberts <·············@re-move.droberts.com> writes:

> I'm working on a set of macros for defining various versions of state
> machines. The macro form works by naming various states and then defining
> transitions between those states. An error can occur if the user mis-names
> a state or transition such that they don't match when they should.
> 
> What is the best way to handle something like this? Should I try to detect
> it when the macro runs, or just let it fly and have the compiler or runtime
> fail later, after the expansion?

What's wrong with both?  I'd try to detect it at macroexpansion time,
and WARN that you're about to produce junk; then actually ERR(OR) at
runtime.  I try not to err at macroexpansion time, except when the
input is complete nonsense -- warnings are good, though.

-- 
           /|_     .-----------------------.                        
         ,'  .\  / | No to Imperialist war |                        
     ,--'    _,'   | Wage class war!       |                        
    /       /      `-----------------------'                        
   (   -.  |                               
   |     ) |                               
  (`-.  '--.)                              
   `. )----'                               
From: Tim Bradshaw
Subject: Re: Error checking style question
Date: 
Message-ID: <fbc0f5d1.0406070520.1862c108@posting.google.com>
···@famine.OCF.Berkeley.EDU (Thomas F. Burdick) wrote in message news:<···············@famine.OCF.Berkeley.EDU>...

> 
> What's wrong with both?  I'd try to detect it at macroexpansion time,
> and WARN that you're about to produce junk; then actually ERR(OR) at
> runtime.  I try not to err at macroexpansion time, except when the
> input is complete nonsense -- warnings are good, though.

I think this is good advice.  I'm currently living in Python hell, and
one of the *really* irritating things about the implementation
(CPython) is that the first you hear about things like unbound
variables / undefined functions &c &c is running the program.  Warning
at compile time is only polite.

--tim
From: Svein Ove Aas
Subject: Re: Error checking style question
Date: 
Message-ID: <ca1quv$gne$1@services.kq.no>
Tim Bradshaw wrote:

> ···@famine.OCF.Berkeley.EDU (Thomas F. Burdick) wrote in message
> news:<···············@famine.OCF.Berkeley.EDU>...
> 
>> 
>> What's wrong with both?  I'd try to detect it at macroexpansion time,
>> and WARN that you're about to produce junk; then actually ERR(OR) at
>> runtime.  I try not to err at macroexpansion time, except when the
>> input is complete nonsense -- warnings are good, though.
> 
> I think this is good advice.  I'm currently living in Python hell, and
> one of the *really* irritating things about the implementation
> (CPython) is that the first you hear about things like unbound
> variables / undefined functions &c &c is running the program.  Warning
> at compile time is only polite.
> 
I'll second that, but I'm also of the opinion that you shouldn't actually
error on anything but a catastrophic failure. Insert (error) blocks in
the macro-expansion, maybe.

Few things are more annoying than having the compiler error out on me
(because it would fail at runtime) for a piece of code that doesn't
actually get called at runtime. If I want to fix it, I'll read the
warnings.
From: Gareth McCaughan
Subject: Re: Error checking style question
Date: 
Message-ID: <87ise3j8fb.fsf@g.mccaughan.ntlworld.com>
··········@tfeb.org (Tim Bradshaw) writes:

> I think this is good advice.  I'm currently living in Python hell, and
> one of the *really* irritating things about the implementation
> (CPython) is that the first you hear about things like unbound
> variables / undefined functions &c &c is running the program.  Warning
> at compile time is only polite.

Do you know about PyChecker <http://pychecker.sourceforge.net/>?
It won't help you at all when developing interactively, of course.

-- 
Gareth McCaughan
.sig under construc
From: Dave Roberts
Subject: Re: Error checking style question
Date: 
Message-ID: <7K%wc.17428$Sw.4820@attbi_s51>
Thomas F. Burdick wrote:

> What's wrong with both?  I'd try to detect it at macroexpansion time,
> and WARN that you're about to produce junk; then actually ERR(OR) at
> runtime.  I try not to err at macroexpansion time, except when the
> input is complete nonsense -- warnings are good, though.
> 

Good point.

-- 
Dave Roberts, ·············@re-move.droberts.com
Slowly but surely, the programming world is finding Lisp...
http://www.findinglisp.com/blog