------art_81463_5176244.1136752140088
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

Here is my submission. Yes, this is my first completed Ruby Quiz ;)
Thanks to Eric Mahurin's syntax.rb for making this work. I've attached
it as well, because it's not easily accessible otherwise ;)

-austin

------art_81463_5176244.1136752140088
Content-Type: application/octet-stream; name=roll.rb
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="roll.rb"

#! /usr/bin/env ruby

require 'syntax'

# Ruby Quiz #61 by Matthew D Moss
# Submission by Austin Ziegler
# 
#   > roll.rb "3d6" 6
#   12 7 13 16 11 17
# 
# Or, for something more complicated:
# 
#   > roll.rb "(5d5-4)d(16/d4)+3"
#   31
# 
# The main code of roll.rb should look something like this:
# 
#   d  ice.new(ARGV[0])
#   (ARGV[1] || 1).to_i.times { print "#{d.roll}  " }
# 
# I've implemented it with a modified BNF and Eric Mahurin's syntax.rb.
#
# integer : "1" - "9" [ "0" - "9" ]*
# white   : [ " " | "\t" | "\n" ]*
# unit    : "(" expr ")" | integer
# dice    : "%" | unit
# term    : unit? [ "d" dice ]*
# fact    : term [ "*" | "/" term ]*
# expr    : fact [ "+" | "-" fact ]*
#
# I have also modified the core function as:
#
#   e  RGV[0]
#   c  ARGV[1] || 1).to_i
#   d  ice.new(e)
#   puts d.roll(c).join(" ")

NULL     yntax::NULL
INF      1.0 / 0.0
LOOP0    0 .. INF)
LOOP1    1 .. INF)

class Dice
  def initialize(dice)
    @dice      ice
    @dice_n    #{@dice}\n"

    integer    (("1" .. "9") * 1) + ("0" .. "9") * LOOP0).qualify do |m|
      m.to_s.to_i
    end
    white      (" " | "\t" | "\n") * LOOP0).qualify { TRUE }

    expr       yntax::Pass.new

    unit       "(" + expr + ")").qualify { |m| m[1] } |
      integer.qualify { |m| m }

    dice       %".qualify { |m| 100 } | unit.qualify { |m| m }

    term       (unit | NULL) + (white + "d" + white + dice) * LOOP0).qualify do |m|
      sum  

      if m[1].nil?
        rep  
        xpr  [0]
      elsif m[1].empty?
        sum  [0]
        xpr  [1]
      else
        rep  [0]
        xpr  [1]
      end

      xpr.each do |mm|
        case mm[0]
        when "d": sum  1..rep).inject(sum) { |s, i| s + (rand(mm[1]) + 1) }
        else
          sum + ep
        end
      end
      sum
    end

    fact       term + (white + ("*" | "/") + white + term) * LOOP0).qualify do |m|
      prod  [0]

      m[1].each do |mm|
        case mm[0]
        when "*": prod * m[1]
        when "/": prod / m[1]
        end
      end

      prod
    end

    expr     << (white + fact + (white + ("+" | "-") + white + fact) * LOOP0).qualify do |m|
      sum  [0]
      m[1].each do |mm|
        case mm[0]
        when "+": sum + m[1]
        when "-": sum - m[1]
        end
      end
      sum
    end

    @die_expr  xpr
  end

  def roll(times  )
    (1 .. times).map { @die_expr  RandomAccessStream.new(@dice_n) }
  end

  def inspect
    @dice
  end
end

expr   RGV[0]
count  ARGV[1] || 1).to_i

if expr
  d  ice.new(expr)

  puts d.roll(count).join(' ')
