@ -1,8 +1,17 @@
#lang racket/base
( require ( for-syntax racket/list racket/base syntax/parse br/syntax racket/syntax syntax/datum syntax/strip-context racket/string ) sugar/define )
( provide ( all-defined-out ) )
( require
( for-syntax racket/list
racket/base
syntax/parse
br/syntax
racket/syntax
syntax/datum
racket/string ) )
( provide ( all-defined-out )
( for-syntax with-shared-id with-calling-site-id ) )
;; everything is prefixed br: whether it needs it or not so it can be stripped by #lang br
( module+ test
( require rackunit ) )
( define-for-syntax ( upcased? str ) ( equal? ( string-upcase str ) str ) )
@ -25,7 +34,7 @@
( define-syntax-parameter shared-syntax ( λ ( stx ) ( error ' shared-syntax-not-parameterized ) ) ) )
( define-syntax ( br: define-cases stx )
( define-syntax ( define-cases stx )
( define-syntax-class syntaxed-id
#:literals ( syntax )
#:description " id in syntaxed form "
@ -83,41 +92,11 @@
( module+ test
( require rackunit )
( define foo-val ' got-foo-val )
( define ( foo-func ) ' got-foo-func )
( br:define-cases #' op
[ ( _ " + " ) #' ' got-plus ]
[ ( _ _ARG ) #' ' got-something-else ]
[ #' ( _ ) #' ( foo-func ) ]
[ #' _ #' foo-val ] )
( check-equal? ( op " + " ) ' got-plus )
( check-equal? ( op 42 ) ' got-something-else )
( check-equal? ( op ) ' got-foo-func )
( check-equal? op ' got-foo-val )
( br:define-cases #' elseop
[ #' ( _ _arg ) #' ' got-arg ]
[ else #' ' got-else ] )
( check-equal? ( elseop " + " ) ' got-arg )
( check-equal? ( elseop " + " 42 ) ' got-else )
( check-exn exn:fail:syntax? ( λ _ ( expand-once #' ( br:define-cases #' badelseop
[ else #' ' got-else ]
[ #' ( _ _arg ) #' ' got-arg ] ) ) ) )
( br:define-cases f
[ ( _ arg ) ( add1 arg ) ]
[ ( _ arg1 arg2 ) ( + arg1 arg2 ) ] )
( define-cases f
[ ( _ arg ) ( add1 arg ) ]
[ ( _ arg1 arg2 ) ( + arg1 arg2 ) ] )
( check-equal? ( f 42 ) 43 )
( check-equal? ( f 42 5 ) 47 )
( check-exn exn:fail:syntax? ( λ _ ( expand-once #' ( br:define-cases ( #' times stx stx2 ) #' * ) ) ) ) )
( check-equal? ( f 42 5 ) 47 ) )
( define-syntax ( br:define stx )
@ -139,13 +118,13 @@
;; syntax
[ ( _ ( syntax ( id . pat-args ) ) . body ) ; (define #'(foo arg) #'(+ arg arg))
#' ( br: define-cases ( syntax id ) [ ( syntax ( _ . pat-args ) ) ( begin . body ) ] ) ]
#' ( define-cases ( syntax id ) [ ( syntax ( _ . pat-args ) ) ( begin . body ) ] ) ]
[ ( _ sid:syntaxed-id sid2:syntaxed-id ) ; (define #'f1 #'f2)
#' ( define-syntax sid.name ( make-rename-transformer sid2 ) ) ]
[ ( _ ( syntax id ) ( syntax thing ) ) ; (define #'f1 #'42)
#' ( br: define-cases ( syntax id ) [ #' _ ( syntax thing ) ] ) ]
#' ( define-cases ( syntax id ) [ #' _ ( syntax thing ) ] ) ]
[ ( _ ( sid:syntaxed-id stx-arg ... ) . exprs ) ; (define (#'f1 stx) expr ...)
( raise-syntax-error ' define " definition of a syntax transformer must use lambda notation, because otherwise it's too easy to confuse the compile-time shape and the run-time shape " ( syntax->datum #' sid.name ) ) ]
@ -158,78 +137,33 @@
[ ( _ . args ) #' ( define . args ) ] ) )
( module+ test
( require rackunit )
( br:define #' plus ( λ ( stx ) #' + ) )
( check-equal? ( plus 42 ) + )
( br:define #' plusser #' plus )
( check-equal? ( plusser 42 ) + )
( check-equal? plusser + )
( br:define #' ( times [ nested _ARG ] ) #' ( * _ARG _ARG ) )
( check-equal? ( times [ nested 10 ] ) 100 )
( br:define #' timeser #' times )
( check-equal? ( timeser [ nested 12 ] ) 144 )
( br:define #' fortytwo #' 42 )
( check-equal? fortytwo 42 )
( check-equal? ( let ( )
( br:define #' ( foo _X )
( with-syntax ( [ zam + ] )
#' ( zam _X _X ) ) ) ( foo 42 ) ) 84 )
( check-exn exn:fail:syntax? ( λ _ ( expand-once #' ( br:define ( #' times stx stx2 ) #' * ) ) ) )
( begin
( br:define #' ( redefine _id ) #' ( define _id 42 ) )
( redefine zoombar )
( check-equal? zoombar 42 ) )
;; use caller-stx parameter to introduce identifier unhygienically
( br:define #' ( zam _arg1 _arg2 _arg3 )
( with-syntax ( [ dz ( datum->syntax caller-stx ' dirty-zam ) ] )
#` ( define dz ' got-dirty-zam ) ) )
( zam ' this ' that 42 )
( check-equal? dirty-zam ' got-dirty-zam ) )
( define-syntax-rule ( br:debug-define ( syntax ( id . pat-args ) ) body-exp )
( br:define #' ( id . pat-args )
#` ( begin
( for-each displayln
( list
( format " input pattern = #'~a " ' #, ' ( id . pat-args ) )
( format " output pattern = #'~a " ( cadr ' #, ' body-exp ) )
( format " invoked as = ~a " ( syntax->datum #' ( id . pat-args ) ) )
( format " expanded as = ~a " ' #, ( syntax->datum body-exp ) )
( format " evaluated as = ~a " #, body-exp ) ) )
#, body-exp ) ) )
( define-syntax-rule ( debug-define-macro ( id . pat-args ) body-exp )
( define-macro ( id . pat-args )
#` ( begin
( for-each displayln
( list
( format " input pattern = #'~a " ' #, ' ( id . pat-args ) )
( format " output pattern = #'~a " ( cadr ' #, ' body-exp ) )
( format " invoked as = ~a " ( syntax->datum #' ( id . pat-args ) ) )
( format " expanded as = ~a " ' #, ( syntax->datum body-exp ) )
( format " evaluated as = ~a " #, body-exp ) ) )
#, body-exp ) ) )
( module+ test
( require rackunit racket/port )
( parameterize ( [ current-output-port ( open-output-nowhere ) ] )
( check-equal? ( let ( )
( br: debug-define #' ( foo _X _Y _Z )
#' ( apply + ( list _X _Y _Z ) ) )
( debug-define-macro ( foo _X _Y _Z )
#' ( apply + ( list _X _Y _Z ) ) )
( foo 1 2 3 ) ) 6 )
( check-equal? ( let ( )
( br: debug-define #' ( foo _X ... ) #' ( apply * ( list _X ... ) ) )
( debug-define-macro ( foo _X ... ) #' ( apply * ( list _X ... ) ) )
( foo 10 11 12 ) ) 1320 ) ) )
( define-syntax-rule ( br:define+provide . args )
( define+provide . args ) )
( define-for-syntax ( expand-macro mac )
( syntax-disarm ( local-expand mac ' expression #f ) #f ) )
( define-syntax ( br:define-inverting stx )
( syntax-case stx ( syntax )
[ ( _ ( syntax ( _id . _pat-args ) ) . _syntaxexprs )
#' ( br:define-cases-inverting ( syntax _id )
[ ( syntax ( _ . _pat-args ) ) . _syntaxexprs ] ) ] ) )
( begin-for-syntax
( begin-for-syntax
( require ( for-syntax racket/base ) )
@ -241,7 +175,7 @@
#' ( datum->syntax caller-stx ( if ( syntax? form )
( syntax-e form )
form ) ) ] ) ) ] ) ) ) )
( provide ( for-syntax with-shared-id with-calling-site-id ) )
( begin-for-syntax
( define-syntax-rule ( with-shared-id ( id ... ) . body )
( with-syntax ( [ id ( shared-syntax ' id ) ] ... )
@ -249,67 +183,102 @@
( define-syntax with-calling-site-id ( make-rename-transformer #' with-shared-id ) ) )
( define-syntax ( define-macro stx )
( define-syntax-class syntaxed-id
#:literals ( syntax )
#:description " id in syntaxed form "
( pattern ( syntax name:id ) ) )
( define-syntax ( br:define-cases-inverting stx )
( syntax-case stx ( syntax )
[ ( _ ( syntax _id ) [ ( syntax _patarg ) . _bodyexprs ] ... )
( with-syntax ( [ LITERALS ( generate-literals #' ( _patarg ... ) ) ] )
#' ( define-syntax ( _id stx )
( syntax-case stx ( )
[ ( _id . rest )
( let ( [ expanded-stx ( with-syntax ( [ expanded-macros ( map expand-macro ( syntax->list #' rest ) ) ] )
#' ( _id . expanded-macros ) ) ] )
( define result
( syntax-case expanded-stx LITERALS
[ _patarg ( syntax-parameterize ( [ caller-stx ( make-rename-transformer #' stx ) ] )
( syntax-parameterize ( [ shared-syntax ( make-shared-syntax-macro caller-stx ) ] )
. _bodyexprs ) ) ] ...
[ else ( raise-syntax-error ' define-cases-inverting ( format " no matching case for syntax pattern ~v " ( syntax->datum stx ) ) ( syntax->datum #' _id ) ) ] ) )
( if ( syntax? result )
result
( datum->syntax #' _id result ) ) ) ] ) ) ) ] ) )
( module+ test
;; an inverting macro expands its arguments.
;; so `foo` does not get `(falsy a) (falsy b) (falsy c)` as arguments,
;; but rather the result of their expansion, namely `((#f a) (#f b) (#f c))`
;; and `tree` does not get `(foo (#f a) (#f b) (#f c))` as its first argument,
;; but rather the result of its expansion, namely (a b c).
( br:define-inverting #' ( tree ( _id ... ) _vals )
#' ( let ( )
( define-values ( _id ... ) _vals )
( list _id ... ) ) )
( br:define-cases-inverting #' foo
[ #' ( _ ( #f _id ) ... ) #' ( _id ... ) ] )
( define-syntax-rule ( falsy id ) ( #f id ) )
( check-equal? ( tree ( foo ( falsy a ) ( falsy b ) ( falsy c ) ) ( values 1 2 3 ) ) ' ( 1 2 3 ) ) )
( define-syntax-class syntaxed-thing
#:literals ( syntax )
#:description " some datum in syntaxed form "
( pattern ( syntax thing:expr ) ) )
( define-syntax ( br:define-macro stx )
( syntax-case stx ( syntax )
( syntax-parse stx
#:literals ( syntax )
[ ( _ id #' other-id ) ; (define-macro id #'other-id)
#' ( br:define #' id #' other-id ) ]
[ ( _ ( id . patargs ) . body )
#' ( br:define #' ( id . patargs ) . body ) ]
[ ( _ id [ pat . patbody ] ... )
#' ( br:define-cases #' id [ pat . patbody ] ... ) ] ) )
#' ( define-cases ( syntax id ) [ pat . patbody ] ... ) ] ) )
( define-syntax ( define-macro-cases stx )
( define-syntax-class syntaxed-id
#:literals ( syntax )
#:description " id in syntaxed form "
( pattern ( syntax name:id ) ) )
( define-syntax ( br:define-macro-cases stx )
( syntax-case stx ( syntax )
( define-syntax-class syntaxed-thing
#:literals ( syntax )
#:description " some datum in syntaxed form "
( pattern ( syntax thing:expr ) ) )
( syntax-parse stx
#:literals ( syntax )
[ ( _ id . body )
#' ( br:define-cases ( syntax id ) . body ) ] ) )
#' ( define-cases ( syntax id ) . body ) ] ) )
( module+ test
( br:define-macro ( add _x ) #' ( + _x _x ) )
;; todo: make these tests work, if they still make sense
#; ( define-macro plus ( λ ( stx ) #' + ) )
#; ( check-equal? ( plus 42 ) + )
#; ( define-macro plusser #' plus )
#; ( check-equal? ( plusser 42 ) + )
#; ( check-equal? plusser + )
( define-macro ( times [ nested ARG ] ) #' ( * ARG ARG ) )
( check-equal? ( times [ nested 10 ] ) 100 )
( define-macro timeser #' times )
( check-equal? ( timeser [ nested 12 ] ) 144 )
( define-macro fortytwo #' 42 )
( check-equal? fortytwo 42 )
( check-equal? ( let ( )
( define-macro ( foo X )
( with-syntax ( [ zam + ] )
#' ( zam X X ) ) ) ( foo 42 ) ) 84 )
( begin
( define-macro ( redefine ID ) #' ( define ID 42 ) )
( redefine zoombar )
( check-equal? zoombar 42 ) )
;; use caller-stx parameter to introduce identifier unhygienically
( define-macro ( zam ARG1 ARG2 ARG3 )
( with-syntax ( [ dz ( datum->syntax caller-stx ' dirty-zam ) ] )
#` ( define dz ' got-dirty-zam ) ) )
( zam ' this ' that 42 )
( check-equal? dirty-zam ' got-dirty-zam )
( define-macro ( add _x ) #' ( + _x _x ) )
( check-equal? ( add 5 ) 10 )
( br:define-macro-cases add-again [ ( _ X ) #' ( + X X ) ] )
( define-macro-cases add-again [ ( _ X ) #' ( + X X ) ] )
( check-equal? ( add-again 5 ) 10 )
( br:define-macro add-3rd [ ( _ X ) #' ( + X X ) ] )
( define-macro add-3rd [ ( _ X ) #' ( + X X ) ] )
( check-equal? ( add-3rd 5 ) 10 )
( br:define-macro add-4th #' add-3rd )
( check-equal? ( add-4th 5 ) 10 ) )
( define-macro add-4th #' add-3rd )
( check-equal? ( add-4th 5 ) 10 )
( define foo-val ' got-foo-val )
( define ( foo-func ) ' got-foo-func )
( define-macro-cases op
[ ( _ " + " ) #' ' got-plus ]
[ ( _ _ARG ) #' ' got-something-else ]
[ ( _ ) #' ( foo-func ) ]
[ _ #' foo-val ] )
( check-equal? ( op " + " ) ' got-plus )
( check-equal? ( op 42 ) ' got-something-else )
( check-equal? ( op ) ' got-foo-func )
( check-equal? op ' got-foo-val )
( define-macro-cases elseop
[ ( _ _arg ) #' ' got-arg ]
[ else #' ' got-else ] )
( check-equal? ( elseop " + " ) ' got-arg )
( check-equal? ( elseop " + " 42 ) ' got-else )
;; todo: fix test, should throw error because `else` clause is out of order
#; ( check-exn exn:fail:syntax? ( λ _ ( expand-once #' ( define-macro-cases badelseop
[ else #' ' got-else ]
[ ( _ _arg ) #' ' got-arg ] ) ) ) ) )