k-infix
k-infix is a simple Haskell-like infix expression parser.
k-infix makes writing infix expressions in Racket easy. It’s extensible as one can add operators freely. k-infix supports prefix, postfix, and binary operators. Left and right -associativity are also supported.
1 Quick start
To get started we require the library and use $ to write expressions.
> (require k-infix) > ($ 1 + 2) 3
> ($ 1 + 2 / (3 * 4)) 7/6
> ($ 2 ^ 3 ^ 4) 2417851639229258349412352
> ($ my-fn 1/2 + 1/2 2) my-fn: arity mismatch;
the expected number of arguments does not match the given
number
expected: 2
given: 1
arguments...:
1/2
2 Custom parse rules
We can add our own parse rules using define-$+, this generates a new parser. For example, let’s make sqrt a unary prefix operator.
> (require k-infix/define) > (define-$+ $1 (sqrt 200 left -100)) > ($1 sqrt sqrt 16) 2
An operator entry ((sqrt 200 left -100)) is of the form (id precedence associativity unary-precedence maybe-description).
In the default context where sqrt is a function and not an operator, evaluation would fail because it parses the above expression to (sqrt sqrt 16).
> ($ sqrt sqrt 16) sqrt: arity mismatch;
the expected number of arguments does not match the given
number
expected: 1
given: 2
arguments...:
#<procedure:sqrt>
16
The binary precedence is 200, this means that it will bind very strongly to whatever is to its right.
> ($1 sqrt 8 * 2) 5.656854249492381
> (* (sqrt 8) 2) 5.656854249492381
When it comes to unary postfix operators the second precedence is important. It specifies how strong the operator’s affinity is to be binary. For instance.
> (define-$+ $3 (! 99 left -100) (sqrt 99 left -100)) ; -100, really does not like to be binary > (define (! n) (if (positive? n) (* n (! (sub1 n))) 1)) > ($3 8 ! + sqrt 2) 40321.41421356237
In this example, once + was seen, ! was demoted to a unary postfix operator, then, once sqrt was seen, + was kept a binary operator, since sqrt has a lower postfix precedence.
We can augment the example by investigating unary precedences among themselves.
> (define-$+ $ (! 99 left -100) (sqrt 99 left -100)) > ($ sqrt 16 !) 24
From this we see that sqrt binds more tightly if the precedences are equal. What if we increase the precedence of !?
> (define-$+ $ (! 100 left -100) (sqrt 99 left -100)) > ($ sqrt 16 !) 4574143.623455652
Now that we have defined the precedence we want, we may want to combine more rules, however, we don’t want to keep re-specifying these rules. What we can do is combine parsers.
> (module other racket (provide meta-parser) (require k-infix/define) (define-$+ meta-parser (! 100 left -100) (sqrt 99 left -100))) > (begin-for-syntax (require 'other)) > (define-$+ $ #:parsers (meta-parser) (I 120 left -100)) > (define I identity) > ($ sqrt I 16 !) 4574143.623455652
Combining define-$ forms of the parser is cumbersome because the definition is always one phase too low to be used in the next define-$. The next section will show you how to keep every parser on the same phase using a non-define form.
3 Lower level parsing
k-infix provides low-level parser-generator ’primitives’. These are found in k-infix/custom, and their result is a function taking a syntax object as input.
> (require k-infix/custom) > ($+ (sqrt 100 left -100)) #<procedure:...te/primitive.rkt:10:2>
The primitives are $* and $+, both are analogous to define-$* and define-$+ respectively except that they do not define the parser one +1 phase.
> (define $ ($+ (sqrt 100 left -100))) > ($ #'(1 + sqrt 2)) #<syntax (+ 1 (sqrt 2))>
Combining parsers is easy using the #:parser directive as a first argument followed by a list of parsers. The parsers are merged in order, with conflicts resolved by prioritizing the last parser.
> (define $1 ($* (sqrt 200 left -100))) > (define $2 ($* (I 200 left -100))) > (define $3 ($* (+ 100 left 0))) > (define $ ($* #:parsers ($1 $2 $3))) > ($ #'(1 I + sqrt 2)) #<syntax (+ (I 1) (sqrt 2))>
One can also add rules whilst adding parsers.
> (define $ ($* #:parsers ($) (- 100 left 0))) > ($ #'(1 I + sqrt 2 - 3)) #<syntax (- (+ (I 1) (sqrt 2)) 3)>
4 Reference
(require k-infix) | package: k-infix |
syntax
($ item ...)
item = infix-expr | ~ (expr) infix-expr = or | and | bior | bxor | band | = | != | >= | <= | > | < | <=> | << | >> | + | - | * | / | % | ^ | not | bnot
value
value
value
value
<< : procedure?
value
>> : procedure?
value
% : procedure?
value
^ : procedure?
value
procedure
(<=> x y) → exact-integer?
x : number? y : number?
4.1 Define forms
(require k-infix/define) | package: k-infix |
syntax
name = id parse-lookup-entry = (id prec associativity maybe-postfix-prec) prec = exact-integer? associativity = left | right maybe-postfix-prec = exact-integer?
syntax
name = id parse-lookup-entry = (id prec associativity maybe-postfix-prec) prec = exact-integer? associativity = left | right maybe-postfix-prec =
| exact-integer?
4.2 Custom forms
(require k-infix/custom) | package: k-infix |
syntax
($* #:parsers (parser ...) parse-lookup-entry ...)
name = id parsers = expr parse-lookup-entry = (id prec associativity maybe-postfix-prec) prec = exact-integer? associativity = left | right maybe-postfix-prec =
| exact-integer?
syntax
($+ #:parsers (parser ...) parse-lookup-entry ...)
name = id parsers = expr parse-lookup-entry = (id prec associativity maybe-postfix-prec) prec = exact-integer? associativity = left | right maybe-postfix-prec =
| exact-integer?
4.3 Default parser rules
> (require k-infix racket/pretty) > (pretty-write ($))
#hash((!= . (50 left 50 "inequality comparison"))
(% . (100 left 100 "modulo"))
(* . (100 left 100 "multiplication"))
(+ . (90 left 100 "addition"))
(- . (90 left 100 "subtraction"))
(/ . (100 left 100 "division"))
(< . (60 left 60 "less-than comparison"))
(<< . (80 left 80 "left binary shift"))
(<= . (60 left 60 "less-than or equal comparison"))
(<=> . (70 left 70 "three-way comparison"))
(= . (50 left 50 "equality comparison"))
(> . (60 left 60 "greater-than comparison"))
(>= . (60 left 60 "greater-than or equal comparison"))
(>> . (80 left 80 "right binary shift"))
(^ . (110 right 110 "power"))
(and . (10 left 10 "boolean and"))
(band . (40 left 40 "bitwise and"))
(bior . (20 left 20 "bitwise ior"))
(bnot . (110 right 110 "bitwise not"))
(bxor . (30 left 30 "bitwise xor"))
(not . (110 right 110 "boolean not"))
(or . (0 left 0 "boolean or")))
5 Examples
Here are some more examples to get you started.
5.1 Coin flipping
Suppose you have N coins and K flips. You can flip an arbitrary coin. All coins start face-down, and a flip has a 50% chance of change the coin to face-up. If a coin is face-up, you can take have it.
What is the expected value of (N K) coins and flips given that you waste any remaining flips on a single coin once all coins are flipped?
> (require k-infix memoize)
> (define/memo (x n k) (match* (n k) [(_ 0) 0] [(0 k) ($ 1/2 * x 0 (k - 1) + 1/2 * (-1 + x 1 (k - 1)))] [(n k) ($ 1/2 * (1 + x (n - 1) (k - 1)) + 1/2 * x n (k - 1))])) > (x 1 1) 1/2
> (x 2 1) 1/2
> (x 2 2) 1
> (x 2 3) 5/4
> (exact->inexact (x 50 250)) 49.5