```--------------020201010307080107070209
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Content-Transfer-Encoding: 7bit

Attached is my submission.  It looks pretty cool to me, but then this is
only my second-ever Ruby program.

Meta-comment: if [QUIZ] opens the quiz, then surely [/QUIZ] should close it.

Luke Blanshard

--------------020201010307080107070209
Content-Type: text/plain;
nameoll.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
filenameoll.rb"

#!/usr/bin/ruby

#  uby-talk Quiz #61: Dice Roller
#   Usage: roll.rb <expr> [n]
#     Evaluates <expr>; if n is given, evaluates n times.  Expressions
#     are "dice expressions" as in D&D.  For example, 3d6 means the
#     total of 3 6-sided dice.
#
#  Design
#
# We use a recursive-descent parser, Dice#parse, to convert the
# expression into a tree.  The leaves of the tree are of class
# Integer, and the interior nodes, of class Dice::BinOp, represent the
# binary operations of the expression.  We are able to treat all nodes
# of the tree equally by augmenting Integer with the diceEval method.

# Add the #diceEval and #diceRoll methods to Integer.  #diceEval makes
# an Integer look like a Dice::BinOp, so it can function as a node in
# the parse tree.  And #diceRoll is our implementation of the basic
# dice rolling operation, which sums n rolls of an m-sided die, where
# n is the self Integer and m is the "sides" argument.
class Integer
# The parse-tree evaluation method.  For integers, the result is
# always self.
def diceEval
self
end
# The binary operator that rolls n m-sided dice and returns the sum.
def diceRoll sides
raise "Dice can't have #{sides} sides" if sides <
(1..self).inject(0) {|sum, i| sum + 1 + rand(sides)}
end
end

# Add some methods to help with using an array as our token stream.
class Array
# Shifts first element off, returns self
def consume
shift; self
end
# Checks for empty, raises parse error
def ensureNotEmpty desc
raise "Parse error: expected "+desc+" at end of input" if empty?
end
end

# The Dice module contains the BinOp class and the #parse method, a
# recursive-descent parser.  The parser returns a tree representing
# the parsed expression that responds to the #diceEval method by
# evaluating the expression.
module Dice

# Represents a binary operation in the parse tree returned by
# #parse.  Contains the synbol of a binary operator on Integer and
# two other nodes in the parse tree.
class BinOp
# Creates the binary operation node with the given "op" symbol and
# two children nodes.
def initialize op, nodeA, nodeB
@op, @a, @b  p, nodeA, nodeB
end
# Evaluates the two children nodes, then executes the binary
# operator.
def diceEval
@a.diceEval.send @op, @b.diceEval
end
end

# A recursive-descent parser that understands "dice expressions."
# Produces an object that evaluates the given dice expression in
# response to the #diceEval method.
def Dice::parse str
tokens  tr.scan /(?:[1-9][0-9]*)|(?:\S)/
raise "Parse error: extra tokens at end of expression: #{tokens}" if not tokens.empty?
end

private
def Dice::expr tokens
until tokens.empty?
case tokens[0]
else break
end
end
end
def Dice::factor tokens
until tokens.empty?
case tokens[0]
else break
end
end
end
def Dice::term tokens
tokens.ensureNotEmpty "number, (, or d"
answer  if tokens[0] "d" then 1 else primary tokens end)
until tokens.empty?
case tokens[0]
else break
end
end
end
def Dice::diceArg tokens
tokens.ensureNotEmpty "number, (, or %"
if tokens[0] "%" then tokens.consume; 100 else primary tokens end
end
def Dice::primary tokens
tokens.ensureNotEmpty "number or ("
case tokens[0]
when "("
raise "Parse error: expected )" if tokens.empty? or tokens.shift ! )"
when /^[1-9]/
else
raise "Parse error: unexpected token '#{tokens[0]}'"
end
end
end

# Main program
d  ice::parse ARGV[0]
\$,, \$\    ", "\n" # Set the field and record terminators
print (1..(ARGV[1] || 1).to_i).collect { d.diceEval }

# Uncomment to dump the structure in readable form
#require "yaml"
#print YAML::dump(d)

--------------020201010307080107070209--

```