Repetition operator #6

Merged
mbutterick merged 11 commits from repetition-operator into master 7 years ago

@ -231,7 +231,7 @@ We write a @tt{brag} program as an BNF grammar, where patterns can be:
@itemize[ @itemize[
@item{the names of other rules (e.g. @racket[chunk])} @item{the names of other rules (e.g. @racket[chunk])}
@item{literal and symbolic token names (e.g. @racket[";"], @racket[INTEGER])} @item{literal and symbolic token names (e.g. @racket[";"], @racket[INTEGER])}
@item{quantified patterns (e.g. @litchar{+} to represent one-or-more repetitions)} @item{quantified patterns (e.g. @litchar{+} to represent one or more repetitions)}
] ]
The result of a @tt{brag} program is a module with a @racket[parse] function The result of a @tt{brag} program is a module with a @racket[parse] function
that can parse tokens and produce a syntax object as a result. that can parse tokens and produce a syntax object as a result.
@ -661,35 +661,41 @@ A program in the @tt{brag} language consists of the language line
@litchar{#lang brag}, followed by a collection of @tech{rule}s and @litchar{#lang brag}, followed by a collection of @tech{rule}s and
@tech{line comment}s. @tech{line comment}s.
A @deftech{rule} is a sequence consisting of: a @tech{rule identifier}, a colon A @deftech{rule} is a sequence consisting of: a @tech{rule identifier}, a separator (either @litchar{":"} or @litchar{"::="}), and a @tech{pattern}.
@litchar{":"}, and a @tech{pattern}.
A @deftech{rule identifier} is an @tech{identifier} that is not in upper case. A @deftech{rule identifier} is an @tech{identifier} that is not in upper case.
A @deftech{symbolic token identifier} is an @tech{identifier} that is in upper case. A @deftech{symbolic token identifier} is an @tech{identifier} that is in upper case.
An @deftech{identifier} is a character sequence of letters, numbers, and An @deftech{identifier} is a sequence of letters, numbers, or
characters in @racket["-.!$%&/<=>?^_~@"]. It must not contain characters in the set @racket["-.!$%&/<=>?^_~@"]. It must not contain
@litchar{*} or @litchar{+}, as those characters are used to denote @litchar{*}, @litchar{+}, or @litchar|{{}| and @litchar|{}}|, as those characters are used to denote quantification.
quantification.
A @deftech{pattern} is one of the following: A @deftech{pattern} is one of the following:
@itemize[ @itemize[
@item{an implicit sequence of @tech{pattern}s separated by whitespace} @item{an implicit sequence of @tech{pattern}s separated by whitespace}
@item{a terminal: either a literal string or a @tech{symbolic token identifier}. @item{a @deftech{terminal}: either a literal string or a @tech{symbolic token identifier}.
When used in a pattern, both these terminals will match the same set of inputs. A literal string can match the string itself, or a @racket[token] structure whose type field contains that string (or its symbol form). So @racket["FOO"] would match @racket["FOO"], @racket[(token "FOO" "bar")], or @racket[(token 'FOO "bar")]. A symbolic token identifier can also match the string version of the identifier, or a @racket[token] whose type field is the symbol or string form of the identifier. So @racket[FOO] would also match @racket["FOO"], @racket[(token 'FOO "bar")], or @racket[(token "FOO" "bar")]. (In every case, the value of a token, like @racket["bar"], can be anything, and may or may not be the same as its type.) When used in a pattern, both kinds of terminals will match the same set of inputs.
Because their underlying meanings are the same, the symbolic token identifier ends up being a notational convenience for readability inside a grammar pattern. Typically, the literal string @racket["FOO"] is used to connote ``match the string @racket["FOO"] exactly'' and the symbolic token identifier @racket[FOO] specially connotes ``match any token of type @racket['FOO]''. A literal string can match the string itself, or a @racket[token] structure whose type field contains that string (or its symbol form). So @racket["FOO"] in a rule pattern would match the tokens @racket["FOO"], @racket[(token "FOO" "bar")], or @racket[(token 'FOO "bar")].
A symbolic token identifier can also match the string version of the identifier, or a @racket[token] whose type field is the symbol or string form of the identifier. So @racket[FOO] in a rule pattern would @emph{also} match the tokens @racket["FOO"], @racket[(token 'FOO "bar")], or @racket[(token "FOO" "bar")]. (In every case, the value of a token, like @racket["bar"], can be anything, and may or may not be the same as its type.)
Because their underlying meanings are the same, the symbolic token identifier ends up being a notational convenience for readability inside a rule pattern. Typically, the literal string @racket["FOO"] is used to connote ``match the string @racket["FOO"] exactly'' and the symbolic token identifier @racket[FOO] specially connotes ``match a token of type @racket['FOO]''.
You @bold{cannot} use the literal string @racket["error"] as a terminal in a grammar, because it's reserved for @tt{brag}. You can, however, adjust your lexer to package it inside a token structure — say, @racket[(token 'ERROR "error")] — and then use the symbolic token identifier @racket[ERROR] in the grammar to match this token structure. You @bold{cannot} use the literal string @racket["error"] as a terminal in a grammar, because it's reserved for @tt{brag}. You can, however, adjust your lexer to package it inside a token structure — say, @racket[(token 'ERROR "error")] — and then use the symbolic token identifier @racket[ERROR] in the grammar to match this token structure.
} }
@item{a @tech{rule identifier}} @item{a @tech{rule identifier}}
@item{a @deftech{choice pattern}: a sequence of @tech{pattern}s delimited with @litchar{|} characters.} @item{a @deftech{choice pattern}: a sequence of @tech{pattern}s delimited with @litchar{|} characters.}
@item{a @deftech{quantifed pattern}: a @tech{pattern} followed by either @litchar{*} (``zero or more'') or @litchar{+} (``one or more'')}
@item{a @deftech{quantified pattern}: a @tech{pattern} followed by either @litchar{*} (``zero or more'') or @litchar{+} (``one or more''). Quantification can also be denoted by integers within curly brackets. So @litchar|{{2}}| means ``exactly 2''; @litchar|{{2,5}}| means ``between 2 and 5, inclusive''; @litchar|{{2,}}| means ``2 or more''; and @litchar|{{,5}}| means ``up to 5''.}
@item{an @deftech{optional pattern}: a @tech{pattern} surrounded by @litchar{[} and @litchar{]}} @item{an @deftech{optional pattern}: a @tech{pattern} surrounded by @litchar{[} and @litchar{]}}
@item{an explicit sequence: a @tech{pattern} surrounded by @litchar{(} and @litchar{)}}] @item{an explicit sequence: a @tech{pattern} surrounded by @litchar{(} and @litchar{)}}]
A @deftech{line comment} begins with either @litchar{#} or @litchar{;} and A @deftech{line comment} begins with either @litchar{#} or @litchar{;} and
@ -931,7 +937,7 @@ bindings. The most important of these is @racket[parse]:
@item{For terminals, the value of the token.} @item{For terminals, the value of the token.}
@item{For @tech{rule identifier}s: the associated parse value for the rule.} @item{For @tech{rule identifier}s: the associated parse value for the rule.}
@item{For @tech{choice pattern}s: the associated parse value for one of the matching subpatterns.} @item{For @tech{choice pattern}s: the associated parse value for one of the matching subpatterns.}
@item{For @tech{quantifed pattern}s and @tech{optional pattern}s: the corresponding values, spliced into the structure.} @item{For @tech{quantified pattern}s and @tech{optional pattern}s: the corresponding values, spliced into the structure.}
] ]
Consequently, it's only the presence of @tech{rule identifier}s in a rule's Consequently, it's only the presence of @tech{rule identifier}s in a rule's
@ -1090,44 +1096,45 @@ In addition to the exports shown below, the @racketmodname[brag/support] module
@defform[(:* re ...)]{ @defform[(:* re ...)]{
Repetition of @racket[re] sequence 0 or more times.} 0 or more occurrences of any @racket[re] pattern.}
@defform[(:+ re ...)]{ @defform[(:+ re ...)]{
Repetition of @racket[re] sequence 1 or more times.} 1 or more occurrences of any @racket[re] pattern.}
@defform[(:? re ...)]{ @defform[(:? re ...)]{
Zero or one occurrence of @racket[re] sequence.} 0 or 1 occurrence of any @racket[re] pattern.}
@defform[(:= n re ...)]{ @defform[(:= n re ...)]{
Exactly @racket[n] occurrences of @racket[re] sequence, where Exactly @racket[n] occurrences of any @racket[re] pattern, where
@racket[n] must be a literal exact, non-negative number.} @racket[n] must be a literal exact, non-negative number.}
@defform[(:>= n re ...)]{ @defform[(:>= n re ...)]{
At least @racket[n] occurrences of @racket[re] sequence, where At least @racket[n] occurrences of any @racket[re] pattern, where
@racket[n] must be a literal exact, non-negative number.} @racket[n] must be a literal exact, non-negative number.}
@defform[(:** n m re ...)]{ @defform[(:** n m re ...)]{
Between @racket[n] and @racket[m] (inclusive) occurrences of Between @racket[n] and @racket[m] (inclusive) occurrences of
@racket[re] sequence, where @racket[n] must be a literal exact, any @racket[re] pattern, where @racket[n] must be a literal exact,
non-negative number, and @racket[m] must be literally either non-negative number, and @racket[m] must be literally either
@racket[#f], @racket[+inf.0], or an exact, non-negative number; a @racket[#f], @racket[+inf.0], or an exact, non-negative number; a
@racket[#f] value for @racket[m] is the same as @racket[+inf.0].} @racket[#f] value for @racket[m] is the same as @racket[+inf.0].}
@defform[(:or re ...)]{ @defform[(:or re ...)]{
Same as @racket[(union re ...)].} Same as @racket[(:union re ...)].}
@deftogether[( @deftogether[(
@defform[(:: re ...)] @defform[(:: re ...)]
@defform[(:seq re ...)] @defform[(:seq re ...)]
)]{ )]{
Both forms concatenate the @racket[re]s.} Both forms concatenate the @racket[re]s into a single, indivisible pattern.
In other words, this matches @emph{all} the @racket[re]s in order, whereas @racket[(:union re ...)] matches @emph{any} of the @racket[re]s.}
@defform[(:& re ...)]{ @defform[(:& re ...)]{

@ -301,7 +301,7 @@
[explicit explicit]) [explicit explicit])
([v (in-list (syntax->list #'(vals ...)))]) ([v (in-list (syntax->list #'(vals ...)))])
(loop v implicit explicit))] (loop v implicit explicit))]
[(repeat min val) [(repeat min max val)
(loop #'val implicit explicit)] (loop #'val implicit explicit)]
[(maybe val) [(maybe val)
(loop #'val implicit explicit)] (loop #'val implicit explicit)]
@ -379,7 +379,7 @@
(for/fold ([acc acc]) (for/fold ([acc acc])
([v (in-list (syntax->list #'(vals ...)))]) ([v (in-list (syntax->list #'(vals ...)))])
(loop v acc))] (loop v acc))]
[(repeat min val) [(repeat min max val)
(loop #'val acc)] (loop #'val acc)]
[(maybe val) [(maybe val)
(loop #'val acc)] (loop #'val acc)]
@ -425,7 +425,7 @@
(define a-child (process-pattern v)) (define a-child (process-pattern v))
(sat:add-child! an-or-node a-child)) (sat:add-child! an-or-node a-child))
an-or-node)] an-or-node)]
[(repeat min val) [(repeat min max val)
(syntax-case #'min () (syntax-case #'min ()
[0 [0
(make-leaf)] (make-leaf)]

@ -1,7 +1,7 @@
#lang racket/base #lang racket/base
(require brag/rules/stx-types (require brag/rules/stx-types
racket/list
(for-syntax racket/base)) (for-syntax racket/base))
(provide flatten-rule (provide flatten-rule
flatten-rules flatten-rules
prim-rule) prim-rule)
@ -68,67 +68,75 @@
(values (apply append (reverse rules)) (values (apply append (reverse rules))
(apply append (reverse patterns)))) (apply append (reverse patterns))))
(with-syntax ([head (if inferred? #'inferred-prim-rule #'prim-rule)] (with-syntax ([HEAD (if inferred? #'inferred-prim-rule #'prim-rule)]
[origin (syntax-case a-rule (rule) [(rule name (pat-head rest ...)) #'pat-head])]) [ORIGIN (syntax-case a-rule (rule) [(rule name (pat-head rest ...)) #'pat-head])])
(syntax-case a-rule (rule) (syntax-case a-rule (rule)
[(rule name pat) [(rule NAME PAT)
(syntax-case #'pat (id inferred-id lit token choice repeat maybe seq) (syntax-case #'PAT (id inferred-id lit token choice repeat maybe seq)
;; The primitive types stay as they are: ;; The primitive types stay as they are:
[(id val) [(id val)
(list #'(head origin name [pat]))] (list #'(HEAD ORIGIN NAME [PAT]))]
[(inferred-id val reason) [(inferred-id val reason)
(list #'(head origin name [pat]))] (list #'(HEAD ORIGIN NAME [PAT]))]
[(lit val) [(lit val)
(list #'(head origin name [pat]))] (list #'(HEAD ORIGIN NAME [PAT]))]
[(token val) [(token val)
(list #'(head origin name [pat]))] (list #'(HEAD ORIGIN NAME [PAT]))]
;; Everything else might need lifting: ;; Everything else might need lifting:
[(choice sub-pat ...) [(choice SUB-PAT ...)
(begin (begin
(define-values (inferred-ruless/rev new-sub-patss/rev) (define-values (inferred-ruless/rev new-sub-patss/rev)
(for/fold ([rs '()] [ps '()]) (for/fold ([rs '()] [ps '()])
([p (syntax->list #'(sub-pat ...))]) ([p (syntax->list #'(SUB-PAT ...))])
(let-values ([(new-r new-p) (let-values ([(new-r new-p)
(lift-nonprimitive-pattern p)]) (lift-nonprimitive-pattern p)])
(values (cons new-r rs) (cons new-p ps))))) (values (cons new-r rs) (cons new-p ps)))))
(with-syntax ([((sub-pat ...) ...) (reverse new-sub-patss/rev)]) (with-syntax ([((SUB-PAT ...) ...) (reverse new-sub-patss/rev)])
(append (list #'(head origin name [sub-pat ...] ...)) (append (list #'(HEAD ORIGIN NAME [SUB-PAT ...] ...))
(apply append (reverse inferred-ruless/rev)))))] (apply append (reverse inferred-ruless/rev)))))]
[(repeat min sub-pat) [(repeat MIN #f SUB-PAT)
;; indefinite repeat
(begin (begin
(define-values (inferred-rules new-sub-pats) (define-values (inferred-rules new-sub-pats)
(lift-nonprimitive-pattern #'sub-pat)) (lift-nonprimitive-pattern #'SUB-PAT))
(with-syntax ([(sub-pat ...) new-sub-pats]) (with-syntax ([(SUB-PAT ...) new-sub-pats]
(cons (cond [(= (syntax-e #'min) 0) [MIN-REPEAT-SUB-PATS (apply append (make-list (syntax-e #'MIN) new-sub-pats))])
#`(head origin name (cons #`(HEAD ORIGIN NAME
[(inferred-id name repeat) sub-pat ...] [(inferred-id NAME repeat) SUB-PAT ...]
[])] MIN-REPEAT-SUB-PATS)
[(= (syntax-e #'min) 1)
#`(head origin name
[(inferred-id name repeat) sub-pat ...]
[sub-pat ...])])
inferred-rules)))] inferred-rules)))]
[(maybe sub-pat) [(repeat MIN MAX SUB-PAT)
;; finite repeat
(begin
(define min (syntax-e #'MIN))
(define max (syntax-e #'MAX))
(define new-rule-stx (with-syntax ([(MIN-SUBPAT ...) (make-list min #'SUB-PAT)]
[(EXTRA-SUBPAT ...) (make-list (- max min) #'SUB-PAT)])
;; has to keep the same name to work correctly
#'(rule NAME (seq MIN-SUBPAT ... (maybe EXTRA-SUBPAT) ...))))
(recur new-rule-stx #f))]
[(maybe SUB-PAT)
(begin (begin
(define-values (inferred-rules new-sub-pats) (define-values (inferred-rules new-sub-pats)
(lift-nonprimitive-pattern #'sub-pat)) (lift-nonprimitive-pattern #'SUB-PAT))
(with-syntax ([(sub-pat ...) new-sub-pats]) (with-syntax ([(SUB-PAT ...) new-sub-pats])
(cons #'(head origin name (cons #'(HEAD ORIGIN NAME
[sub-pat ...] [SUB-PAT ...]
[]) [])
inferred-rules)))] inferred-rules)))]
[(seq sub-pat ...) [(seq SUB-PAT ...)
(begin (begin
(define-values (inferred-rules new-sub-pats) (define-values (inferred-rules new-sub-pats)
(lift-nonprimitive-patterns (syntax->list #'(sub-pat ...)))) (lift-nonprimitive-patterns (syntax->list #'(SUB-PAT ...))))
(with-syntax ([(sub-pat ...) new-sub-pats]) (with-syntax ([(SUB-PAT ...) new-sub-pats])
(cons #'(head origin name [sub-pat ...]) (cons #'(HEAD ORIGIN NAME [SUB-PAT ...])
inferred-rules)))])])))) inferred-rules)))])]))))
@ -162,7 +170,7 @@
#t] #t]
[(choice sub-pat ...) [(choice sub-pat ...)
#f] #f]
[(repeat min val) [(repeat min max val)
#f] #f]
[(maybe sub-pat) [(maybe sub-pat)
#f] #f]

@ -0,0 +1,7 @@
#lang brag
;; test the curly quantifier
start : a-rule | b-rule | c-rule | d-rule
a-rule : "a"{2} ; exactly 2
b-rule : "b"{,2} ; up to 2
c-rule : "c"{2,} ; 2 or more
d-rule : "d"{2,3} ; 2 or 3

@ -9,7 +9,7 @@
;; (for DrRacket selections etc) ;; (for DrRacket selections etc)
[whitespace (token 'WHITE lexeme)] [whitespace (token 'WHITE lexeme)]
[(:or (from/to "'" "'") (from/to "\"" "\"")) (token 'LIT lexeme)] [(:or (from/to "'" "'") (from/to "\"" "\"")) (token 'LIT lexeme)]
[(:or (char-set "()[]|+*:") hide-char splice-char) (token 'MISC lexeme)] [(:or (char-set "()[]{}|+*:") hide-char splice-char) (token 'MISC lexeme)]
[(:seq (:or "#" ";") (complement (:seq (:* any-char) NL (:* any-char))) (:or NL "")) (token 'COMMENT lexeme)] [(:seq (:or "#" ";") (complement (:seq (:* any-char) NL (:* any-char))) (:or NL "")) (token 'COMMENT lexeme)]
[id (token 'ID lexeme)] [id (token 'ID lexeme)]
[any-char (token 'OTHER lexeme)])) [any-char (token 'OTHER lexeme)]))

@ -13,8 +13,8 @@
;; A newline can be any one of the following. ;; A newline can be any one of the following.
(define-lex-abbrev NL (:or "\r\n" "\r" "\n")) (define-lex-abbrev NL (:or "\r\n" "\r" "\n"))
;; chars used for quantifiers & parse-tree filtering ;; reserved-chars = chars used for quantifiers & parse-tree filtering
(define-for-syntax quantifiers "+:*") ; colon is reserved to separate rules and productions (define-for-syntax quantifiers "+:*{}") ; colon is reserved to separate rules and productions
(define-lex-trans reserved-chars (define-lex-trans reserved-chars
(λ(stx) #`(char-set #,(format "~a~a~a" quantifiers hide-char splice-char)))) (λ(stx) #`(char-set #,(format "~a~a~a" quantifiers hide-char splice-char))))
@ -64,7 +64,8 @@
(token-SPLICE lexeme)] (token-SPLICE lexeme)]
["|" ["|"
(token-PIPE lexeme)] (token-PIPE lexeme)]
[(:or "+" "*") [(:or "+" "*"
(:: "{" (:* digit) (:? (:: "," (:* digit))) "}"))
(token-REPEAT lexeme)] (token-REPEAT lexeme)]
[whitespace [whitespace
;; Skip whitespace ;; Skip whitespace

@ -157,11 +157,25 @@
(cond [(string=? $2 "*") (cond [(string=? $2 "*")
(pattern-repeat (position->pos $1-start-pos) (pattern-repeat (position->pos $1-start-pos)
(position->pos $2-end-pos) (position->pos $2-end-pos)
0 $1)] 0 #f $1)]
[(string=? $2 "+") [(string=? $2 "+")
(pattern-repeat (position->pos $1-start-pos) (pattern-repeat (position->pos $1-start-pos)
(position->pos $2-end-pos) (position->pos $2-end-pos)
1 $1)] 1 #f $1)]
[(regexp-match #px"^\\{(\\d+)?(,)?(\\d+)?\\}$" $2) ; "{min,max}" with both min & max optional
=> (λ (m)
(match-define (list min-repeat max-repeat)
(match m
[(list _ min range? max) (let ([min (if min (string->number min) 0)])
(list
min
(cond
[(and range? max) (string->number max)]
[(and (not range?) (not max)) min] ; {3} -> {3,3}
[(not max) #f])))]))
(pattern-repeat (position->pos $1-start-pos)
(position->pos $2-end-pos)
min-repeat max-repeat $1))]
[else [else
(error 'grammar-parse "unknown repetition operator ~e" $2)])] (error 'grammar-parse "unknown repetition operator ~e" $2)])]
[(atomic-pattern) [(atomic-pattern)
@ -222,8 +236,8 @@
(pattern-lit start-pos end-pos v (or hide? h))] (pattern-lit start-pos end-pos v (or hide? h))]
[(pattern-choice _ _ vs) [(pattern-choice _ _ vs)
(pattern-choice start-pos end-pos vs)] (pattern-choice start-pos end-pos vs)]
[(pattern-repeat _ _ m v) [(pattern-repeat _ _ min max v)
(pattern-repeat start-pos end-pos m v)] (pattern-repeat start-pos end-pos min max v)]
[(pattern-maybe _ _ v) [(pattern-maybe _ _ v)
(pattern-maybe start-pos end-pos v)] (pattern-maybe start-pos end-pos v)]
[(pattern-seq _ _ vs) [(pattern-seq _ _ vs)

@ -31,7 +31,8 @@
(struct pattern-choice pattern (vals) (struct pattern-choice pattern (vals)
#:transparent) #:transparent)
(struct pattern-repeat pattern (min ;; either 0 or 1 (struct pattern-repeat pattern (min
max
val) val)
#:transparent) #:transparent)

@ -80,8 +80,8 @@
'hide hide)] 'hide hide)]
[(struct pattern-choice (start end vals)) [(struct pattern-choice (start end vals))
(datum->syntax #f`(choice ,@(map recur vals)) source-location)] (datum->syntax #f`(choice ,@(map recur vals)) source-location)]
[(struct pattern-repeat (start end min val)) [(struct pattern-repeat (start end min max val))
(datum->syntax #f`(repeat ,min ,(recur val)) source-location)] (datum->syntax #f`(repeat ,min ,max ,(recur val)) source-location)]
[(struct pattern-maybe (start end val)) [(struct pattern-maybe (start end val))
(datum->syntax #f`(maybe ,(recur val)) source-location)] (datum->syntax #f`(maybe ,(recur val)) source-location)]
[(struct pattern-seq (start end vals)) [(struct pattern-seq (start end vals))

@ -0,0 +1,23 @@
#lang racket/base
(require brag/examples/curly-quantifier
brag/support
rackunit)
(check-exn exn:fail:parsing? (λ () (parse-to-datum "a")))
(check-equal? (parse-to-datum "aa") '(start (a-rule "a" "a")))
(check-exn exn:fail:parsing? (λ () (parse-to-datum "aaa")))
(check-equal? (parse-to-datum "") '(start (b-rule)))
(check-equal? (parse-to-datum "b") '(start (b-rule "b")))
(check-equal? (parse-to-datum "bb") '(start (b-rule "b" "b")))
(check-exn exn:fail:parsing? (λ () (parse-to-datum "bbb")))
(check-exn exn:fail:parsing? (λ () (parse-to-datum "c")))
(check-equal? (parse-to-datum "cc") '(start (c-rule "c" "c")))
(check-equal? (parse-to-datum "ccc") '(start (c-rule "c" "c" "c")))
(check-equal? (parse-to-datum "cccc") '(start (c-rule "c" "c" "c" "c")))
(check-exn exn:fail:parsing? (λ () (parse-to-datum "d")))
(check-equal? (parse-to-datum "dd") '(start (d-rule "d" "d")))
(check-equal? (parse-to-datum "ddd") '(start (d-rule "d" "d" "d")))
(check-exn exn:fail:parsing? (λ () (parse-to-datum "dddd")))

@ -1,8 +1,13 @@
#lang info #lang info
(define version "1.0") (define version "1.0")
(define deps '("base" "br-parser-tools-lib" "rackunit-lib"))
(define build-deps '("at-exp-lib" "br-parser-tools-doc" "racket-doc"
"scribble-lib"))
(define collection 'multi) (define collection 'multi)
(define deps '("base"
"br-parser-tools-lib"
"rackunit-lib"))
(define build-deps '("at-exp-lib"
"br-parser-tools-doc"
"racket-doc"
"scribble-lib"))
(define update-implies '("br-parser-tools-lib"))

Loading…
Cancel
Save