class MODL::Parser::RefProcessor

Convert MODL reference to the replacement value

Constants

MATCHER
MAX_RECURSE_DEPTH
NESTED_SEPARATOR

Public Class Methods

deref(str, global) click to toggle source

Check str for references and process them. Return the processed string and a new_value if there is one.

# File lib/modl/parser/ref_processor.rb, line 44
def self.deref(str, global)
  obj = str
  obj, new_value = split_by_ref_tokens str, global unless trivial_reject(str)
  [obj, new_value]
end
split_by_ref_tokens(str, global) click to toggle source

Process the next %ref token

# File lib/modl/parser/ref_processor.rb, line 51
def self.split_by_ref_tokens(str, global)
  new_value = nil

  text = str
  original = str

  new_value, str = process_tokens(global, original, str, text) if new_value.nil?

  [str, new_value]
end
trivial_reject(str) click to toggle source
# File lib/modl/parser/ref_processor.rb, line 37
def self.trivial_reject(str)
  # do a fast check to see if we need to deref - save processing the regex if we don't have to.
  !(str.is_a?(String) && !str.start_with?('%*') && (str.nil? || str.include?('%') || str.include?('`')))
end

Private Class Methods

expand(depth, global, ref) click to toggle source
# File lib/modl/parser/ref_processor.rb, line 118
def self.expand(depth, global, ref)
  if depth > MAX_RECURSE_DEPTH
    raise InterpreterError, 'Recursing too deep to resolve: "' + ref + '"'
  end
  result = nil
  prev = nil

  degraved = Sutil.replace(ref, '`', '')

  parts = Sutil.tail(degraved).split('.') if degraved[0] == '%'
  parts = degraved.split('.') unless degraved[0] == '%'
  parts[-1] = Sutil.head(parts[-1]) if parts[-1].end_with?('%')

  if degraved.include?('%')
    resolved = 0
    parts.each do |p|
      if p.include?('%')
        p, _ignore = expand(depth + 1, global, p)
        if p.is_a?(MODL::Parser::MODLParserBaseListener)
          p = p.text
        end
      end
      n = p.to_i
      result = if n.to_s == p
                 # Numeric ref
                 if !result.nil? && !result.respond_to?(:find_property)
                   if !result.is_a?(Parsed::ParsedArrayValueItem)
                     t = result.class
                     t = 'map' if result.is_a? Parsed::ParsedMapItem
                     raise InterpreterError, 'Found a ' + t + ' when expecting an array'
                   end
                   raise InterpreterError, 'Invalid object reference: "' + degraved + '"'
                 end
                 result.nil? ? global.index_value(n, degraved) : result.find_property(n)
               else
                 # String ref
                 if result.is_a? String
                   if StandardMethods.valid_method?(p)
                     StandardMethods.run_method(p, result)
                   else
                     mthd = global.user_method(p)
                     if !mthd.nil?
                       mthd.run(result)
                     else
                       mthd
                     end
                   end
                 elsif result.is_a? Parsed::ParsedPair
                   prop = result.find_property(p)
                   if result.text && !prop
                     if StandardMethods.valid_method?(p)
                       StandardMethods.run_method(p, Sutil.unquote(result.text))
                     else
                       mthd = global.user_method(p)
                       if !mthd.nil?
                         mthd.run(result.text)
                       else
                         mthd
                       end
                     end
                   else
                     prop
                   end
                 elsif result.is_a? Parsed::ParsedArrayValueItem
                   prop = result.find_property(p)
                   if result.text && !prop
                     if StandardMethods.valid_method?(p)
                       result_text = result.text
                       if result_text.start_with?('`') && result_text.end_with?('`')
                         result_text = Sutil.toptail(result_text)
                       end
                       StandardMethods.run_method(p, result_text)
                     else
                       mthd = global.user_method(p)
                       if !mthd.nil?
                         mthd.run(result.text)
                       else
                         mthd
                       end
                     end
                   else
                     prop
                   end
                 elsif result.is_a? Array
                   nil
                 else
                   if !result.nil? && !result.respond_to?(:find_property)
                     raise InterpreterError, 'Invalid object reference: "' + degraved + '"'
                   end
                   if result.nil?
                     unless ref.start_with?('%`')
                       a_pair = global.pair(p)
                     end
                     if a_pair.nil?
                       p
                     else
                       a_pair
                     end
                   else
                     result.find_property(p)
                   end
                 end
               end
      break if result.nil?

      prev = result
      resolved += 1
    end
    if prev.nil?
      remainder = ''
      prev = degraved
    else
      remainder = resolved < parts.length ? '.' + parts[resolved..parts.length].join('.') : ''
    end
    if (prev == Sutil.between(ref, '%', '%')) || (ref.start_with?('%') && prev == Sutil.tail(ref))
      prev = ref
    end
    [prev, remainder]
  else
    # Remove the graves if there are any.
    result = parts[0]
    i = 1
    stalled = false
    while i < parts.length
      stalled |= StandardMethods.valid_method?(parts[i]) ? false : true

      if stalled
        result << '.'
        result << parts[i]
      else
        result = StandardMethods.run_method(parts[i], result)
      end
      i += 1
    end
    [result, '']
  end
end
process_tokens(global, original, str, text) click to toggle source
# File lib/modl/parser/ref_processor.rb, line 64
def self.process_tokens(global, original, str, text)
  new_value = nil
  loop do
    text_s = text.to_s
    match = MATCHER.match(text_s)
    break if match.nil?

    match_index = text_s.index(match[0])
    if match_index > 0
      if text_s[match_index - 1] == '~' || text_s[match_index - 1] == '\\'
        break
      end
      if text_s[match_index + match.length] == '~' || text_s[match_index + match.length] == '\\'
        break
      end
    end


    ref = match[0]
    text = Sutil.after(text, ref)

    new_value, remainder = expand(0, global, ref)
    ref = Sutil.until(ref, remainder)
    if new_value.is_a?(String)
      str = str.sub(ref, new_value)
    elsif new_value.is_a?(Parsed::ParsedArrayItem)
      nv_text = new_value.arrayValueItem.text
      str = if ref == str
              nv_text
            else
              str.sub(ref, nv_text.to_s)
            end
      new_value = nil
    elsif new_value.is_a?(Parsed::ParsedMapItem)
      raise InterpreterError, 'Found a map when expecting an array'
    elsif new_value.is_a?(MODL::Parser::MODLParserBaseListener)
      if new_value.text
        str = if ref == str
                new_value.text
              else
                str.sub(ref, Sutil.unquote(new_value.text.to_s))
              end
        new_value = nil
      else
        str = nil
      end
    else
      new_value = nil
      raise InterpreterError, 'Invalid object reference: "' + str + '"' if str == original
    end
  end
  return new_value, str
end