macrosschemeguilesyntax-rules

Guile `syntax-rules`: Misplaced Ellipsis in Form; How to Write this Macro with Two Ellipses?


I'm trying to, more or less, recreate a let construct via syntax-rules but it seems to be tripping on the use of two ellipses.

I tried writting it out as so:

(define-syntax if-let
  (syntax-rules ()
    [(_ (([binding        value]  ...)
         ([binding2 funct value2])
         ([binding3       value3] ...)) then else) (let ([binding  value]  ...
                                                         [binding2 value2]
                                                         [binding3 value3] ...)
                                                     (if (funct binding2) then else))]))

I figured the distinct pattern in the middle of [binding2 funct value2] would be able to provide a clear division, in terms of pattern, of when the first pattern ended and when the second one would begin but I keep getting back the error in the title.

The intended result is to be able to do something like

(if-let ([var1                  1]
         [diff null? '(1 2 3 4 5)]
         [var2                  2])
    var1
  var2)

and get back 2 but being able to have as many vars before and after diff as remotely desired so the order of the variables used, ultimately, doesn't matter.

Am I missing something obvious? And is this pattern feasibly able to be done with hygenic macros? Thanks for any help!


Solution

  • This is possible with a helper macro, to do the recursion necessary for finding the funct things in the middle.

    (if-let-helper processed-bindings processed-conditions input-binding-conditions then else)

    As it recurs, it transfers the information in input-binding-conditions into the processed-bindings and processed-conditions, stopping when the input-binding-conditions is empty.

    (define-syntax if-let-helper
      (syntax-rules ()
        [(_ ([bnd val] ...) (cnd ...) () then else)
         (let ([bnd val] ...)
           (if (and cnd ...) then else))]
        [(_ ([bnd val] ...) (cnd ...) ([binding value] . rest) then else)
         (if-let-helper ([bnd val] ... [binding value]) (cnd ...) rest then else)]
        [(_ ([bnd val] ...) (cnd ...) ([binding funct value] . rest) then else)
         (if-let-helper ([bnd val] ... [binding value]) (cnd ... (funct binding))
                        rest then else)]))
    
    (define-syntax if-let
      (syntax-rules ()
        [(_ (binding-funct-value ...) then else)
         (if-let-helper () () (binding-funct-value ...) then else)]))
    

    Using it:

    > (if-let ([var1                  1]
               [diff null? '(1 2 3 4 5)]
               [var2                  2])
        var1
        var2)
    2
    

    To explain this, I will step through how this example processes each clause. It initially turns into this if-let-helper call:

    (if-let-helper
      ()
      ()
      ([var1                  1]
       [diff null? '(1 2 3 4 5)]
       [var2                  2])
      var1
      var2)
    

    The first two lists start empty because it hasn't processed anything yet.

    (if-let-helper
      ([var1                  1])
      ()
      ([diff null? '(1 2 3 4 5)]
       [var2                  2])
      var1
      var2)
    

    At this point it has processed the first clause and added a binding-value pair to the first "processed-bindings" list. However, there was no funct in the first clause, so it didn't add a condition to the second "processed-conditions" list.

    (if-let-helper
      ([var1                  1]
       [diff       '(1 2 3 4 5)])
      ((null? diff))
      ([var2                  2])
      var1
      var2)
    

    At this point it has processed the first two clauses, and added a (null? diff) condition to the second list because it saw the funct there in the second clause.

    (if-let-helper
      ([var1                  1]
       [diff       '(1 2 3 4 5)]
       [var2                  2])
      ((null? diff))
      ()
      var1
      var2)
    

    It this point it has processed all three clauses, so it hits the base case and transforms into the final let and if:

    (let ([var1                  1]
          [diff       '(1 2 3 4 5)]
          [var2                  2])
      (if (and (null? diff))
          var1
          var2))