## Thursday, July 1, 2010

### Rolling Dice

Update: I noticed that random is `[0,b)` (from `0` to `n-1`). The code has been fixed to make it `[1,b]` (e.g., return a number from 1 to n).

I was curious about the ability to define new syntax for Factor, and to learn a little about how the lexer works. Inspired by a recent project that I've been working on, I thought it would be interesting to define a simple DSL for calculating dice rolls.

We need a way to describe a dice roll. There are various methods used in Python or Ruby. A common example of a common short-hand description is `4d8` -- specifying how many times (`4`) to roll a dice with a number (`8`) of sides.

Let's setup a vocabulary and a list of dependencies that will be used to implement this functionality:

```USING: fry kernel lexer math math.parser peg.ebnf random
sequences strings ;

IN: dice```

We will be using EBNF parsers similar to what was used in building a calculator. First we will implement support for basic (e.g., `4d8`) rolls.

```EBNF: parse-roll

number = ([0-9])+    => [[ >string string>number ]]
dice   = "d" number  => [[ second '[ _ random ] ]]
roll   = number dice => [[ first2 '[ 0 _ [ @ + 1 + ] times ] ]]
error  = .*          => [[ "unknown dice" throw ]]
total  = roll | error

;EBNF```

We can see how this works by trying it out:

```( scratchpad ) "4d8" parse-roll .
[ 0 4 [ 8 random + 1 + ] times ]

( scratchpad ) "4d8" parse-roll call .
15

unknown dice```

We can now define a piece of SYNTAX: for dice rolls:

`SYNTAX: ROLL: scan parse-roll append ;`

And using it:

```( scratchpad ) ROLL: 4d8 .
13```

This could be extended to support other things such as "base" numbers (e.g., `4d8+10`), negative rolls, ranges, and better handling of parse errors. The code for this is available on my Github.