lib/games_dice/parser.rb in games_dice-0.1.2 vs lib/games_dice/parser.rb in games_dice-0.1.3
- old
+ new
@@ -1,36 +1,58 @@
require 'parslet'
# converts string dice descriptions to data usable for the GamesDice::Dice constructor
class GamesDice::Parser < Parslet::Parser
- # Descriptive language examples (capital letters stand in for integers)
- # NdXk[Z,worst] - a roll of N dice, sides X, keep worst Z results and sum them
- # NdXr[Z,add] - a roll of N dice, sides X, re-roll and add on a result of Z
- # NdXr[Y..Z,add] - a roll of N dice, sides X, re-roll and add on a result of Y..Z
- # NdXm[>=Z,A] - mapped dice, values greater than or equal to Z score A (unmapped values score 0 by default)
+ # 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.
- # These are the Parslet rules that define the dice grammar
rule(:integer) { match('[0-9]').repeat(1) }
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) }
+ rule(:underscore?) { space.maybe }
+
rule(:bunch_start) { integer.as(:ndice) >> dlabel >> integer.as(:sides) }
rule(:reroll_label) { match(['r']).as(:reroll) }
rule(:keep_label) { match(['k']).as(:keep) }
rule(:map_label) { match(['m']).as(:map) }
rule(:alias_label) { match(['x']).as(:alias) }
rule(:single_modifier) { alias_label }
rule(:modifier_label) { reroll_label | keep_label | map_label }
rule(:simple_modifier) { modifier_label >> integer.as(:simple_value) }
- rule(:complex_modifier) { modifier_label >> str('[') >> str(']') } # TODO: param extraction
+ rule(:comparison_op) { str('>=') | str('<=') | str('==') | str('>') | str('<') }
+ rule(:ctl_string) { match('[a-z_]').repeat(1) }
+ rule(:output_string) { match('[A-Za-z0-9_]').repeat(1) }
- rule(:bunch_modifier) { single_modifier | simple_modifier }
+ 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(: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_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(:reroll_params) { condition_type_and_num | condition_and_type | condition_only }
+ rule(:map_params) { condition_num_and_output | condition_and_num | condition_only }
+
+ rule(:full_reroll) { reroll_label >> str(':') >> reroll_params >> stop }
+ rule(:full_map) { map_label >> str(':') >> map_params >> stop }
+
+ rule(:complex_modifier) { full_reroll | full_map }
+
+ rule(:bunch_modifier) { complex_modifier | ( single_modifier >> stop.maybe ) | ( simple_modifier >> stop.maybe ) }
rule(:bunch) { bunch_start >> bunch_modifier.repeat.as(:mods) }
- rule(:space) { match('\s').repeat(1) }
- rule(:space?) { space.maybe }
+
rule(:operator) { match('[+-]').as(:op) >> space? }
rule(:add_bunch) { operator >> bunch >> space? }
rule(:add_constant) { operator >> integer.as(:constant) >> space? }
rule(:dice_expression) { add_bunch | add_constant }
rule(:expressions) { dice_expression.repeat.as(:bunches) }
@@ -114,12 +136,22 @@
# 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 ]
+ return
end
- # TODO: Handle complex descriptions
+ # 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
+
+ if reroll_mod[:num]
+ out_hash[:rerolls] << [ v, op, type, reroll_mod[:num].to_i ]
+ else
+ out_hash[:rerolls] << [ v, op, type ]
+ end
end
# Called for any parsed keeper mode
def collect_keeper_rule keeper_mod, out_hash
if keeper_mod[:simple_value]
@@ -135,9 +167,24 @@
out_hash[:maps] ||= []
if map_mod[:simple_value]
out_hash[:maps] << [ map_mod[:simple_value].to_i, :<=, 1 ]
return
end
- # TODO: Handle complex descriptions
+
+ # Typical
+ 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 = {
+ '==' => :==,
+ '>=' => :<=,
+ '>' => :<,
+ '<' => :>,
+ '<=' => :>=,
+ }
+
+ def get_op_symbol parsed_op_string
+ OP_CONVERSION[ parsed_op_string.to_s ]
end
end # class Parser