else
  require 'test/unit'

  class TestDice < Test::Unit::TestCase
    def test_simple
      assert (1..4).include?(Dice.new("d4").roll)
      assert (1..6).include?(Dice.new("d6").roll)
      assert (1..8).include?(Dice.new("d8").roll)
      assert (1..10).include?(Dice.new("d10").roll)
      assert (1..12).include?(Dice.new("d12").roll)
      assert (1..20).include?(Dice.new("d20").roll)
      assert (1..30).include?(Dice.new("d30").roll)
      assert (1..100).include?(Dice.new("d100").roll)
      assert (1..100).include?(Dice.new("d%").roll)
    end

    def test_3d6
      assert (3..18).include?(Dice.new("3d6").roll)
    end

    def test_complex
      assert (5..25).include?(Dice.new("5d5").roll)
      assert (1..21).include?(Dice.new("5d5-4").roll)
      assert [4, 5, 8, 16].include?(Dice.new("16/d4").roll)
      assert (1..336).include?(Dice.new("(5d5-4)d(16/d4)").roll)
      assert (4..339).include?(Dice.new("(5d5-4)d(16/d4)+3").roll)
    end
  end
end

------art_81463_5176244.1136752140088
Content-Type: application/octet-stream; name=syntax.rb
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="syntax.rb"

# Classes in this module allow one to define BNF-like grammar directly in
#   Ruby
# Author: Eric Mahurin
# License: free, but you are at your own risk if you use it

module Syntax
  # base class where common operators are defined
  class Base
    def |(other)
      Alteration.new(self,other)
    end
    def +(other)
      Sequence.new(self,other)
    end
    def *(multiplier)
      Repeat.new(self,multiplier)
    end
    def +@
      Positive.new(self)
    end
    def -@
      Negative.new(self)
    end
    def qualify(*args,&code)
      Qualify.new(self,*args,&code)
    end
  end

  # just passes the syntax through - needed for recursive syntax
  class Pass < Base
    def initialize(syntaxLL)
      @syntax if (syntax.kind_of?Base)
          syntax
        else
          Verbatim.new(syntax)
        end
    end
    def <<(syntax)
      initialize(syntax)
    end
    def (stream)
      @syntaxstream
    end
  end

  # generic code matches to the stream (first arg to the code)
  # [] operator allows additional arguments to be passed to the code
  class Code < Base
    def initialize(*args,&code)
      @args  rgs
      @code  ode
    end
    def (stream,*args) # passing args here will bypass creating a new object
      (match  code[stream,*(@args+args)]) ||
        stream.buffered || raise(Error.new(stream,"a semantic error"))
      match
    end
    def [](*args)
      self.class.new(*(@args+args),&@code)
    end
  end

  # qualify the match with some code that takes the match
  # [] operator allows additional arguments to be passed to the code
  class Qualify < Base
    def initialize(syntaxLL,*args,&code)
      @syntax if (syntax.kind_of?Base)
          syntax
        else
          Verbatim.new(syntax)
        end
      @args  rgs
      @code  ode
    end
    def (stream,*args) # passing args here will bypass creating a new object
      (match  @syntaxstream)) || (return match)
      (match  code[match,*(@args+args)]) ||
        stream.buffered || raise(Error.new(stream,"a semantic qualification error"))
      match
    end
    def [](*args)
      self.class.new(@syntax,*(@args+args),&@code)
    end
  end

  # sequence of syntaxes
  class Sequence < Base
    def initialize(*syntaxes)
      @syntax  yntaxes.collect do |syntax|
        if (syntax.kind_of?Base)
          syntax
        else
          Verbatim.new(syntax)
        end
      end
    end
    def +(other)
      self.class.new(*(@syntax+[other])) # pull in multiple sequence items
    end
    def <<(other)
      @syntax << ((other.kind_of?Base)?other:Verbatim.new(other))
    end
    def (stream)
      matches  ]
      @syntax.each do |syntax|
        match  syntaxstream)
        if (!match)
          matchesL
          break
        end
        matches << match if match!UE
      end
      matches
    end
  end

  # alternative syntaxes
  class Alteration < Base
    def initialize(*syntaxes)
      @syntax  yntaxes.collect do |syntax|
        if (syntax.kind_of?Base)
          syntax
        else
          Verbatim.new(syntax)
        end
      end
    end
    def |(other)
      self.class.new(*(@syntax+[other])) # pull in multiple alteration items
    end
    def <<(other)
      @syntax << ((other.kind_of?Base)?other:Verbatim.new(other))
    end
    def (stream)
      match  il
      @syntax.detect do |syntax|
        match  tream.buffer { |stream| syntaxstream }
      end
      match || stream.buffered || raise(Error.new(stream,nil,"an alteration"))
      match
    end
    alias 
  end

  # repeating syntax
  class Repeat < Base
    def initialize(syntax,multiplier)
      @syntax if (syntax.kind_of?Base)
          syntax
        else
          Verbatim.new(syntax)
        end
      @repeat if (multiplier.kind_of?Proc)
          multiplier
        else
          lambda do |matches|
            compare  multiplier<matches.length+1))
            if (compare)
              compare  multiplier<atches.length)
            end
            compare
          end
        end
    end
    def (stream)
      matches  ]
      while ((compareepeat[matches])>
        if (compare>0)
          unless (match  @syntaxstream))
            return NIL
          end
        else
          unless (match  tream.buffer { |stream| @syntaxstream })
            break
          end
        end
        matches << match
      end
      # filter out simple TRUE elements
      matches  atches.find_all { |match| match!UE }
      matches
    end
  end

  # positive syntactic predicate
  class Positive < Base
    def initialize(syntax)
      @syntax if (syntax.kind_of?Base)
          syntax
        else
          Verbatim.new(syntax)
        end
    end
    def (stream)
      stream.buffer { |stream| match  @syntaxstream); FALSE }
      if (match)
        TRUE
      else
        stream.buffered || raise(Error.new(stream,nil,"a positive syntatic predicate"))
        FALSE
      end
    end
  end

  # negative syntactic predicate
  class Negative < Positive
    def (stream)
      stream.buffer { |stream| match  @syntaxstream); FALSE }
      if (!match)
        TRUE
      else
        stream.buffered || raise(Error.new(stream,nil,"a negative syntatic predicate"))
        FALSE
      end
    end
  end

  # all atoms can also use ~ to invert what's matches

  # element match (uses  to match)
  class Atom < Base
    def initialize(pattern,lengthL,invertSE)
      @pattern  attern
      @length  ength
      @invert  nvert
    end
    def ~@
      new(pattern,length,!invert)
    end
    def (stream)
      element  tream.get(@length)
      match  @patternelement)
      match  match if (@invert)
      if (matchRUE)
        element || TRUE
      else
        match || begin
        stream.buffered || raise(Error.new(stream,element.inspect,@pattern.inspect))
        FALSE
      end
    end
  end
