class Lex::Lexer::RuleDSL

Rules DSL used internally by {Lexer}

@api private

Attributes

lex_tokens[R]
state_error[R]
state_ignore[R]
state_info[R]
state_lexemes[R]
state_names[R]
state_re[R]

Public Class Methods

new() click to toggle source

@api private

# File lib/lex/lexer/rule_dsl.rb, line 18
def initialize
  @state_info    = { initial: :inclusive }
  @state_ignore  = { initial: '' }  # Ignored characters for each state
  @state_error   = {} # Error conditions for each state
  @state_re      = Hash.new { |hash, name| hash[name] = {}} # Regexes for each state
  @state_names   = {} # Symbol names for each state
  @state_lexemes = Hash.new { |hash, name| hash[name] = State.new(name) }
  @lex_tokens    = []  # List of valid tokens
end

Public Instance Methods

error(states = :initial, &action) click to toggle source

Define error condition for a state

@api public

# File lib/lex/lexer/rule_dsl.rb, line 104
def error(states = :initial, &action)
  state_names = states.to_s.split('_').map(&:to_sym)
  state_names.each do |state_name|
    @state_error[state_name] = action
  end
  @state_info.each do |state_name, state_type|
    if state_name != :initial && state_type == :inclusive
      if !@state_error.key?(state_name)
        @state_error[state_name] = @state_error[:initial]
      end
    end
  end
end
ignore(states, value = (not_set = true)) click to toggle source

Define ignore condition for a state

@param [Symbol] states

the optional state names

@param [String] value

the characters to ignore

@api public

# File lib/lex/lexer/rule_dsl.rb, line 79
def ignore(states, value = (not_set = true))
  if not_set
    value = states
    state_names = [:initial]
  else
    state_names = states.to_s.split('_').map(&:to_sym)
  end
  if !value.is_a?(String)
    logger.error("Ignore rule '#{value}' has to be defined with a string")
  end
  state_names.each do |state_name|
    @state_ignore[state_name] = value
  end
  @state_info.each do |state_name, state_type|
    if state_name != :initial && state_type == :inclusive
      if !@state_ignore.key?(state_name)
        @state_ignore[state_name] = @state_ignore[:initial]
      end
    end
  end
end
rule(name, pattern, &action) click to toggle source

Specify lexing rule

@param [Symbol] name

the rule name

@param [Regex] pattern

the regex pattern

@api public

# File lib/lex/lexer/rule_dsl.rb, line 51
def rule(name, pattern, &action)
  state_names, token_name = *extract_state_token(name)
  if token_name =~ /^[[:upper:]]*$/ && !@lex_tokens.include?(token_name)
    complain("Rule '#{name}' defined for" \
             " an unspecified token #{token_name}")
  end
  state_names.each do |state_name|
    state = @state_lexemes[state_name]
    state << Lexeme.new(token_name, pattern, &action)
  end
  update_inclusive_states
  state_names.each do |state_name|
    if @state_re[state_name].key?(token_name)
      complain("Rule '#{name}' redefined.")
    end
    @state_re[state_name][token_name] = pattern
  end
end
states(value) click to toggle source

Add states to lexer

@api public

# File lib/lex/lexer/rule_dsl.rb, line 38
def states(value)
  @state_info.merge!(value)
end
tokens(*value) click to toggle source

Add tokens to lexer

@api public

# File lib/lex/lexer/rule_dsl.rb, line 31
def tokens(*value)
  @lex_tokens = value
end

Private Instance Methods

complain(*args) click to toggle source

@api private

# File lib/lex/lexer/rule_dsl.rb, line 160
def complain(*args)
  raise LexerError, *args
end
extract_state_token(name) click to toggle source

Extract tuple of state names and token name

@param [Symbol] name

the rule name

@return [Array, Symbol]

returns tuples [states, token]

@api private

# File lib/lex/lexer/rule_dsl.rb, line 141
def extract_state_token(name)
  parts = name.to_s.split('_')
  state_i = 0
  parts.each_with_index do |part, i|
    if !@state_info.keys.include?(part.to_sym)
      state_i = i
      break
    end
  end
  states = if state_i > 0
             parts[0...state_i].map(&:to_sym)
           else
             [:initial]
           end
  token_name = parts[state_i..-1].join('_').to_sym
  [states, token_name]
end
update_inclusive_states() click to toggle source

For inclusive states copy over initial state rules

@api private

# File lib/lex/lexer/rule_dsl.rb, line 123
def update_inclusive_states
  @state_info.each do |state_name, state_type|
    if state_name != :initial && state_type == :inclusive
      initial_state = @state_lexemes[:initial]
      @state_lexemes[state_name].update(initial_state.lexemes)
    end
  end
end