```Here's my solution.

I spent a few hours writing a BNF parser which was supposed to let me do this:

-- begin buggy code --
CENT = BnfTerm.new(/(%)/ ) { '100' }
INTEGER = /([1-9][0-9]*)/
DICE = BnfTerm.new(CENT,:|,INTEGER)
term = BnfTerm.new()
ROLL = BnfTerm.new(term, /d/, DICE) {|a,b|
(1..a.to_i).inject(0){|s,i|s+rand(b.to_i)+1} }
term.define(DICE, :|,ROLL) {|m| m}
#...
class Dice
@@rule = DIEROLL
def initialize expr
@expr = expr
end
def roll
@@rule.parse(@expr)
end
end
-- end --
but it was too brittle, and it would go into endless recursion on a
lot of valid inputs.

So I switched to a quick,short simple solution: add a #d method to
integer and let eval do the work:

--- dice.rb --
class Integer
def d n
(0...self).inject(0){|s,i| s+rand(n)+1}
end
end

class Dice
def initialize str
@rule= str.gsub(/%/,'100').gsub(/([^\d)]|^)d/,'\1 1d')  # %->100
and bare d ->1d
while @rule.gsub!(/([^.])d(\d+|\(.*\))/,'\1.d(\2)')          #
'dX' ->  '.d(X)'
end
#repeat to deal with nesting
end
def roll
eval(@rule)
end
end

d = Dice.new(ARGV[0]||'d6')
(ARGV[1] || 1).to_i.times { print "#{d.roll}  " }
puts

---