class Hocon::Impl::ConfigParser::ParseContext

Public Class Methods

new(flavor, origin, document, includer, include_context) click to toggle source
# File lib/hocon/impl/config_parser.rb, line 46
def initialize(flavor, origin, document, includer, include_context)
  @line_number = 1
  @document = document
  @flavor = flavor
  @base_origin = origin
  @includer = includer
  @include_context = include_context
  @path_stack = []
  @array_count = 0
end

Public Instance Methods

create_value_under_path(path, value) click to toggle source
# File lib/hocon/impl/config_parser.rb, line 122
def create_value_under_path(path, value)
  # for path foo.bar, we are creating
  # { "foo" : { "bar" : value } }
  keys = []

  key = path.first
  remaining = path.remainder
  until key.nil?
    keys.push(key)
    if remaining.nil?
      break
    else
      key = remaining.first
      remaining = remaining.remainder
    end
  end

  # the setComments(null) is to ensure comments are only
  # on the exact leaf node they apply to.
  # a comment before "foo.bar" applies to the full setting
  # "foo.bar" not also to "foo"
  keys = keys.reverse
  # this is just a ruby means for doing first/rest
  deepest, *rest = *keys
  o = SimpleConfigObject.new(value.origin.with_comments(nil),
                             {deepest => value})
  while !rest.empty?
    deepest, *rest = *rest
    o = SimpleConfigObject.new(value.origin.with_comments(nil),
                               {deepest => o})
  end

  o
end
full_current_path() click to toggle source
# File lib/hocon/impl/config_parser.rb, line 86
def full_current_path
  # pathStack has top of stack at front
  if @path_stack.empty?
    raise ConfigBugOrBrokenError, "Bug in parser; tried to get current path when at root"
  else
    Path.from_path_list(@path_stack.reverse)
  end
end
line_origin() click to toggle source
# File lib/hocon/impl/config_parser.rb, line 78
def line_origin
  @base_origin.with_line_number(@line_number)
end
parse() click to toggle source
# File lib/hocon/impl/config_parser.rb, line 373
def parse
  result = nil
  comments = []
  last_was_new_line = false
  @document.children.each do |node|
    if node.is_a?(ConfigNodeComment)
      comments << node.comment_text
      last_was_new_line = false
    elsif node.is_a?(ConfigNodeSingleToken)
      t = node.token
      if Tokens.newline?(t)
        @line_number += 1
        if last_was_new_line && result.nil?
          comments.clear
        elsif !result.nil?
          result = result.with_origin(result.origin.append_comments(comments.clone))
          comments.clear
          break
        end
        last_was_new_line = true
      end
    elsif node.is_a?(Hocon::Impl::ConfigNodeComplexValue)
      result = parse_value(node, comments)
      last_was_new_line = false
    end
  end
  result
end
parse_array(n) click to toggle source
# File lib/hocon/impl/config_parser.rb, line 331
def parse_array(n)
  @array_count += 1

  array_origin = line_origin
  values = []

  last_was_new_line = false
  comments = []

  v = nil

  n.children.each do |node|
    if node.is_a?(ConfigNodeComment)
      comments << node.comment_text
      last_was_new_line = false
    elsif node.is_a?(ConfigNodeSingleToken) && Tokens.newline?(node.token)
      @line_number += 1
      if last_was_new_line && v.nil?
        comments.clear
      elsif !v.nil?
        values << v.with_origin(v.origin.append_comments(comments.clone))
        comments.clear
        v = nil
      end
      last_was_new_line = true
    elsif node.is_a?(Hocon::Impl::AbstractConfigNodeValue)
      last_was_new_line = false
      unless v.nil?
        values << v.with_origin(v.origin.append_comments(comments.clone))
        comments.clear
      end
      v = parse_value(node, comments)
    end
  end
  # There shouldn't be any comments at this point, but add them just in case
  unless v.nil?
    values << v.with_origin(v.origin.append_comments(comments.clone))
  end
  @array_count -= 1
  SimpleConfigList.new(array_origin, values)
end
parse_concatenation(n) click to toggle source

merge a bunch of adjacent values into one value; change unquoted text into a string value.

# File lib/hocon/impl/config_parser.rb, line 60
def parse_concatenation(n)
  # this trick is not done in JSON
  if @flavor.equal?(ConfigSyntax::JSON)
    raise ConfigBugOrBrokenError, "Found a concatenation node in JSON"
  end

  values = []

  n.children.each do |node|
    if node.is_a?(Hocon::Impl::AbstractConfigNodeValue)
      v = parse_value(node, nil)
      values.push(v)
    end
  end

  ConfigConcatenation.concatenate(values)
end
parse_error(message, cause = nil) click to toggle source
# File lib/hocon/impl/config_parser.rb, line 82
def parse_error(message, cause = nil)
  ConfigParseError.new(line_origin, message, cause)
end
parse_include(values, n) click to toggle source
# File lib/hocon/impl/config_parser.rb, line 157
def parse_include(values, n)
  case n.kind
    when ConfigIncludeKind::URL
      url = nil
      begin
        url = Hocon::Impl::Url.new(n.name)
      rescue Hocon::Impl::Url::MalformedUrlError => e
        raise parse_error("include url() specifies an invalid URL: #{n.name}", e)
      end
      obj = @includer.include_url(@include_context, url)
    when ConfigIncludeKind::FILE
      obj = @includer.include_file(@include_context, n.name)
    when ConfigIncludeKind::CLASSPATH
      obj = @includer.include_resources(@include_context, n.name)
    when ConfigIncludeKind::HEURISTIC
      obj = @includer.include(@include_context, n.name)
    else
      raise ConfigBugOrBrokenError, "should not be reached"
  end

  # we really should make this work, but for now throwing an
  # exception is better than producing an incorrect result.
  # See https://github.com/typesafehub/config/issues/160
  if @array_count > 0 && (obj.resolve_status != Hocon::Impl::ResolveStatus::RESOLVED)
    raise parse_error("Due to current limitations of the config parser, when an include statement is nested inside a list value, " +
                          "${} substitutions inside the included file cannot be resolved correctly. Either move the include outside of the list value or " +
                          "remove the ${} statements from the included file.")
  end

  if !(@path_stack.empty?)
    prefix = full_current_path
    obj = obj.relativized(prefix)
  end

  obj.key_set.each do |key|
    v = obj.get(key)
    existing = values[key]
    if !(existing.nil?)
      values[key] = v.with_fallback(existing)
    else
      values[key] = v
    end
  end
