lib/games_dice/parser.rb in games_dice-0.1.3 vs lib/games_dice/parser.rb in games_dice-0.2.0
- old
+ new
@@ -1,14 +1,13 @@
require 'parslet'
# converts string dice descriptions to data usable for the GamesDice::Dice constructor
class GamesDice::Parser < Parslet::Parser
- # These are the Parslet rules that define the dice grammar. It's an inefficient and over-complex
- # use of Parslet, and could do with logical a clean-up.
-
+ # Parslet rules that define the dice string grammar.
rule(:integer) { match('[0-9]').repeat(1) }
+ rule(:plus_minus_integer) { ( match('[+-]') >> integer ) | integer }
rule(:range) { integer.as(:range_start) >> str('..') >> integer.as(:range_end) }
rule(:dlabel) { match('[d]') }
rule(:space) { match('\s').repeat(1) }
rule(:space?) { space.maybe }
rule(:underscore) { str('_').repeat(1) }
@@ -31,24 +30,29 @@
rule(:opint_or_int) { (comparison_op.as(:comparison) >> integer.as(:compare_num)) | integer.as(:compare_num) }
rule(:comma) { str(',') }
rule(:stop) { str('.') }
rule(:condition_only) { opint_or_int.as(:condition) }
+ rule(:num_only) { integer.as(:num) }
+
rule(:condition_and_type) { opint_or_int.as(:condition) >> comma >> ctl_string.as(:type) }
- rule(:condition_and_num) { opint_or_int.as(:condition) >> comma >> integer.as(:num) }
+ rule(:condition_and_num) { opint_or_int.as(:condition) >> comma >> plus_minus_integer.as(:num) }
rule(:condition_type_and_num) { opint_or_int.as(:condition) >> comma >> ctl_string.as(:type) >> comma >> integer.as(:num) }
- rule(:condition_num_and_output) { opint_or_int.as(:condition) >> comma >> integer.as(:num) >> comma >> ctl_string.as(:output) }
+ rule(:condition_num_and_output) { opint_or_int.as(:condition) >> comma >> plus_minus_integer.as(:num) >> comma >> output_string.as(:output) }
+ rule(:num_and_type) { integer.as(:num) >> comma >> ctl_string.as(:type) }
rule(:reroll_params) { condition_type_and_num | condition_and_type | condition_only }
rule(:map_params) { condition_num_and_output | condition_and_num | condition_only }
+ rule(:keeper_params) { num_and_type | num_only }
rule(:full_reroll) { reroll_label >> str(':') >> reroll_params >> stop }
rule(:full_map) { map_label >> str(':') >> map_params >> stop }
+ rule(:full_keepers) { keep_label >> str(':') >> keeper_params >> stop }
- rule(:complex_modifier) { full_reroll | full_map }
+ rule(:complex_modifier) { full_reroll | full_map | full_keepers }
rule(:bunch_modifier) { complex_modifier | ( single_modifier >> stop.maybe ) | ( simple_modifier >> stop.maybe ) }
rule(:bunch) { bunch_start >> bunch_modifier.repeat.as(:mods) }
rule(:operator) { match('[+-]').as(:op) >> space? }
@@ -56,17 +60,15 @@
rule(:add_constant) { operator >> integer.as(:constant) >> space? }
rule(:dice_expression) { add_bunch | add_constant }
rule(:expressions) { dice_expression.repeat.as(:bunches) }
root :expressions
- def parse dice_description, dice_name = nil
+ def parse dice_description
dice_description = dice_description.to_s.strip
- dice_name ||= dice_description
# Force first item to start '+' for simpler parse rules
dice_description = '+' + dice_description unless dice_description =~ /\A[+-]/
dice_expressions = super( dice_description )
-
{ :bunches => collect_bunches( dice_expressions ), :offset => collect_offset( dice_expressions ) }
end
private
@@ -135,13 +137,14 @@
# Called for any parsed reroll rule
def collect_reroll_rule reroll_mod, out_hash
out_hash[:rerolls] ||= []
if reroll_mod[:simple_value]
- out_hash[:rerolls] << [ reroll_mod[:simple_value].to_i, :>=, :reroll_replace, 1 ]
+ out_hash[:rerolls] << [ reroll_mod[:simple_value].to_i, :>=, :reroll_replace ]
return
end
+
# Typical reroll_mod: {:reroll=>"r"@5, :condition=>{:compare_num=>"10"@7}, :type=>"add"@10}
op = get_op_symbol( reroll_mod[:condition][:comparison] || '==' )
v = reroll_mod[:condition][:compare_num].to_i
type = ( 'reroll_' + ( reroll_mod[:type] || 'replace' ) ).to_sym
@@ -152,26 +155,42 @@
end
end
# Called for any parsed keeper mode
def collect_keeper_rule keeper_mod, out_hash
+ raise "Cannot set keepers for a bunch twice" if out_hash[:keep_mode]
if keeper_mod[:simple_value]
out_hash[:keep_mode] = :keep_best
out_hash[:keep_number] = keeper_mod[:simple_value].to_i
return
end
- # TODO: Handle complex descriptions
+
+ # Typical keeper_mod: {:keep=>"k"@5, :num=>"1"@7, :type=>"worst"@9}
+ out_hash[:keep_number] = keeper_mod[:num].to_i
+ out_hash[:keep_mode] = ( 'keep_' + ( keeper_mod[:type] || 'best' ) ).to_sym
end
# Called for any parsed map mode
def collect_map_rule map_mod, out_hash
out_hash[:maps] ||= []
if map_mod[:simple_value]
out_hash[:maps] << [ map_mod[:simple_value].to_i, :<=, 1 ]
return
end
- # Typical
+ # Typical map_mod: {:map=>"m"@4, :condition=>{:compare_num=>"5"@6}, :num=>"2"@8, :output=>"Qwerty"@10}
+ op = get_op_symbol( map_mod[:condition][:comparison] || '>=' )
+ v = map_mod[:condition][:compare_num].to_i
+ out_val = 1
+ if map_mod[:num]
+ out_val = map_mod[:num].to_i
+ end
+
+ if map_mod[:output]
+ out_hash[:maps] << [ v, op, out_val, map_mod[:output].to_s ]
+ else
+ out_hash[:maps] << [ v, op, out_val ]
+ end
end
# The dice description language uses (r).op.x, whilst GamesDice::RerollRule uses x.op.(r), so
# as well as converting to a symbol, we must reverse sense of input to constructor
OP_CONVERSION = {