added unstable/redex with useful typesetting (rewriter) helpers

This commit is contained in:
Ryan Culpepper 2013-04-04 01:23:57 -04:00
parent 516f56fc83
commit ffa589116f
3 changed files with 700 additions and 0 deletions

356
collects/unstable/redex.rkt Normal file
View File

@ -0,0 +1,356 @@
#lang racket
(require racket/contract
redex/reduction-semantics
redex/pict
slideshow/pict
racket/list)
;; TO DO:
;; - chained binary operators
;; eg (+ 1 2 3) => "1 + 2 + 3"
;; - helper for thin spaces around operators/functions ?
;; - rewriters for more conventions (eg subst, env, judgments, ...)
(provide
(contract-out
;; Using and controlling rewriters
[with-rewriters
(-> (-> any) any)]
[current-atomic-rewriters
(parameter/c (plistof symbol? atomic-rewriter/c))]
[current-compound-rewriters
(parameter/c (plistof symbol? compound-rewriter/c))]
[current-unquote-rewriters
(parameter/c (plistof (-> lw? any/c) (-> lw? lw?)))]
[add-atomic-rewriters!
(->* [] [] #:rest (plistof symbol? atomic-rewriter/c)
void?)]
[add-compound-rewriters!
(->* [] [] #:rest (plistof symbol? compound-rewriter/c)
void?)]
[add-unquote-rewriters!
(->* [] [] #:rest (plistof (-> lw? any/c) (-> lw? lw?))
void?)]
;; Rewriter constructors
[only-first-rw
(-> compound-rewriter/c)]
[binary-rw
(->* [content/c]
[#:parenthesize-arg (or/c #t #f (listof symbol?) (-> lw? any/c))
#:parenthesize-left (or/c #t #f (listof symbol?) (-> lw? any/c))
#:parenthesize-right (or/c #t #f (listof symbol?) (-> lw? any/c))]
compound-rewriter/c)]
[prefix-rw
(-> content/c
compound-rewriter/c)]
[postfix-rw
(-> content/c
compound-rewriter/c)]
[function-rw
(-> content/c
compound-rewriter/c)]
[splice-rw
(-> compound-rewriter/c)]
[constant-rw
(-> content/c
compound-rewriter/c)]
[bracket-rw
(->* [(or/c 'round 'square 'curly 'angle (list/c (or/c string? pict?) (or/c string? pict?)))]
[#:comma? any/c]
compound-rewriter/c)]
[set-cons-rw
(-> compound-rewriter/c)]
;; Contracts
[compound-rewriter/c
contract?]
[plistof
(-> contract? contract? contract?)]
#|
[content/c
contract?]
|#))
(define (plistof key/c val/c)
(letrec ([ctc
(recursive-contract
(or/c '()
(cons/c key/c (cons/c val/c ctc))))])
ctc))
(define content/c
(or/c string? pict? lw? (-> (or/c string? pict? lw?))))
(define atomic-rewriter/c
(or/c string? pict? (-> (or/c string? pict?))))
(define compound-rewriter/c
(-> (listof lw?)
(listof (or/c string? pict? lw?))))
;; ============================================================
(define current-atomic-rewriters (make-parameter null))
(define current-compound-rewriters (make-parameter null))
(define current-unquote-rewriters (make-parameter null))
(define (add-atomic-rewriters! . args)
(push-rewriters! 'add-atomic-rewriters! current-atomic-rewriters args))
(define (add-compound-rewriters! . args)
(push-rewriters! 'add-compound-rewrites! current-compound-rewriters args))
(define (add-unquote-rewriters! . args)
(push-rewriters! 'add-unquote-rewrites! current-unquote-rewriters args))
(define (with-rewriters thunk)
(with-atomic-rewriter*
(lambda ()
(with-compound-rewriter*
(lambda ()
(with-unquote-rewriter*
(lambda ()
(thunk))
(reverse (current-unquote-rewriters))))
(reverse (current-compound-rewriters))))
(reverse (current-atomic-rewriters))))
;; ============================================================
(define (push-rewriters! who param args)
(unless (even? (length args))
(error who "expected even number of arguments, got: ~e" args))
(param (append args (param))))
;; Note: l is reversed, so rewriter comes first, symbol/pred comes second.
(define (with-atomic-rewriter* thunk l)
(if (null? l)
(thunk)
(with-atomic-rewriter
(cadr l)
(let ([rw (car l)])
(if (string? rw)
(lambda () (text rw (default-style) (default-font-size)))
rw))
(with-atomic-rewriter* thunk (cddr l)))))
(define (with-compound-rewriter* thunk l)
(if (null? l)
(thunk)
(with-compound-rewriter
(cadr l) (car l)
(with-compound-rewriter* thunk (cddr l)))))
(define (with-unquote-rewriter* thunk l)
(with-unquote-rewriter
(lambda (lw)
(let loop ([l l])
(cond [(null? l)
lw]
[else
(let ([pred (cadr l)]
[tx (car l)])
(cond [(pred lw) (tx lw)]
[else (loop (cddr l))]))])))
(thunk)))
;; ============================================================
(define (only-first-rw)
(compound-rw 'only-first-rw
(lambda (lws)
(let ([arg-lw (list-ref lws 2)])
(list arg-lw)))))
(define (binary-rw op
#:parenthesize-arg [parenthesize-arg #f]
#:parenthesize-left [parenthesize-left parenthesize-arg]
#:parenthesize-right [parenthesize-right parenthesize-arg])
(compound-rw 'binary-rw
(lambda (lws)
(let ([left (list-ref lws 2)]
[right (list-ref lws 3)])
;; (list left (just-after op left) (between "" left right) right)
;; (list left (just-after op left) "" right)
(append (maybe-parenthesize left parenthesize-left)
(list (between (->string/pict op) left right))
(maybe-parenthesize right parenthesize-right))))
2 2))
(define (prefix-rw pre
#:parenthesize-arg [parenthesize-arg #f])
(compound-rw 'prefix-rw
(lambda (lws)
(let ([arg-lw (list-ref lws 2)])
(append (list (just-before (->string/pict pre) arg-lw))
(maybe-parenthesize arg-lw parenthesize-arg))))
1 1))
(define (postfix-rw post
#:parenthesize-arg [parenthesize-arg #f])
(compound-rw 'postfix-rw
(lambda (lws)
(let ([arg-lw (list-ref lws 2)])
(append (maybe-parenthesize arg-lw parenthesize-arg)
(list (just-after (->string/pict post) arg-lw)))))
1 1))
(define (function-rw name)
(compound-rw 'function-rw
(lambda (lws)
(list* (re-lw (->string/pict name) (list-ref lws 1))
(between "(" (list-ref lws 1) (list-ref lws 2))
(comma-ize (cddr lws) #t)))))
(define (bracket-rw brackets #:tall? [tall? #f] #:comma? [comma? #t])
(let-values ([(left-bracket right-bracket)
(cond [(symbol? brackets)
(case brackets
((round) (values "(" ")"))
((square) (values "[" "]"))
((curly) (values "{" "}"))
((angle) (values "" ""))
;; FIXME: more
(else (error 'bracket-rw "unknown bracket kind: ~e" brackets)))]
[(list? brackets)
(values (first brackets) (second brackets))])])
(compound-rw 'bracket-rw
(lambda (lws)
(let ([lwA (first lws)]
[lwZ (last lws)]
[elems (drop-right (drop lws 2) 1)])
(append (list (re-lw (if tall? (taller left-bracket) left-bracket) lwA)
"")
(if comma? (comma-ize elems #f) elems)
(list (re-lw (if tall? (taller right-bracket) right-bracket) lwZ))))))))
(define (splice-rw)
(compound-rw 'splice-rw
(lambda (lws)
(drop-right (drop lws 2) 1))))
(define (constant-rw s)
(compound-rw 'constant-rw
(lambda (lws)
(list (re-lw s (first lws))))))
(define (set-cons-rw)
(compound-rw 'set-cons-rw
(lambda (lws)
(list (just-before "{" (list-ref lws 2))
(list-ref lws 2)
(just-after "}" (list-ref lws 2))
(between "" (list-ref lws 2) (list-ref lws 3))
(list-ref lws 3)))))
;; ============================================================
;; Content = (U string symbol pict (listof (U 'spring lw)))
;; between : Content lw lw -> lw
;; Makes an lw with given content and location between given lws.
(define (between s a b)
(build-lw s
(lw-line a)
0
(+ (lw-column a) (lw-column-span a))
(max 0 (- (lw-column b)
(+ (lw-column a) (lw-column-span a))))))
;; re-lw : Content lw -> lw
;; Makes an lw with locations of old lw and new content.
(define (re-lw new-e lw)
(build-lw new-e
(lw-line lw) (lw-line-span lw)
(lw-column lw) (lw-column-span lw)))
;; refit : (listof lw) (listof lw) -> (listof lw)
;; Add empty lws ("") around new based on locations of old lws (??)
;; Soaks up logical space ????
(define (refit orig new)
(append (list (between "" (just-before "" (car orig)) (car new)))
new
(list (between "" (just-after "" (last new)) (just-after "" (last orig))))))
(define (compound-rw who proc [min-args 0] [max-args +inf.0]
#:refit? [refit? #t])
((if refit? refit-rw values)
(lambda (lws)
(unless (>= (length lws) 3)
(error who "expected list of at least 3 lws, got: ~e" lws))
(let ([lwA (first lws)]
[lwB (second lws)]
[lwZ (last lws)])
(unless (member (lw-e lwA) '("(" "[" "{"))
(error who "expected first lw to contain open-paren, got: ~e" lwA))
(unless (symbol? (lw-e lwB))
(error who "expected second lw to contain symbol, got: ~e" lwB))
(unless (member (lw-e lwZ) '(")" "]" "}"))
(error who "expected last lw to contain close-paren, got: ~e" lwZ))
(let ([args (- (length lws) 3)])
(unless (<= min-args args max-args)
(if (= min-args max-args)
(error who "expected ~s argument(s), got ~s: ~e"
min-args args lws)
(error who "expected between ~s and ~s arguments, got ~s: ~e"
min-args max-args args lws))))
(proc lws)))))
(define ((refit-rw proc) lws)
(refit lws (proc lws)))
(define (taller s)
(define p
(cond [(string? s) (text s (default-style) (default-font-size))]
[(pict? s) s]))
(define h (pict-height p))
(drop-below-ascent (scale (launder p) 1 1.3) (* 0.1 h)))
(define (comma-ize lws contains-close-paren?)
(let loop ([lws lws])
(cond [(and contains-close-paren?
(or (null? (cdr lws))
(null? (cddr lws))))
lws]
[(and (not contains-close-paren?)
(or (null? lws)
(null? (cdr lws))))
lws]
[(positive? (lw-line-span (car lws)))
;; a line break?
(cons (car lws) (loop (cdr lws)))]
[else (list*
(car lws)
(between ", " (car lws) (cadr lws))
(loop (cdr lws)))])))
;; ->string/pict : (U string pict (-> (U string pict)) -> (U string pict)
(define (->string/pict c)
(cond [(pict? c) c]
[(string? c) c] ;; ((current-text) c (default-style) (default-font-size))
[else (c)]))
;; maybe-parenthesize : lw _ -> (nonempty-listof lw)
(define (maybe-parenthesize arg paren-spec)
(if (parenthesize? arg paren-spec)
(parenthesize-lws (list arg))
(list arg)))
;; parenthesize? : lw (U #t #f (listof symbol) (-> lw boolean)) -> boolean
(define (parenthesize? arg parenthesize-arg)
(cond [(boolean? parenthesize-arg) parenthesize-arg]
[(list? parenthesize-arg)
(let ([contents (lw-e arg)])
(and (not (lw-unq? arg))
(not (lw-metafunction? arg))
(list? contents)
(>= (length contents) 3)
(member (lw-e (first contents)) '("(" "[" "{"))
(member (lw-e (last contents)) '(")" "]" "}"))
(member (lw-e (second contents)) parenthesize-arg)
#t))]
[(procedure? parenthesize-arg)
(parenthesize-arg arg)]))
;; parenthesize-lws : (nonempty-listof lw) -> (nonempty-listof lw)
(define (parenthesize-lws lws)
(let ([lwA (first lws)]
[lwZ (last lws)])
(append (list (just-before "(" lwA))
lws
(list (just-after ")" lwZ)))))

View File

@ -0,0 +1,343 @@
#lang scribble/manual
@(require racket/stxparam scribble/base scribble/eval "utils.rkt"
(for-syntax racket/base syntax/srcloc)
(for-label racket/base racket/contract slideshow/pict redex unstable/redex))
@(define the-eval (make-base-eval))
@(the-eval '(require redex/reduction-semantics redex/pict unstable/redex slideshow/pict))
@title[#:tag "redex"]{Redex}
@unstable-header[]
@defmodule[unstable/redex]
This library provides functions to help typesetting for
@racketmodname[redex] models. The following example program provides
an overview of the features:
@with-eval-preserve-source-locations[
@interaction[#:eval the-eval
(define-language ZF
[e empty
(Set e)
(Union e_1 e_2)
(Powerset e)
ZZ
variable-not-otherwise-mentioned]
[formula (same? e_1 e_2)
(in? e_1 e_2)
true
false
(implies formula_1 formula_2)])
]]
By default, Redex models are typeset as S-expressions with some basic
styling that distinguishes literals from nonterminal names, handles
subscripting, etc.
@with-eval-preserve-source-locations[
@interaction[#:eval the-eval
(language->pict ZF)
(term->pict ZF (in? x (Set 1 2 3 ...)))
]]
This library provides helper functions for creating and using
rewriters that transform the S-expression model terms into other
notations.
@with-eval-preserve-source-locations[
@interaction[#:eval the-eval
(add-atomic-rewriters!
'empty "∅"
'formula "φ"
'ZZ "\u2124"
'variable-not-otherwise-mentioned
(lambda () (text "x, y, z, ..." (literal-style) (default-font-size)))
'true (lambda () (text "true" '(caps . modern) (default-font-size)))
'false (lambda () (text "false" '(caps . modern) (default-font-size))))
(add-compound-rewriters!
'same? (binary-rw " = ")
'in? (binary-rw " ∈ ")
'Set (bracket-rw 'curly)
'Powerset (function-rw "𝒫 ")
'Union (binary-rw "")
'implies (binary-rw " ⇒ " #:parenthesize-left '(implies)))
(with-rewriters
(lambda ()
(language->pict ZF)))
(with-rewriters
(lambda ()
(render-term ZF (in? x (Set 1 2 3 ...)))))
]]
@; ----------------------------------------
@defproc[(with-rewriters [proc (-> any)])
any]{
Calls @racket[proc] with the rewriters of
@racket[current-atomic-rewriters], @racket[current-compound-rewriters],
and @racket[current-unquote-rewriters].
}
@defparam[current-atomic-rewriters rewriters
(let ([atomic-rewriter/c
(or/c string? pict?
(-> (or/c string? pict?)))])
(plistof symbol? atomic-rewriter/c))]{
Parameter of atomic rewriters (as in @racket[with-atomic-rewriter])
used by @racket[with-rewriters].
}
@defparam[current-compound-rewriters rewriters
(plistof symbol? compound-rewriter/c)]{
Parameter of compound rewriters (as in
@racket[with-compound-rewriter]) used by @racket[with-rewriters].
}
@defparam[current-unquote-rewriters rewriters
(plistof (-> lw? any/c) (-> lw? lw?))]{
Parameter of unquote rewriters (as in @racket[with-unquote-rewriter])
used by @racket[with-rewriters].
}
@deftogether[[
@defproc[(add-atomic-rewriters! [rewriters
(let ([atomic-rewriter/c
(or/c string? pict?
(-> (or/c string? pict?)))])
(plistof symbol? atomic-rewriter/c))])
void?]
@defproc[(add-compound-rewriters! [rewriters
(plistof symbol? compound-rewriter/c)])
void?]
@defproc[(add-unquote-rewriters! [rewriters
(plistof (-> lw? any/c) (-> lw? lw?))])
void?]]]{
Add rewriters to the @racket[current-atomic-rewriters],
@racket[current-compound-rewriters], or
@racket[current-unquote-rewriters], respectively.
}
@defproc[(plistof [key/c contract?] [value/c contract?])
contract?]{
Contract for even-length lists of alternating @racket[key/c] and
@racket[value/c] values.
Equivalent to
@racketblock[
(letrec ([ctc
(recursive-contract
(or/c '()
(cons/c key/c (cons/c value/c ctc))))])
ctc)
]
}
@defthing[compound-rewriter/c contract?]{
Contract for compound rewriters, which take a list of @racket[lw]
structs and returns a list of @racket[lw]s, @racket[pict]s, or
strings.
Equivalent to
@racketblock[
(-> (listof lw?)
(listof (or/c lw? pict? string?)))
]
}
@defproc[(binary-rw [operator (or/c string? pict? (-> (or/c string? pict?)))]
[#:parenthesize-arg parenthesize-arg
(or/c #t #f (listof symbol?) (-> lw? any/c))
#f]
[#:parenthesize-left parenthesize-left
(or/c #t #f (listof symbol?) (-> lw? any/c))
parenthesize-arg]
[#:parenthesize-right parenthesize-right
(or/c #t #f (listof symbol?) (-> lw? any/c))
parenthesize-arg])
compound-rewriter/c]{
Typesets @racket[(_sym _term1 _term2)] using @racket[operator] as a
binary operator between @racket[_term1] and @racket[_term2].
@with-eval-preserve-source-locations[
@examples[#:eval the-eval
(add-compound-rewriters!
'plus (binary-rw " + "))
(with-rewriters
(lambda ()
(term->pict ZF (plus 1 2))))
]]
Redex terms may become ambiguous when typeset. To avoid ambiguity, use
@racket[#:parenthesize-arg] to direct when arguments should be
parenthesized. If @racket[parenthesize-arg] is @racket[#t], then
arguments are always parenthesized; if it is @racket[#f], never; if it
is a list of symbols, then an argument is parenthesized only if the
argument is a term starting with a symbol in the list; if it is a
procedure, then the argument is parenthesized if the procedure applied
to the argument's @racket[lw] struct returns a true value.
@with-eval-preserve-source-locations[
@interaction[#:eval the-eval
(add-compound-rewriters!
'times (binary-rw " × "))
(with-rewriters
(lambda ()
(term->pict ZF (times (plus 1 2) 3))))
(add-compound-rewriters!
'times (binary-rw " × " #:parenthesize-arg '(plus)))
(with-rewriters
(lambda ()
(term->pict ZF (times (plus 1 2) 3))))
]]
The parenthesization rules for left and right arguments can be
supplied separately through @racket[#:parenthesize-left] and
@racket[#:parenthesize-right], for example to create left-associated
or right-associated operators:
@with-eval-preserve-source-locations[
@interaction[#:eval the-eval
(add-compound-rewriters!
'arrow (binary-rw " → " #:parenthesize-left '(arrow)))
(with-rewriters
(lambda ()
(term->pict ZF (arrow (arrow A B) (arrow C D)))))
]]
}
@defproc[(prefix-rw [prefix (or/c string? pict? (-> (or/c string? pict?)))]
[#:parenthesize-arg parenthesize-arg
(or/c #f #t (listof symbol?) (-> lw? any/c))
#f])
compound-rewriter/c]{
Typesets @racket[(_sym _term)] by placing @racket[prefix] before
@racket[_term].
@with-eval-preserve-source-locations[
@examples[#:eval the-eval
(add-compound-rewriters!
'not (prefix-rw "¬ "))
(with-rewriters
(lambda ()
(term->pict ZF (not (in? x empty)))))
]]
}
@defproc[(postfix-rw [postfix (or/c string? pict? (-> (or/c string? pict?)))]
[#:parenthesize-arg parenthesize-arg
(or/c #f #t (listof symbol?) (-> lw? any/c))
#f])
compound-rewriter/c]{
Typesets @racket[(_sym _term)] by placing @racket[postfix] after
@racket[_term].
@with-eval-preserve-source-locations[
@examples[#:eval the-eval
(add-compound-rewriters!
'nonempty (postfix-rw " is nonempty"))
(with-rewriters
(lambda ()
(term->pict ZF (nonempty (Set x)))))
]]
}
@defproc[(function-rw [function (or/c string? pict? (-> (or/c string? pict?)))])
compound-rewriter/c]{
Typesets @racket[(_sym _term ...)] by placing @racket[function] before
the parenthesized, comma-separated list of @racket[_term]s.
@with-eval-preserve-source-locations[
@examples[#:eval the-eval
(add-compound-rewriters!
'f (function-rw "f")
'max (function-rw (text "max" '(bold . modern) (default-font-size))))
(with-rewriters
(lambda ()
(term->pict ZF (max 1 2 (f 3)))))
]]
}
@defproc[(only-first-rw)
compound-rewriter/c]{
Typesets @racket[(_sym _term1 _term2 ...)] as @racket[_term1]. Useful
for hiding parameters that are necessary for defining the semantics
but can be glossed over in its explanation, such as state parameters
used for generating unique names.
@with-eval-preserve-source-locations[
@examples[#:eval the-eval
(add-compound-rewriters!
'First (only-first-rw))
(with-rewriters
(lambda ()
(term->pict ZF [First (in? x y) counter])))
]]
}
@defproc[(splice-rw)
compound-rewriter/c]{
Typesets @racket[(_sym _term ...)] by rendering the @racket[_term]s
side-by-side.
}
@defproc[(constant-rw [constant (or/c string? pict? (-> (or/c string? pict?)))])
compound-rewriter/c]{
Typesets @racket[(_sym _term ...)] as @racket[constant].
}
@defproc[(bracket-rw [brackets (or/c 'round 'square 'curly 'angle
(list (or/c string? pict?)
(or/c string? pict?)))]
[#:comma? comma? any/c #t])
compound-rewriter/c]{
Typesets @racket[(_sym _term ...)] by surrounding the comma-separated
(or space-separated, if @racket[comma?] is false) sequence of
@racket[_term]s with brackets. If @racket[brackets] is a list, the
first element is the left bracket and the second is the right bracket;
@racket['round] is equivalent to @racket['("(" ")")]; @racket['square]
is equivalent to @racket['("[" "]")]; @racket['curly] is equivalent to
@racket['("{" "}")]; and @racket['angle] is equivalent to
@racket['("〈" "〉")].
@with-eval-preserve-source-locations[
@examples[#:eval the-eval
(add-compound-rewriters!
'Tuple (bracket-rw 'angle))
(with-rewriters
(lambda ()
(term->pict ZF (Tuple 1 2 3))))
]]
}
@defproc[(set-cons-rw)
compound-rewriter/c]{
Rewriter that typesets @racket[(_sym _elem-term _set-term)] as the
union of the singleton set containing @racket[_elem-term] with the set
@racket[_set-term].
@with-eval-preserve-source-locations[
@examples[#:eval the-eval
(add-compound-rewriters!
'set-cons (set-cons-rw))
(with-rewriters
(lambda ()
(term->pict ZF (set-cons x S))))
]]
}

View File

@ -99,6 +99,7 @@ Keep documentation and tests up to date.
@include-section["parameter-group.scrbl"]
@include-section["pretty.scrbl"]
@include-section["recontract.scrbl"]
@include-section["redex.scrbl"]
@include-section["sandbox.scrbl"]
@include-section["sequence.scrbl"]
@include-section["string.scrbl"]