Better Grammar
define-grammar
bettergrammar*
bnf:  add
bnf:  sub
typeset-grammar
bettergrammar*-diff
bettergrammar*-ndiff
typeset-grammar-diff
8.12

Better Grammar🔗ℹ

William J. Bowman <wjb@williamjbowman.com>

It’s like racketgrammar*, but better.

Specifically, scribble/bettergrammar is library for typesetting ‘racketgrammar*‘s with difference annotations, for when you are typesetting lots of grammars with small changes between them. It supports defining and reusing grammars, annotations for differences and changes between grammars, automatically computing the difference between two grammars (using ‘sexp-diff‘), and rendering grammar differences with both highlighting and annotations to emphasize what has changed between grammars.

syntax

(define-grammar maybe-literals maybe-datum-literals id [id clause-datum ...+] ...)

 
maybe-literals = 
  | (#:literals (id ...))
     
maybe-datum-literals = 
  | (#:datum-literals (id ...))
Defines id, id-block, and id-block0 as rendering forms analogou to racket, racketblock, and racketblock0, and defines the grammar id to be typeset by bettergrammar*, bettergrammar*-diff, and bettergrammar*-ndiff.

The [id clause-datum] ..., maybe-literals, and maybe-datum-literals are the same as specified by second form or bettergrammar*.

Must be used in definition context.

Examples:
> (require scribble/bettergrammar)
> (define-grammar stlc-grammar (e (λ (x) e) (e e) x))
> (define-grammar let-grammar
   #:literals (integer?)
   #:datum-literals (let)
   (e (integer? (let ([x e]) e))))

Example:
@bettergrammar*[stlc-grammar]
@bettergrammar*[let-grammar]

Renders like:

  e ::= (λ (x) e)
  | (e e)
  | x
  e ::= integer?
  | (let ([x e]) e)

We can now also use let-grammar to typeset inline examples which are typeset according to the grammar specification, with any datum literals or literals typeset as in the rendering above, and other symbols typeset as variables using racketvarfont. For example:

Example:
@let-grammar[(let ([x 5]) x)]

Renders like:

(let ([x 5]) x)

Example:
@let-grammar-block[
(let ([x 5])
  x)
]

Renders like:
(let ([x 5])
  x)

Example:
@let-grammar-block0[
(let ([x integer?])
  x)
]

Renders like:
(let ([x integer?])
  x)

Changed in version 1.4 of package scribble-bettergrammar-lib: Added #:literals, #:datum-literals support.
Changed in version 1.6: Added rendering forms that obey #:literals and #:datum-literals used in definition.

syntax

(bettergrammar* maybe-literals
                maybe-datum-literals
                ([addid clause-datum ...+] ...)
                ([subid clause-datum ...+] ...)
                ([id clause-datum ...+] ...))
(bettergrammar* maybe-literals
                maybe-datum-literals
                [id clause-datum ...+] ...)
(bettergrammar* maybe-literals maybe-datum-literals id)
 
maybe-datum-literals = 
  | (#:datum-literals (id ...))
Like racketgrammar*, but supports typesetting a grammar with difference annotations for non-terminals, and typesetting grammars pre-defined using define-grammar. maybe-literals have the same interpretation as in racketgrammar*.

Identifiers included in maybe-datum-literals are not typeset as variables, nor as racket literals.

The addid clauses are typeset as additions to the grammar, using +::= instead of ::= to indicate the addition to an existing nonterminal.

The subid clauses are typeset as removed from the grammar, using -::= instead of ::= to indicate the addition to an existing nonterminal.

The id clauses are typeset as in racketgrammar*, but using ::= instead of = to define the nonterminal.

The form supports a second syntax that is identical to racketgrammar*, except the nonterminal is still separated from its productions using ::=.o

The third syntax enables typesetting a grammar previously defined by define-grammar. If the grammar was defined with literals or datum-literals, these are merged into any specified directly via maybe-literals or maybe-datum-literals.

All forms also support escapes in Scribble using unsyntax, and various Scribble element transformers, such as code:hilite.

Example:
@bettergrammar*[(e (λ (x) e) x (e e) 1)]

Renders like:
  e ::= (λ (x) e)
  | x
  | (e e)
  | 1

Example:
@bettergrammar*[
((e v))
()
((v natural ()))
]

Renders like:
  e +::= v
     
  v ::= natural
  | ()

Example:
@bettergrammar*[
(e (unsyntax @bnf:add{v}) (λ (x) e) x (e e) (unsyntax @bnf:sub{1}))
((unsyntax @bnf:add{v}) natural ())
]

Renders like:
  e ::= v
  | (λ (x) e)
  | x
  | (e e)
  | 1
     
  v ::= natural
  | ()

Example:
@bettergrammar*[
(e (code:hilite v) (λ (x) e) x (e e))
(v natural ())
]

Renders like:

  e ::= v
  | (λ (x) e)
  | x
  | (e e)
     
  v ::= natural
  | ()

Examples:
> (require scribble/bettergrammar)
> (define-grammar stlc-grammar (e (λ (x) e) (e e) x))

Example:
@bettergrammar*[stlc-grammar]
Renders like:
  e ::= (λ (x) e)
  | (e e)
  | x

Changed in version 1.2 of package scribble-bettergrammar-lib: Merged functionality from typeset-grammar-diff into this form.
Changed in version 1.4: Added #:literals, #:datum-literals support

procedure

(bnf:add datum)  string?

  datum : string?
Typesets datum as an addition to a bettergrammar*. In the default style, this means hilighting with a blue background and adding a + superscript. You can redefine this by redefining the bnf-add class in your style file.

As it works over decoded strings, it can also be used outside of bettergrammar*, although this interface may change.

Example:
@bettergrammar*[
(e (unsyntax (bnf:add "v")) (λ (x) e) x (e e))
((unsyntax (bnf:add "v")) natural ())
]

Renders like:
  e ::= v
  | (λ (x) e)
  | x
  | (e e)
     
  v ::= natural
  | ()

Example:
@bnf:add{natural}

Renders like:

natural

procedure

(bnf:sub datum)  string?

  datum : string?
Typesets datum as a removal from a bettergrammar*. In the default style, this means hilighting with a red background and adding a - superscript. You can redefine this by redefining the bnf-sub class in your style file.

As it works over decoded strings, it can also be used outside of bettergrammar*, although this interface may change.

Example:
@bettergrammar*[
(e (unsyntax (bnf:sub "v")) (λ (x) e) x (e e))
((unsyntax (bnf:sub "v")) natural ())
]

Renders like:
  e ::= v
  | (λ (x) e)
  | x
  | (e e)
     
  v ::= natural
  | ()

Example:
@bnf:sub{natural}

Renders like:

natural

syntax

(typeset-grammar id)

Typeset the grammar defined as id using bettergramar*. id must have been previously defined by define-grammar.

Example:
(define-grammar stlc-grammar (e (λ (x) e) (e e) x))

Example:
@typeset-grammar[stlc-grammar]

Renders like:
  e ::= (λ (x) e)
  | (e e)
  | x

NOTE: This form is deprecated; use bettergrammar*, instead. Functionality merged into bettergrammar*; exists for backwards compatibility and will eventually be removed.

Changed in version 1.2 of package scribble-bettergrammar-lib: Deprecated

syntax

(bettergrammar*-diff maybe-literals
                     maybe-datum-literals
                     maybe-include
                     maybe-exclude clause-spec clause-spec)
 
maybe-include = 
  | (#:include (id ...))
     
maybe-exclude = 
  | (#:include (id ...))
     
maybe-literals = 
  | (#:literals (id ...))
     
maybe-datum-literals = 
  | (#:datum-literals (id ...))
     
clause-spec = identifier?
  | (clauses ...)
Compute and typeset the differnce between two grammars. Either clause-spec can be a valid racketgrammar* spec, or an identifier previously defined by define-grammar

If the optional maybe-include is given, then only the nonterminals specified in (id ...) are included in the rendered grammar.

If the optional maybe-exclude is given, then any nonterminals specified in (id ...) are excluded from the rendered grammar.

If the optional maybe-literals is given, the literals are passed to bettergrammar*. If an identifier from define-grammar is used, the literal used in that definition will be merged with the maybe-literals.

If the optional maybe-datum-literals is given, the datum literals are passed to bettergrammar*. If an identifier from define-grammar is used, the datum literal used in that definition will be merged with the maybe-datum-literals.

Renders a new grammar where non-terminals and productions the old grammar that have been removed in new grammar are typeset like with bnf:sub, and non-terminals and productions that have been added in the new grammar are typeset like with bnf:add.

The diff algorithm may not preserve source location information used to typeset, so large productions may be collapsed to a single line. The diff should preserve paren-shape. Literals should be linked properly with racket.

Example:
(define-grammar stlc-grammar-v1
  (e (λ (x) e) (e e) x)
  (v (λ (x) e))
  (x name))
(define-grammar stlc-grammar-v2
  (e (e e) x v)
  (v (λ (x) e) natural ())
  (natural 0 (add1 natural)))

Example:
@bettergramar*-diff[stlc-grammar-v1 stlc-grammar-v2]

Renders like:
  e ::= (λ (x) e)
  | (e e)
  | x
  | v
     
  v ::= (λ (x) e)
  | natural
  | ()
     
  natural ::= 0
  | (add1 natural)
     
  x ::= name

Example:
@bettergramar*-diff[#:include (e) stlc-grammar-v1 stlc-grammar-v2]

Renders like:
  e ::= (λ (x) e)
  | (e e)
  | x
  | v

Example:
@bettergramar*-diff[#:exclude (e) stlc-grammar-v1 stlc-grammar-v2]

Renders like:
  v ::= (λ (x) e)
  | natural
  | ()
     
  natural ::= 0
  | (add1 natural)
     
  x ::= name

Example:
@bettergrammar*[(e (1 ...)
 
(3 ...))]
 
@bettergrammar*[(e (2 ...))]
 
@bettergrammar*-diff[
((e (1 ...)
 
(3 ...)))
 
((e (2 ...)))
]

Renders like:

  e ::= (1 ...)
  | (3 ...)
  e ::= (2 ...)
  e ::= (2 1 ...)
  | (3 ...)

Example:
@bettergrammar*-diff[
#:literals (integer?)
#:datum-literals (let)
((e integer? (let ([x e]) e)))
((e (let ([x v]) v)
 (v integer?)))
]

Renders like:
  e ::= integer?
  | (let ([x v e]) v e)
  | (v integer?)

Changed in version 1.1 of package scribble-bettergrammar-lib: Support for second, anonymous form.
Changed in version 1.2: Renamed from typeset-grammar-diff
Changed in version 1.3: Added #:include and #:exclude support.
Changed in version 1.4: Added #:literals, #:datum-literals support; enabled mixed-diff between clauses and defined grammars.

syntax

(bettergrammar*-ndiff
 maybe-labels
 (maybe-include maybe-exclude maybe-clause-spec clause-spec) ...)
 
maybe-labels = 
  | (#:labels (string-literal ...))
     
maybe-include = 
  | (#:include (id ...))
     
maybe-exclude = 
  | (#:include (id ...))
     
maybe-literals = 
  | (#:literals (id ...))
     
maybe-datum-literals = 
  | (#:datum-literals (id ...))
     
clause-spec = identifier?
  | (clauses ...)
     
maybe-clause-spec = 
  | clause-spec
Compute and typeset a sequence of differences. The intention is for this to only be supported on the HTML backend, to support an interactive view comparing different grammars.

The main input is a sequence of clause specifications that are valid for bettergrammar*-diff. Each clause specification takes the same optional include and exclude arguments, can be a clause specification directly or a defined grammar. The first clause spec is optional; if excluded, then no difference is computed, and the required clause spec is typeset normally.

If labels are provided, then there must be one label for each pair of difference specification. The labels are used in the tabbed interface.

Example:
@bettergrammar*-ndiff[
(stlc-grammar-v1)
]

Renders like:

  e ::= (λ (x) e)
  | (e e)
  | x
     
  v ::= (λ (x) e)
     
  x ::= name

Example:
@bettergrammar*-ndiff[
(stlc-grammar-v1 stlc-grammar-v2)
]

Renders like:

  e ::= (λ (x) e)
  | (e e)
  | x
  | v
     
  v ::= (λ (x) e)
  | natural
  | ()
     
  natural ::= 0
  | (add1 natural)
     
  x ::= name

Example:
@bettergrammar*-ndiff[
#:labels ("Diff" "stlc-grammar-v2")
(stlc-grammar-v1 stlc-grammar-v2)
(stlc-grammar-v2)
]

Renders like:

  e ::= (λ (x) e)
  | (e e)
  | x
  | v
     
  v ::= (λ (x) e)
  | natural
  | ()
     
  natural ::= 0
  | (add1 natural)
     
  x ::= name
  e ::= (e e)
  | x
  | v
     
  v ::= (λ (x) e)
  | natural
  | ()
     
  natural ::= 0
  | (add1 natural)

Example:
@bettergrammar*-ndiff[
#:labels ("Diff" "stlc-grammar-v2")
(#:include (e v) stlc-grammar-v1 stlc-grammar-v2)
(stlc-grammar-v2)
]

Renders like:

  e ::= (λ (x) e)
  | (e e)
  | x
  | v
     
  v ::= (λ (x) e)
  | natural
  | ()
  e ::= (e e)
  | x
  | v
     
  v ::= (λ (x) e)
  | natural
  | ()
     
  natural ::= 0
  | (add1 natural)

Added in version 1.5 of package scribble-bettergrammar-lib.

An alias for bettergrammar*-diff for backwards compatibility.

NOTE: This form is deprecated; use bettergrammar*-diff, instead. Renamed to bettergrammar*-diff for nicer interface; exists for backwards compatibility and will eventually be removed.

Changed in version 1.2 of package scribble-bettergrammar-lib: Made an alias; deprecated