class Cucumber::TagExpressions::Parser

Ruby tag expression parser

Public Class Methods

new() click to toggle source
# File lib/cucumber/tag_expressions/parser.rb, line 7
def initialize
  @expressions = []
  @operators = []

  @operator_types = {
    'or'  => { type: :binary_operator,    precedence: 0, assoc: :left },
    'and' => { type: :binary_operator,   precedence: 1, assoc: :left },
    'not' => { type: :unary_operator,   precedence: 2, assoc: :right },
    ')'   => { type: :close_paren,       precedence: -1 },
    '('   => { type: :open_paren,        precedence: 1 }
  }
end

Public Instance Methods

parse(infix_expression) click to toggle source
# File lib/cucumber/tag_expressions/parser.rb, line 20
def parse(infix_expression)
  expected_token_type = :operand

  tokens = tokenize(infix_expression)
  return True.new if tokens.empty?

  tokens.each do |token|
    if @operator_types[token]
      expected_token_type = send("handle_#{@operator_types[token][:type]}", infix_expression, token, expected_token_type)
    else
      expected_token_type = handle_literal(infix_expression, token, expected_token_type)
    end
  end

  while @operators.any?
    raise %Q{Tag expression "#{infix_expression}" could not be parsed because of syntax error: Unmatched (.} if @operators.last == '('
    push_expression(pop(@operators))
  end
  expression = pop(@expressions)
  @expressions.empty? ? expression : raise('Not empty')
end

Private Instance Methods

assoc_of(token, value) click to toggle source

Helpers

# File lib/cucumber/tag_expressions/parser.rb, line 47
def assoc_of(token, value)
  @operator_types[token][:assoc] == value
end
check(infix_expression, expected_token_type, token_type) click to toggle source
# File lib/cucumber/tag_expressions/parser.rb, line 153
def check(infix_expression, expected_token_type, token_type)
  if expected_token_type != token_type
    raise %Q{Tag expression "#{infix_expression}" could not be parsed because of syntax error: Expected #{expected_token_type}.}
  end
end
handle_binary_operator(infix_expression, token, expected_token_type) click to toggle source
# File lib/cucumber/tag_expressions/parser.rb, line 121
def handle_binary_operator(infix_expression, token, expected_token_type)
  check(infix_expression, expected_token_type, :operator)
  while @operators.any? && operator?(@operators.last) &&
        lower_precedence?(token)
    push_expression(pop(@operators))
  end
  @operators.push(token)
  :operand
end
handle_close_paren(infix_expression, _token, expected_token_type) click to toggle source
# File lib/cucumber/tag_expressions/parser.rb, line 137
def handle_close_paren(infix_expression, _token, expected_token_type)
  check(infix_expression, expected_token_type, :operator)
  while @operators.any? && @operators.last != '('
    push_expression(pop(@operators))
  end
  raise %Q{Tag expression "#{infix_expression}" could not be parsed because of syntax error: Unmatched ).} if @operators.empty?
  pop(@operators) if @operators.last == '('
  :operator
end
handle_literal(infix_expression, token, expected_token_type) click to toggle source
# File lib/cucumber/tag_expressions/parser.rb, line 147
def handle_literal(infix_expression, token, expected_token_type)
  check(infix_expression, expected_token_type, :operand)
  push_expression(token)
  :operator
end
handle_open_paren(infix_expression, token, expected_token_type) click to toggle source
# File lib/cucumber/tag_expressions/parser.rb, line 131
def handle_open_paren(infix_expression, token, expected_token_type)
  check(infix_expression, expected_token_type, :operand)
  @operators.push(token)
  :operand
end
handle_unary_operator(infix_expression, token, expected_token_type) click to toggle source

Handlers

# File lib/cucumber/tag_expressions/parser.rb, line 115
def handle_unary_operator(infix_expression, token, expected_token_type)
  check(infix_expression, expected_token_type, :operand)
  @operators.push(token)
  :operand
end
lower_precedence?(operation) click to toggle source
# File lib/cucumber/tag_expressions/parser.rb, line 51
def lower_precedence?(operation)
  (assoc_of(operation, :left) &&
   precedence(operation) <= precedence(@operators.last)) ||
    (assoc_of(operation, :right) &&
     precedence(operation) < precedence(@operators.last))
end
operator?(token) click to toggle source
# File lib/cucumber/tag_expressions/parser.rb, line 58
def operator?(token)
  @operator_types[token][:type] == :unary_operator ||
      @operator_types[token][:type] == :binary_operator
end
pop(array, n = 1) click to toggle source
# File lib/cucumber/tag_expressions/parser.rb, line 159
def pop(array, n = 1)
  result = array.pop(n)
  raise('Empty stack') if result.size != n
  n == 1 ? result.first : result
end
precedence(token) click to toggle source
# File lib/cucumber/tag_expressions/parser.rb, line 63
def precedence(token)
  @operator_types[token][:precedence]
end
push_expression(token) click to toggle source
# File lib/cucumber/tag_expressions/parser.rb, line 99
def push_expression(token)
  case token
  when 'and'
    @expressions.push(And.new(*pop(@expressions, 2)))
  when 'or'
    @expressions.push(Or.new(*pop(@expressions, 2)))
  when 'not'
    @expressions.push(Not.new(pop(@expressions)))
  else
    @expressions.push(Literal.new(token))
  end
end
tokenize(infix_expression) click to toggle source
# File lib/cucumber/tag_expressions/parser.rb, line 67
def tokenize(infix_expression)
  tokens = []
  escaped = false
  token = ""
  infix_expression.chars.each do | ch |
    if escaped
      if ch == '(' || ch == ')' || ch == '\\' || ch.match(/\s/)
        token += ch
        escaped = false
      else
        raise %Q{Tag expression "#{infix_expression}" could not be parsed because of syntax error: Illegal escape before "#{ch}".}
      end
    elsif ch == '\\'
      escaped = true
    elsif ch == '(' || ch == ')' || ch.match(/\s/)
      if token.length > 0
        tokens.push(token)
        token = ""
      end
      if !ch.match(/\s/)
        tokens.push(ch)
      end
    else
      token += ch
    end
  end
  if token.length > 0
    tokens.push(token)
  end
  tokens
end