[SOLUTION] Dice Roller (#61)

My solution send out 17 hours ago from gmail.
But it seems the mailing list still not receive it.
So, I just re-send it from Ruby Forum.
( If later on, gmail does send out, please ignore it, my appologize )

---------- Forwarded message ----------
From: David T. [email protected]
Date: Jan 9, 2006 3:57 PM
Subject: [SOLUTION] Dice Roller (#61)
To: [email protected]

Personally, I like to use parser tools to solve this quiz instead of
‘eval’.
First try is with Rockit (Rockit download | SourceForge.net),
unfortunately I cannot get it work with Ruby 1.8.x
So, I try with ANTLR.
The ANTLR 3.0 early access version will suport Ruby language.
You can download and see an example at
split-s: ANTLR for Ruby

Unfortunately, ANTLR 3.0 syntax is kind of different than what I know
( version 2.6.7)
I am not successful to use direct return value, neither generate AST
Tree.
Anyway, at least, by using ANTLR 3.0, my solution does solve this quiz.

File: roll.rb

Ideally it is better generate an “simplified” AST tree"

for example expression “3d6+(1+2*5)” will have “3d6+11” AST Tree,

and each roll operation will just do the tree walking to calculate the

value.

Unfortunately, with ANTLR 3ea7, the syntax change from version 2.7.6

and I am not able to create an AST Tree for Ruby language.

This forces me to reparse for each roll operation. Really bad :(.

require ‘DiceCalculator’
require ‘DiceCalculatorLexer’

class Dice
def initialize(input)
@sstream = StringStream.new(input)
end

def roll
lexer =
DiceCalculatorLexer.new(ANTLR::CharStream.new(@sstream.rewind))
parser = DiceCalculator.new(ANTLR::TokenStream.new(lexer))
parser.parse
parser.result
end

help class, treat String as IO

class StringStream
def initialize(str)
@str = str
@pos = 0
end

def read(n)
return nil if @pos >= @str.size
s = @str[@pos, n]
@pos += n
s
end

def rewind
@pos = 0
self
end
end
end

if $0 == FILE
abort(“Usage: #$0 expression [count]”) if ARGV.size <= 0
d = Dice.new(ARGV[0])
(ARGV[1] || 1).to_i.times { print "#{d.roll} " }
end

File roll.g

/*
DiceCalculator Grammar

Date: 2006-01-09

ANTLR 3.0 Early Access (alpha?) will support Ruby language

However I am not successful to create AST tree by using ANTLR 3ea7
The Syntax changes a lot from version 2.7.6.

Also I am not successful to use direct “returns [value]” syntax to
allow each expression returns a value; so use a @stack variable
to do the calculation.
*/

grammar DiceCalculator;

options {
language = Ruby;
}

@members {
@stack = []

def result
@stack.first
end
}

parse
: expr
;

expr
: mexpr
( PLUS mexpr { @stack.push(@stack.pop + @stack.pop) }
| MINUS mexpr { n = @stack.pop; @stack.push(@stack.pop - n) }
)*
;

mexpr
: term
( MULTI term { @stack.push(@stack.pop * @stack.pop) }
| DIVIDE term { n = @stack.pop; @stack.push(@stack.pop / n) }
)*
;

term
: (unit | { @stack.push(1) })
(DICE (PERCENT { @stack.push(100) } | unit)
{
side = @stack.pop
time = @stack.pop
result = 0
time.times { result += rand(side) + 1 }
@stack.push(result)
}
)*
;

unit
: INTEGER { @stack.push($INTEGER.text.to_i) }
| LPAREN n=expr RPAREN
;

LPAREN : ‘(’ ;
RPAREN : ‘)’ ;
PLUS : ‘+’ ;
MINUS : ‘-’ ;
MULTI : ‘’ ;
DIVIDE : ‘/’ ;
PERCENT : ‘%’ ;
DICE : ‘d’ ;
INTEGER : (‘1’…‘9’)(‘0’…‘9’)
;
WS : (’ ’ | ‘\t’ | ‘\n’ | ‘\r’) { channel = 99; };

================================================================
use java to create parser ruby file by:
java -cp antlr-3.0ea7.jar;stringtemplate-2.3b4.jar;antlr-2.7.5.jar
org.antlr.Tool roll.g
This will generate lexer and parser ruby files, you also need ANTLR
ruby runtime
library (antlr.rb).

In case you like to try it and don’t like use java and antlr to
regenerate:
Below are a zip file (encoded base 64) contents
ANTLR runtime library file (antlr.rb) and
DiceCalculator.rb and DiceCalculatorLexer.rb generated files.

File antlr_gen.zip (base 64 encoded)

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