[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Macro hell



Aaargh!  Let's just get this out of the way up front:  Dylan macros are
hell compared to Common Lisp macros.  Every time I try to write a non-trivial
Dylan macro I end up spending inordinate amounts of time and then end up
compromising for some syntax that's not quite what I want but is pretty close,
because it makes the compiler happy.  What am I missing?  Do I have to go
study that horrific BNF?  I sure hope not.

Here's the example that I'm currently struggling with.  If anyone can
show me how to fix it that would be wonderful.  Bonus points for figuring
out how my thinking about macros is broken such that I couldn't figure
it out myself.

I want to be able to define a class, some of whos slots map to columns
in a database table:

define record <account> (<database-record>)
  database slot username :: <string>, column-name: "username";
  database slot password :: <string>, column-name: "password";
  slot foo;  // non-database slot
end;

My basic approach is to have this expand into two other macro
calls, each passing along all the arguments:

define record-class <account> (<database-record>)
  database slot username :: <string>, column-name: "username";
  database slot password :: <string>, column-name: "password";
  slot foo;  // non-database slot
end;

define record-slots <account> (<database-record>)
  database slot username :: <string>, column-name: "username";
  database slot password :: <string>, column-name: "password";
  slot foo;  // non-database slot
end;

"define record-class" then expands to a normal "define class"
form and "define record-slots" expands to a bunch of code for
storing information about the mapping between class slots and 
database columns.  Therefore, record-class-definer needs to strip
out all the stuff unacceptable to class-definer and record-slots-definer
needs to strip out anything it doesn't need.

Is that basic approach reasonable?  Better way?

I actually managed to get the whole thing working except
for one little problem: the comma after the slot name and type.
For some reason i'm unable to use my little "keyvals" trick
(see *** comment in macros below) to make this comma optional
in record-slots-definer.  I get a warning about invalid syntax
in the "slot-name" expansion.
It seems to work ok in record-class-definer, however.

Any suggestions?

Thanks.
-Carl


define macro record-definer
  { define ?modifiers:* record ?record-name:name (?superclasses)
        ?slot-specs:*
    end }
  =>
  { define ?modifiers record-class ?record-name (?superclasses)
      ?slot-specs
    end;
    define record-slots ?record-name (?superclasses)
      ?slot-specs
    end }
superclasses:
  { } => { }
  { ?superclass:expression, ... } => { ?superclass, ... }
end;

// This basically strips out everything that "define class" doesn't handle.
//
define macro record-class-definer
  { define ?modifiers:* record-class ?record-name:name (?superclasses:*)
        ?slot-specs
    end }
  =>
  { define ?modifiers class ?record-name (?superclasses) ?slot-specs end }
slot-specs:
  { } => { }
  { ?slot-spec ; ... } => { ?slot-spec ; ... }
slot-spec:
  { ?modifiers slot ?slot-name:variable ?keyvals }
  =>
  { ?modifiers slot ?slot-name ?keyvals }
modifiers:
  { } => { }
  { database ... } => { ... }                        // strip
  { ?modifier:name ... } => { ?modifier ... }
keyvals:
  { } => { }
  { , ?keys-and-vals } => { ?keys-and-vals }         // remove leading comma
keys-and-vals:
  { } => { }
  { column-name:  ?foo:expression, ... } => { ... }  // strip
  { column-index: ?foo:expression, ... } => { ... }  // strip
  { ?key:token ?foo:expression, ... } => { ?key ?foo, ... }
end macro record-class-definer;


define macro record-slots-definer
  { define ?modifiers:* record-slots ?record-name:name (?superclasses:*)
        ?slot-specs
    end }
  =>
  { begin
      // This needs to be referenced from auxiliary rule sets below.
      let _record-name = ?record-name;
      // Associate all the slot descriptors of the superclasses with
      // this record class.
      for (_class in vector(?superclasses))
        for (_desc in slot-descriptors(_class))
          add-slot-descriptor(_record-name, _desc);
        end;
      end;
      ?slot-specs
    end }
slot-specs:
  { } => { }
  { ?slot-spec ; ... } => { ?slot-spec ; ... }
slot-spec:
  // ********* had to insert comma below, which makes it required.
  { ?modifiers slot ?slot-name , ?keys-and-vals }
  =>
  { begin
      let _database? = ?modifiers;
      let (_name, _type) = ?slot-name;
      let _args = vector(?keys-and-vals);
      add-slot-descriptor(_record-name, _name, _type, _database, _args);
    end }
modifiers:
  { } => { #f } // didn't find the 'database' modifier
  { database ... } => { #t }
  { ?modifier:name ... } => { ?modifier ... }
slot-name:
  { ?:name } => { values(?name, <object>) }
  { ?:name :: ?type:expression } => { values(?name, ?type) }
/*
keyvals:
  { } => { }
  { , ?keys-and-vals } => { ?keys-and-vals }         // remove leading comma
*/
keys-and-vals:
  { } => { }
  { column-name:  ?a:expression, ... } => { column-name: ?a, ... }
  { column-index: ?b:expression, ... } => { column-index: ?b, ... }
  { init-keyword: ?c:expression, ... } => { init-keyword: ?c, ... }
  { ?key:token ?d:expression, ... } => { ... }  // remove everything else
end macro record-slots-definer;