bcdice/BCDice

View on GitHub
lib/bcdice/common_command/add_dice/parser.y

Summary

Maintainability
Test Coverage
class BCDice::CommonCommand::AddDice::Parser
token NUMBER CMP_OP S D K H L M A X I N U R F C PLUS MINUS ASTERISK SLASH PARENL PARENR QUESTION

rule
  command: secret add
         {
           secret, lhs = val
           raise ParseError unless lhs.include_dice?

           result = Node::Command.new(secret, lhs)
         }
         | secret add CMP_OP target
         {
           secret, lhs, cmp_op, rhs = val
           raise ParseError if !lhs.include_dice? || rhs.include_dice? || cmp_op.nil?

           result = Node::Command.new(secret, lhs, cmp_op, rhs)
         }

  secret: /* none */
        { result = false }
        | S
        { result = true }

  target: add
        | QUESTION
        { result = Node::UndecidedTarget.instance }

  add: add PLUS mul
     {
      lhs = val[0]
      op, rhs = expand_negate(:+, val[2])
      result = Node::BinaryOp.new(lhs, op, rhs)
     }
     | add MINUS mul
     {
      lhs = val[0]
      op, rhs = expand_negate(:-, val[2])
      result = Node::BinaryOp.new(lhs, op, rhs)
     }
     | mul

  mul: mul ASTERISK unary
     {
      lhs = val[0]
      rhs = val[2]
      result = Node::BinaryOp.new(lhs, :*, rhs)
     }
     | mul SLASH unary round_type
     {
       lhs = val[0]
       rhs = val[2]
       divied_class = val[3]
       result = divied_class.new(lhs, rhs)
     }
     | unary

  round_type: /* none */
            { result = Node::DivideWithGameSystemDefault }
            | F
            { result = Node::DivideWithRoundingDown }
            | U
            { result = Node::DivideWithRoundingUp }
            | C
            { result = Node::DivideWithRoundingUp }
            | R
            { result = Node::DivideWithRoundingOff }

  unary: PLUS unary
       { result = val[1] }
       | MINUS unary
       {
         body = val[1]
         result = body.is_a?(Node::Negate) ? body.body : Node::Negate.new(body)
       }
       | dice

  dice: term D term
      {
        times = val[0]
        sides = val[2]
        raise ParseError if times.include_dice? || sides.include_dice?

        result = Node::DiceRoll.new(times, sides)
      }
      | term D
      {
        times = val[0]
        raise ParseError if times.include_dice?

        result = Node::ImplicitSidesDiceRoll.new(times)
      }
      | term D explicit_or_implicit_sides filter
      {
        times = val[0]
        sides = val[2]
        filter = val[3][:filter]
        n_filtering = val[3][:n_filtering]

        raise ParseError if sides != :implicit && sides.include_dice?
        raise ParseError if times.include_dice? || n_filtering.include_dice?

        result = Node::DiceRollWithFilter.new(times, sides, n_filtering, filter)
      }
      | D term
      {
        times = Node::Number.new(1)
        sides = val[1]
        raise ParseError if sides.include_dice?
        raise ParseError if sides.instance_of?(Node::Number) && sides.literal == 66

        result = Node::DiceRoll.new(times, sides)
      }
      | D term filter
      {
        times = Node::Number.new(1)
        sides = val[1]
        filter = val[2][:filter]
        n_filtering = val[2][:n_filtering]

        raise ParseError if sides != :implicit && sides.include_dice?
        raise ParseError if n_filtering.include_dice?
        raise ParseError if sides.instance_of?(Node::Number) && sides.literal == 66

        result = Node::DiceRollWithFilter.new(times, sides, n_filtering, filter)
      }
      | term

  explicit_or_implicit_sides: /* 指定なし => 暗黙の面数 */
                            { result = :implicit }
                            | term
                            { result = val[0] }


  filter: filter_type term
        { result = {filter: val[0], n_filtering: val[1]} }
        | filter_type_with_shorthand
        { result = {filter: val[0], n_filtering: Node::Number.new(1)} }

  filter_shorthand: M A X
                  { result = Node::DiceRollWithFilter::KEEP_HIGHEST }
                  | M I N
                  { result = Node::DiceRollWithFilter::KEEP_LOWEST }

  filter_type: K H
             { result = Node::DiceRollWithFilter::KEEP_HIGHEST }
             | K L
             { result = Node::DiceRollWithFilter::KEEP_LOWEST }
             | D H
             { result = Node::DiceRollWithFilter::DROP_HIGHEST }
             | D L
             { result = Node::DiceRollWithFilter::DROP_LOWEST }

  filter_type_with_shorthand: filter_type | filter_shorthand

  term: PARENL add PARENR
      { result = Node::Parenthesis.new(val[1]) }
      | NUMBER
      { result = Node::Number.new(val[0]) }
end

---- header

require "bcdice/common_command/lexer"
require "bcdice/common_command/add_dice/node"

---- inner

def self.parse(source)
  new.parse(source)
end

def parse(source)
  @lexer = Lexer.new(source)
  do_parse()
rescue ParseError
  nil
end

private

def next_token
  @lexer.next_token
end

# 加減算の右辺が負数である場合に加減算を逆転させる
def expand_negate(op, rhs)
  if rhs.is_a?(Node::Negate)
    if op == :+
      return [:-, rhs.body]
    elsif op == :-
      return [:+, rhs.body]
    end
  end

  [op, rhs]
end