end

# element set (uses include? to match)
class Set < Atom
  def (stream)
    element  tream.get(@length)
    match  pattern.include?(element)
    match  match if (@invert)
    if (matchRUE)
      element || TRUE
    else
      match || begin
      stream.buffered || raise(Error.new(stream,element.inspect,"one of these: #{@pattern.to_s}"))
      FALSE
    end
  end
end
    end

    # element lookup array or hash (uses [] to match)
    # translation will occur if the lookup returns anything but TRUE
    class Lookup < Atom
      def stream)
        element  tream.get(@length)
        match  pattern[element]
        match  match if (@invert)
        if (matchRUE)
          element || TRUE
        else
          match || begin
          stream.buffered || raise(Error.new(stream,element.inspect,"one of these: #{@pattern.keys.to_s}"))
          FALSE
        end
      end
    end
    end

    # element sequence that knows its length
    class Verbatim < Atom
      def initialize(pattern,invertSE)
        @pattern  attern
        @invert  nvert
      end
      def ~@
        new(pattern,!invert)
      end
      def (stream)
        element  tream.get(@pattern.length)
        if (element)
          match  @patternelement)
          match  match if (@invert)
        else
          match  ALSE
        end
        if (matchRUE)
          element || TRUE
        else
          match || begin
          stream.buffered || raise(Error.new(stream,element.inspect,@pattern.inspect))
          FALSE
        end
      end
    end
    end

    # any element
    class Any < Atom
      def initialize(lengthL,invertSE)
        @length  ength
        @invert  nvert
      end
      def ~@ # create a never matching Atom
        new(length,!invert)
      end
      def (stream)
        element  tream.get(@length)
        !@invert && element
      end
    end
    ANY  ny.new


    # zero length constants
    FLUSH  ode.new { |stream| stream.flush; TRUE }
    FAIL  ode.new { FALSE }
    NULL  ode.new { TRUE }
    NULLS  ode.new { [] }
    EOF  ode.new { !(element  tream.get) }

    # exception class for handling syntax errors
    class Error < RuntimeError
      attr_accessor(:stream,:found,:expected)
      def initialize(streaml,foundl,expectedl)
        @stream  tream
        @found  ound
        @expected  xpected
      end
      def to_s
        err  super]
        err << "found #{found.to_s}" if found
        err << "expected #{expected.to_s}" if expected
        err << stream.location.to_s if stream
        err * ", "
      end
    end

