Some Nu Control Operators and Macros

Wednesday, 05 Sep 2007

Nu has had while and until operators for a couple of months now, but I had put off trying to support break and continue. This morning I realized that I could use an existing Objective-C pattern to add them easily (one of many reasons to take regular showers). The key was to use exceptions.

The new Nu break and continue operators simply throw special subclasses of NSException. The while and until operators evaluate their subexpressions in an Objective-C try block, and that try block has separate catch blocks for the two exception types plus one more in case any other exceptions are thrown. Here is the code:

@implementation Nu_while
- (id) callWithArguments:(id)args context:(NSMutableDictionary *)context
{
    id result = __null;
    id test = [[args car] evalWithContext:context];
    while (valueIsTrue(test)) {
        @try
        {
            id expressions = [args cdr];
            while (expressions && (expressions != __null)) {
                result = [[expressions car] evalWithContext:context];
                expressions = [expressions cdr];
            }
        }
        @catch (NuBreakException *exception) {
            break;
        }
        @catch (NuContinueException *exception) {
            // do nothing, just continue with the next loop iteration
        }
        @catch (id exception) {
            @throw(exception);
        }
        test = [[args car] evalWithContext:context];
    }
    return result;
}

@end

After writing a couple of basic tests for the new break and continue operators, I decided to have some (more) fun and try to use them in a loop macro. After fooling around a bit with some needlessly elaborate attempts, I realized that there was a really simple way to create what I wanted:

(macro loop (eval (append '(while t) margs)))

This takes the arguments to the loop macro and attaches them to the end of another list. The result is a call to the while operator, which is then evaluated. It seems deceptively simple. Can anyone do this with Ruby? Kudos await anyone who can show the necessary ParseTree gymnastics in the comments or a related blog post.

Here’s a test that pulls all this together.

 (imethod (id) testLoopMacro is
      ;; a simple macro defining an unending loop
      (macro loop (eval (append '(while t) margs)))
      ;; a macro that decrements a named value
      (macro decrement 
        (set (unquote (margs car)) (- (unquote (margs car)) 1)))
      ;; a macro that increments a named value
      (macro increment 
        (set (unquote (margs car)) (+ (unquote (margs car)) 1)))
      ;; run the loop, breaking out after 5 iterations
      (set count 0)
      (set x 10)
      (loop
           (decrement x)
           (increment count)
           (if (eq x 5) (break)))
      (assert_equal 5 count)
      ;; run the loop, breaking out after 10 iterations
      ;; but only counting until the loop counter (x) drops below 5
      (set count 0)
      (set x 10)
      (loop
           (decrement x)
           (if (eq x 0) (break))
           (if (< x 5) (continue))
           (increment count))
      (assert_equal 5 count))

By the way, this is so fun and instructive that I’d suggest that any programmer give it a try sometime. Why worry about going to the right school when there’s so much that you can now teach yourself by just trying?

Comments (0) post a reply