Guest User

Untitled

a guest
Oct 25th, 2018
164
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.59 KB | None | 0 0
  1. #lang racket
  2.  
  3. (require (for-syntax syntax/parse))
  4.  
  5. ;; Macros created with define-syntax are functions from syntax objects to syntax
  6. ;; objects. Syntax objects are basically s-expressions + a bunch of binding
  7. ;; information and metadata.
  8.  
  9. ;; GRAMMAR: (when-let ([id condition-expr] ...) body ...)
  10. ;; Executes body forms if every condition-expr is non-false, and binds the
  11. ;; result of each condition-expr to the corresponding id in the body forms.
  12. (define-syntax (when-let stx)
  13.  
  14. ;; syntax-parse is a DSL that accepts a syntax object and a list of pattern +
  15. ;; expression pairs, grammar is (syntax-parse stx-expr [pattern expr] ...). It
  16. ;; performs a pattern match on the syntax object and evaluates the expression
  17. ;; for the matching pattern.
  18.  
  19. (syntax-parse stx
  20.  
  21. ;; Syntax patterns are s-expressions but can include "syntax classes" on
  22. ;; parts of the expression, written using colons. For example this pattern:
  23. ;;
  24. ;; (foo:id bar:str any)
  25. ;;
  26. ;; ...matches this s-expression:
  27. ;;
  28. ;; (some-id "lorem ipsum" (any (random (s-exp) here)))
  29. ;;
  30. ;; These are some of the syntax classes included by default in syntax/parse:
  31. ;;
  32. ;; id - matches an identifier
  33. ;; str - matches a literal string
  34. ;; expr - matches any s-expression other than a keyword(*)
  35. ;;
  36. ;; (*) - Keywords are kind of like symbols, but they #:look #:like #:this.
  37.  
  38. [(when-let ([id:id condition-expr:expr] ...) body ...)
  39.  
  40. ;; Matched subexpressions are bound as "pattern variables" that can
  41. ;; be used in a (syntax (...)) form to substitute the matched code.
  42. (syntax
  43. (let ([id condition-expr] ...)
  44. (when (and id ...) body ...)))]))
  45.  
  46.  
  47. ;; Assume find-user and find-widget look up a user/widget ID in a database and
  48. ;; return either a user/widget data structure if the row exists, or false if the
  49. ;; ID doesn't match any rows
  50.  
  51. (define (find-user _) #f)
  52. (define (find-widget _) #f)
  53. (define check-user-widget-permissions void)
  54. (define frobnicate-user void)
  55.  
  56. (when-let ([user (find-user "bob@aol.com")]
  57. [widget (find-widget "eW91cmVhY3VyaW91c2ZlbGxvd2FyZW50eW91")])
  58. ;; This code only runs if both user and widget are not false
  59. (check-user-widget-permissions user widget)
  60. (frobnicate-user user widget))
  61.  
  62. ;@------------------------------------------------------------------------------
  63. ;; PART 2 - SYNTAX CLASSES
  64.  
  65. ;; Now lets define some syntax classes to abstract over some common structures
  66. ;; that show up a lot in different macros, and to make when-let handle duplicate
  67. ;; identifiers correctly by raising an error.
  68.  
  69. ;; Syntax classes are used in macros at compile time. So we have to define them
  70. ;; at compile time by wrapping them in a (begin-for-syntax ...) block.
  71. (begin-for-syntax
  72.  
  73. ;; A syntax class matching a single [id expr] binding pair.
  74. (define-syntax-class binding
  75.  
  76. ;; A syntax class's description text is used when generating error messages.
  77. ;; It also serves as lightweight in-source documentation.
  78.  
  79. #:description "binding pair"
  80.  
  81. ;; A syntax class can have attributes, which let you reference sub-parts of
  82. ;; what the class matches. For example, a pattern like pair:binding matched
  83. ;; against the syntax [user (find-user "bob@aol.com")] would make the
  84. ;; following pattern variables available in a (syntax ...) form:
  85. ;;
  86. ;; pair - [user (find-user "bob@aol.com")] (the whole binding pair)
  87. ;; pair.var - user (just the variable name)
  88. ;; pair.rhs - user (the right-hand-side (RHS) expression)
  89.  
  90. #:attributes [var rhs]
  91.  
  92. ;; A (pattern _) form uses the same pattern syntax that syntax-parse uses.
  93. ;; Matched variables are used for the syntax class's attributes. Failing to
  94. ;; use a variable for every attribute causes a compile error when the syntax
  95. ;; class is defined.
  96. (pattern [var:id rhs:expr]))
  97.  
  98. ;; A syntax class matching a series of binding pairs ([id expr] ...) where no
  99. ;; two identifiers can be the same.
  100. (define-syntax-class distinct-bindings
  101. #:description "sequence of unique binding pairs"
  102.  
  103. ;; The 1 part means "with one level of ellipses", because this class binds
  104. ;; multiple pairs, variables, and right-hand-side expressions instead of
  105. ;; only one. Users of this syntax class must use each attribute with the
  106. ;; correct number of ellipses.
  107.  
  108. #:attributes [(pair 1) (var 1) (rhs 1)]
  109.  
  110. (pattern (pair:binding ...)
  111. ;; The #:with clause binds more attributes in addition to whatever
  112. ;; was bound by the pattern. Grammar: #:with pattern expr
  113. #:with (var ...) (syntax (pair.var ...))
  114. #:with (rhs ...) (syntax (pair.rhs ...))
  115.  
  116. ;; The #:fail-when clause evaluates an expression when the syntax
  117. ;; class is used and, if it is not false, fails the pattern match.
  118. ;; The expression can return a syntax object instead of just
  119. ;; returning true; if it does that syntax object is combined with
  120. ;; the message string in the error message created by the failed
  121. ;; pattern match.
  122. #:fail-when (check-duplicate-identifier
  123. (syntax->list (syntax (var ...))))
  124. "duplicate variable name")))
  125.  
  126. ;; Now we can write a more robust when-let macro using our reusable syntax
  127. ;; classes.
  128.  
  129. (define-syntax (when-let2 stx)
  130. (syntax-parse stx
  131. [(when-let2 bindings:distinct-bindings body ...)
  132. (syntax
  133. (let bindings
  134. (when (and bindings.var ...)
  135. body ...)))]))
  136.  
  137. ;; The previous usage example works the same as before
  138.  
  139. (when-let2 ([user (find-user "bob@aol.com")]
  140. [widget (find-widget "eW91cmVhY3VyaW91c2ZlbGxvd2FyZW50eW91")])
  141. (check-user-widget-permissions user widget)
  142. (frobnicate-user user widget))
  143.  
  144. ;; But this example raises a compile error. It's commented out for now; to
  145. ;; uncomment it and see the compile error, delete the #; characters prefixing
  146. ;; the expression. If you're using DrRacket, notice that the compile error
  147. ;; highlights the second "user" identifier in red when it's raised.
  148.  
  149. #;(when-let2 ([user (find-user "bob@aol.com")]
  150. [user (find-user "alice@aol.com")]
  151. [widget (find-widget "eW91cmVhY3VyaW91c2ZlbGxvd2FyZW50eW91")])
  152. (check-user-widget-permissions user widget)
  153. (frobnicate-user user widget))
  154.  
  155. ;; Error message text:
  156. ;;
  157. ;; when-let2: duplicate variable name
  158. ;; parsing context:
  159. ;; while parsing sequence of unique binding pairs in: user
  160. ;;
  161. ;; The message also points to the second "user" identifier via highlighting in
  162. ;; DrRacket or via a file path with line and column numbers in the terminal
  163. ;; console (if this module is compiled from the command line).
Add Comment
Please, Sign In to add comment