end
parse_object(n) click to toggle source
# File lib/hocon/impl/config_parser.rb, line 202
def parse_object(n)
  values = Hash.new
  object_origin = line_origin
  last_was_new_line = false

  nodes = n.children.clone
  comments = []
  i = 0
  while i < nodes.size
    node = nodes[i]
    if node.is_a?(ConfigNodeComment)
      last_was_new_line = false
      comments.push(node.comment_text)
    elsif node.is_a?(ConfigNodeSingleToken) && Tokens.newline?(node.token)
      @line_number += 1
      if last_was_new_line
        # Drop all comments if there was a blank line and start a new comment block
        comments.clear
      end
      last_was_new_line = true
    elsif !@flavor.equal?(ConfigSyntax::JSON) && node.is_a?(ConfigNodeInclude)
      parse_include(values, node)
      last_was_new_line = false
    elsif node.is_a?(Hocon::Impl::ConfigNodeField)
      last_was_new_line = false
      path = node.path.value
      comments += node.comments

      # path must be on-stack while we parse the value
      # Note that, in upstream, pathStack is a LinkedList, so use unshift instead of push
      @path_stack.unshift(path)
      if node.separator.equal?(Tokens::PLUS_EQUALS)
        # we really should make this work, but for now throwing
        # an exception is better than producing an incorrect
        # result. See
        # https://github.com/typesafehub/config/issues/160
        if @array_count > 0
          raise parse_error("Due to current limitations of the config parser, += does not work nested inside a list. " +
                                "+= expands to a ${} substitution and the path in ${} cannot currently refer to list elements. " +
                                "You might be able to move the += outside of the list and then refer to it from inside the list with ${}.")
        end

        # because we will put it in an array after the fact so
        # we want this to be incremented during the parseValue
        # below in order to throw the above exception.
        @array_count += 1
      end

      value_node = node.value

      # comments from the key token go to the value token
      new_value = parse_value(value_node, comments)

      if node.separator.equal?(Tokens::PLUS_EQUALS)
        @array_count -= 1

        concat = []
        previous_ref = ConfigReference.new(new_value.origin,
                                           Hocon::Impl::SubstitutionExpression.new(full_current_path, true))
        list = SimpleConfigList.new(new_value.origin, [new_value])
        concat << previous_ref
        concat << list
        new_value = ConfigConcatenation.concatenate(concat)
      end

      # Grab any trailing comments on the same line
      if i < nodes.size - 1
        i += 1
        while i < nodes.size
          if nodes[i].is_a?(ConfigNodeComment)
            comment = nodes[i]
            new_value = new_value.with_origin(new_value.origin.append_comments([comment.comment_text]))
            break
          elsif nodes[i].is_a?(ConfigNodeSingleToken)
            curr = nodes[i]
            if curr.token.equal?(Tokens::COMMA) || Tokens.ignored_whitespace?(curr.token)
              # keep searching, as there could still be a comment
            else
              i -= 1
              break
            end
          else
            i -= 1
            break
          end
          i += 1
        end
      end

      @path_stack.shift

      key = path.first
      remaining = path.remainder

      if remaining.nil?
        existing = values[key]
        unless existing.nil?
          # In strict JSON, dups should be an error; while in
          # our custom config language, they should be merged
          # if the value is an object (or substitution that
          # could become an object).

          if @flavor.equal?(ConfigSyntax::JSON)
            raise parse_error("JSON does not allow duplicate fields: '#{key}'" +
                                  " was already seen at #{existing.origin().description()}")
          else
            new_value = new_value.with_fallback(existing)
          end
        end
        values[key] = new_value
      else
        if @flavor == ConfigSyntax::JSON
          raise Hocon::ConfigError::ConfigBugOrBrokenError, "somehow got multi-element path in JSON mode"
        end

        obj = create_value_under_path(remaining, new_value)
        existing = values[key]
        if !existing.nil?
          obj = obj.with_fallback(existing)
        end
        values[key] = obj
      end
    end
    i += 1
  end

  SimpleConfigObject.new(object_origin, values)
end
parse_value(n, comments) click to toggle source
# File lib/hocon/impl/config_parser.rb, line 95
def parse_value(n, comments)
  starting_array_count = @array_count

  if n.is_a?(Hocon::Impl::ConfigNodeSimpleValue)
    v = n.value
  elsif n.is_a?(Hocon::Impl::ConfigNodeObject)
    v = parse_object(n)
  elsif n.is_a?(Hocon::Impl::ConfigNodeArray)
    v = parse_array(n)
  elsif n.is_a?(Hocon::Impl::ConfigNodeConcatenation)
    v = parse_concatenation(n)
  else
    raise parse_error("Expecting a value but got wrong node type: #{n.class}")
  end

  unless comments.nil? || comments.empty?
    v = v.with_origin(v.origin.prepend_comments(comments.clone))
    comments.clear
  end

  unless @array_count == starting_array_count
    raise ConfigBugOrBrokenError, "Bug in config parser: unbalanced array count"
  end

  v
end