> Leexer/parsers?  We ain't got no Leexer/parsers. We don't need no
> Leexer/parsers. I don't have to show you any steenking Leexer/parser.
> Just call eval and use Ruby's fine lexer/parser  (apologies to Mel
> Brooks, John Huston and Banditos Mexicanos everywhere).
>
> This approach uses regex substitutions to first munge the input
> expression to deal with the default cases (like d6 to 1d6 and 1% to
> 1d100), then it substitutes ** for d and hands it over to the
> evaluator and prints the result.
>

> Conveniently, ** has the desired
> precedence relative to the other operators, plus it is binary and
> left-associative.  This feels so evil.  Seduced by the Dark Side I am.
>


I used the same approach, but found that ** is right-associative (as  
it's generally defined outside of Ruby).  To confirm the  
associativity for yourself, try this: 2**3**4.  If it's left  
associative, it should equal 8**4 (4096), right-associativity gives  
2**81 (a lot).  I ended up doing a lot more redefining and mucking  
about:

Dice Ruby
  d    *
  *    +
  /    -
  +    <<
  -    >>

Interestingly, the difference between a left-associating and a right- 
associating 'd' operator isn't particularly visible from the 'loaded- 
dice' testing common on the list.  For example, 2d2d6 gives a maximum  
of 24 whichever associativity is used, but the distributions of the  
two solutions are vastly different; the left-associative result has a  
minimum value of 4, the right-associative result has a minimum of 2.

Here's my solution, which maintains correct associativity for 'd'  
according to the initial quiz, but does a lot more mucking about with  
Fixnum:

matthew smillie.

#!/usr/local/bin/ruby

class Fixnum
   alias old_mult *
   alias old_div /
   alias old_plus +
   alias old_minus -

   def >>(arg) old_minus(arg) end
   def <<(arg) old_plus(arg) end
   def -(arg) old_div(arg) end
   def +(arg) old_mult(arg) end

   def *(arg)
     sum = 0
     self.times do
       sum = sum.old_plus(rand(arg).old_plus(1))
     end
     sum
   end
end

class Dice
   def initialize(str)
     # make assumed '1's explicit - do it twice to cover cases
     # like '3ddd6' which would otherwise miss one match.
     @dice = str.gsub(/([+\-*\/d])(d)/) { |s| "#{$1}1#{$2}" }
     @dice = @dice.gsub(/([+\-*\/d])(d)/) { |s| "#{$1}1#{$2}" }
     # sub all the operators.
     @dice = @dice.gsub(/\+/, "<<")
     @dice = @dice.gsub(/-/, ">>")
     @dice = @dice.gsub(/\*/, "+")
     @dice = @dice.gsub(/\//, "-")
     @dice = @dice.gsub(/d/, "*")
   end

   def roll
     eval(@dice)
   end
end

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




----
Matthew Smillie            <M.B.Smillie / sms.ed.ac.uk>
Institute for Communicating and Collaborative Systems
University of Edinburgh