end

# class acts like an iterator over a string/array/etc except that using
# buffer allows one go back to a certain point another class could be
# designed to work on an IO/File
class RandomAccessStream
  def initialize(s,pos
    @s  
    @pos  os
    @buffered  IL
    self
  end
  def get(terminationL)
    if (@pos>.length)
      # end of file/string/array
      element  IL
    elsif (!termination)
      # read one character/element
      element  s[@pos]
      @pos + 
    else
      # read a sub-string/sub-array
      pos1  termination.kind_of?(Integer)) ? @pos+termination :
        (t  s.index(termination,@pos)) ? t+termination.length :
        @s.length
      element  s[@pos...pos1]
      @pos  os1
    end
    element
  end
  def buffer(&code)
    old_buffered  buffered
    @buffered  pos if (!@buffered || @pos<@buffered)
    pos  pos
    match  IL
    match  ode[self]
    if (@buffered && @buffered<s)
      @buffered  ld_buffered
    elsif (!match)
      raise(IndexError,"need to rewind buffer, but it was flushed")
    end
    @pos  os if !match
    match
  end
  def flush
    @buffered  IL
  end
  def buffered
    @buffered ? TRUE : FALSE
  end
  def location
    "index #{@pos} in #{@s.inspect}"
  end
end

# Put stuff in String to have Syntax objects magically appear.
class String
  def |(other)
    Syntax::Verbatim.new(self)|other
  end
  def +@
    +Syntax::Verbatim.new(self)
  end
  def -@
    -Syntax::Verbatim.new(self)
  end
  alias _repeat *
    def *(other)
      if (other.kind_of?Numeric)
        _repeat(other)
      else
        Syntax::Verbatim.new(self)*other
      end
    end
  alias _concat +
    def +(other)
      if (other.kind_of?String)
        _concat(other)
      else
        Syntax::Verbatim.new(self)+other
      end
    end
  def (other)
    if (other.kind_of?String)
      selfther
    else
      Syntax::Verbatim.new(self)other
    end
  end
  def qualify(&code)
    Syntax::Verbatim.new(self).qualify(&code)
  end
end

# Allow an Array to look more like a Hash with keys and values
class Array
  def keys
    (0...length).find_all { |i| self[i] }
  end
  def values
    find_all { | element | element }
  end
end

# make things fully comparable to Ranges
# also * makes a Syntax
class Range
  include Comparable
  def <other)
    if (other<self.begin)
      +1
    elsif (if exclude_end? then other>ョ  セョ ゥ
    ュア
  
    ー
  

 ゜゜    ゥ
     ィョ゜ソメゥ
      」   
      ゜゜ィゥ
    
      ィシゥ
    
  
 ェィゥ
  モココチョィャアゥェ



ュュュュュュ゜クアエカウ゜オアキカイエエョアアウカキオイアエーーククュュ