> 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