Skip to content

Commit

Permalink
add fnarg
Browse files Browse the repository at this point in the history
  • Loading branch information
bennn committed Oct 27, 2021
1 parent a133b11 commit 0334f34
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 0 deletions.
58 changes: 58 additions & 0 deletions fnarg/fnarg-test.rkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#lang racket/base
(module+ test
(require rackunit syntax/macro-testing
(for-syntax racket/base syntax/parse syntax/transformer syntax-parse-example/fnarg/fnarg))

;; macro for defining functions that cannot mutate their parameters and do
;; not accept keyword arguments
(define-syntax (define/immutable-parameter stx)
(syntax-parse stx
[(_ (name:id (~var arg (fnarg #:keyword? #f)) ...) body:expr ...+)
;; The `~@` form _splices_ the keyword at (A), if it exists, into the
;; function header.
;; The `~?` form at (B) chooses the first form if both the arg.fresh-var
;; and arg.default attributes have values. Otherwise, `~?` chooses the
;; second form.
#'(define (name (~@ (~? arg.keyword) ;; (A)
(~? [arg.fresh-var arg.default] ;; (B)
arg.fresh-var))
...)
;; Disable set! for arg
(define-syntax arg.name
(make-variable-like-transformer #'arg.fresh-var #f))
...
body ...)]))

(check-exn #rx"set!: cannot mutate identifier"
(lambda ()
(convert-compile-time-error
(let ()
(define/immutable-parameter (bad-fn x)
(set! x 'oops)
(void))
(void)))))

(check-exn #rx"keyword argument is not allowed"
(lambda ()
(convert-compile-time-error
(define/immutable-parameter (has-kw a #:cache cache?)
(void)))))

(test-case "fib/cache"
(define fib-cache (make-hash))
(define/immutable-parameter (fib n [cache #f])
(cond
[(<= n 1) n]
[(and cache (hash-has-key? cache n))
(hash-ref cache n)]
[else
(define result
(+ (fib (- n 1) cache)
(fib (- n 2) cache)))
(when cache
(hash-set! cache n result))
result]))

(check-equal? (fib 8) 21)
(check-equal? (fib 40 fib-cache) 102334155))
)
28 changes: 28 additions & 0 deletions fnarg/fnarg.rkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#lang racket/base

(provide fnarg)

(require racket/syntax syntax/parse)

;; Use _splicing_ to parse a sequence of elements for keyword args.
(define-splicing-syntax-class (fnarg [allow-optional? #t]
#:keyword? [allow-keyword? #t])
#:attributes (name fresh-var keyword default)
#:commit
(pattern name:id
#:attr keyword #f
#:attr default #f
#:with fresh-var (generate-temporary #'name))
(pattern [name:id default:expr]
#:fail-unless allow-optional? "optional argument is not allowed"
#:attr keyword #f
#:with fresh-var (generate-temporary #'name))
;; The ~seq pattern describes a _sequence_ of elements
(pattern (~seq keyword:keyword name:id)
#:fail-unless allow-keyword? "keyword argument is not allowed"
#:attr default #f
#:with fresh-var (generate-temporary #'name))
(pattern (~seq keyword:keyword [name:id default:expr])
#:fail-unless allow-optional? "optional argument is not allowed"
#:fail-unless allow-keyword? "keyword argument is not allowed"
#:with fresh-var (generate-temporary #'name)))
31 changes: 31 additions & 0 deletions fnarg/fnarg.scrbl
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#lang syntax-parse-example
@require[
(for-label racket/base syntax/parse syntax-parse-example/fnarg/fnarg)]

@title{Function Parameter Syntax Class}
@stxbee2021["shhyou" 23]

@; =============================================================================

@defmodule[syntax-parse-example/fnarg/fnarg]{}

Syntax classes offer a powerful mechanism for abstracting over classes of
conceptually related patterns. Moreover, syntax classes can be parameterized,
with @racket[expr/c] being one prominent example.

In this example, we define a syntax class that roughly captures the grammar of
formal parameters in function headers. It parses the name of the formal
parameter, the default expressions of optional arguments and the keywords of
keyworded arguments.

@defidform[fnarg]{
The @racket[fnarg] syntax class matches the basic argument patterns that
can appear in function headers: mandatory, optional, and keyword arguments.
The class optionally accepts two parameters to toggle whether optional and/or
keyword arguments are allowed.

Refer to the source file @tt{fnarg-test.rkt} for an example use.

@racketfile{fnarg.rkt}

}
1 change: 1 addition & 0 deletions index.scrbl
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@
@include-example{flaggable-app}
@include-example{js-dict}
@include-example{define-freevar}
@include-example{fnarg}

0 comments on commit 0334f34

Please sign in to comment.