class NestedText::Parser

A LL(1) recursive descent parser for NT.

Public Class Methods

assert_valid_top_level_type(top_class) click to toggle source
# File lib/nestedtext/parser.rb, line 13
def self.assert_valid_top_level_type(top_class)
  if !top_class.nil? && top_class.is_a?(Class) && TOP_LEVEL_TYPES.map(&:object_id).include?(top_class.object_id)
    return
  end

  raise Errors::UnsupportedTopLevelTypeError, top_class
end
new(io, top_class, strict: false) click to toggle source
# File lib/nestedtext/parser.rb, line 21
def initialize(io, top_class, strict: false)
  assert_valid_input_type io
  Parser.assert_valid_top_level_type(top_class)
  @top_class = top_class
  @strict = strict
  @line_scanner = LineScanner.new(io)
end

Public Instance Methods

parse() click to toggle source
# File lib/nestedtext/parser.rb, line 29
def parse
  result = parse_any(0)
  case @top_class.object_id
  when Object.object_id
    return_object(result)
  when Hash.object_id
    return_hash(result)
  when Array.object_id
    return_array(result)
  when String.object_id
    return_string(result)
  else
    raise Errors::UnsupportedTopLevelTypeError, @top_class
  end
end

Private Instance Methods

assert_dict_item_line(line, indentation) click to toggle source
# File lib/nestedtext/parser.rb, line 193
def assert_dict_item_line(line, indentation)
  Errors.raise_unrecognized_line(line) if line.tag == :unrecognized
  raise Errors::ParseInvalidIndentationError.new(line, indentation) if line.indentation != indentation
  raise Errors::ParseLineTypeExpectedDictItemError, line unless %i[dict_item key_item].include? line.tag
end
assert_list_line(line, indentation) click to toggle source
# File lib/nestedtext/parser.rb, line 112
def assert_list_line(line, indentation)
  Errors.raise_unrecognized_line(line) if line.tag == :unrecognized
  raise Errors::ParseLineTypeExpectedListItemError, line unless line.tag == :list_item
  raise Errors::ParseInvalidIndentationError.new(line, indentation) if line.indentation != indentation
end
assert_string_line(line, indentation) click to toggle source
# File lib/nestedtext/parser.rb, line 215
def assert_string_line(line, indentation)
  raise Errors::ParseInvalidIndentationError.new(line, indentation) if line.indentation != indentation
  raise Errors::ParseLineTypeNotExpectedError.new(line, %i[string_item], line.tag) unless line.tag == :string_item
end
assert_valid_input_type(input) click to toggle source
# File lib/nestedtext/parser.rb, line 75
def assert_valid_input_type(input)
  return if input.nil? || input.is_a?(IO) || input.is_a?(StringIO)

  raise Errors::WrongInputTypeError.new([IO, StringIO], input)
end
deserialize_custom_class(hash, first_line) click to toggle source
# File lib/nestedtext/parser.rb, line 128
def deserialize_custom_class(hash, first_line)
  return hash unless !@strict && hash.length == 2 && hash.key?(CUSTOM_CLASS_KEY)

  class_name = hash[CUSTOM_CLASS_KEY]
  begin
    clazz = class_name == 'nil' ? NilClass : Object.const_get(class_name, false)
  rescue NameError
    raise Errors::ParseCustomClassNotFoundError.new(first_line, class_name)
  end
  raise Errors::ParseCustomClassNoCreateMethodError.new(first_line, class_name) unless clazz.respond_to? :nt_create

  clazz.nt_create(hash['data'])
end
parse_any(indentation) click to toggle source
# File lib/nestedtext/parser.rb, line 81
def parse_any(indentation)
  return nil if @line_scanner.peek.nil?

  case @line_scanner.peek.tag
  when :list_item
    parse_list_item(indentation)
  when :dict_item, :key_item
    parse_dict_item(indentation)
  when :string_item
    parse_string_item(indentation)
  when :inline_dict
    parse_inline_dict
  when :inline_list
    parse_inline_list
  when :unrecognized
    Errors.raise_unrecognized_line(@line_scanner.peek)
  else
    raise Errors::AssertionError, "Unexpected line tag! #{@line_scanner.peek.tag}"
  end
end
parse_dict_item(indentation) click to toggle source
# File lib/nestedtext/parser.rb, line 199
def parse_dict_item(indentation)
  result = {}
  first_line = nil
  while !@line_scanner.peek.nil? && @line_scanner.peek.indentation >= indentation
    line = @line_scanner.read_next
    first_line = line if first_line.nil?
    assert_dict_item_line(line, indentation)
    key, value = parse_dict_key_value(line, indentation)
    raise Errors::ParseDictDuplicateKeyError, line if result.key? key

    result[key] = value
  end

  deserialize_custom_class(result, first_line)
end
parse_dict_key_value(line, indentation) click to toggle source
# File lib/nestedtext/parser.rb, line 185
def parse_dict_key_value(line, indentation)
  if line.tag == :dict_item
    parse_kv_dict_item(indentation, line)
  else
    parse_kv_key_item(indentation, line)
  end
end
parse_inline_dict() click to toggle source
# File lib/nestedtext/parser.rb, line 232
def parse_inline_dict
  result = InlineParser.new(@line_scanner.read_next).parse
  unless result.is_a? Hash
    raise Errors::AssertionError,
          "Expected inline value to be Hash but is #{result.class.name}"
  end

  result
end
parse_inline_list() click to toggle source
# File lib/nestedtext/parser.rb, line 242
def parse_inline_list
  result = InlineParser.new(@line_scanner.read_next).parse
  unless result.is_a? Array
    raise Errors::AssertionError,
          "Expected inline value to be Array but is #{result.class.name}"
  end

  result
end
parse_key_item_key(indentation, line) click to toggle source
# File lib/nestedtext/parser.rb, line 154
def parse_key_item_key(indentation, line)
  key = line.attribs['key']
  while @line_scanner.peek&.tag == :key_item && @line_scanner.peek.indentation == indentation
    line = @line_scanner.read_next
    key += "\n#{line.attribs['key']}"
  end
  key
end
parse_key_item_value(indentation, line) click to toggle source
# File lib/nestedtext/parser.rb, line 163
def parse_key_item_value(indentation, line)
  return '' if @line_scanner.peek.nil?

  exp_types = %i[dict_item key_item list_item string_item]
  unless exp_types.member?(@line_scanner.peek.tag)
    raise Errors::ParseLineTypeNotExpectedError.new(line, exp_types, line.tag)
  end

  unless @line_scanner.peek.indentation > indentation
    raise Errors::ParseMultilineKeyNoValueError,
          line
  end

  parse_any(@line_scanner.peek.indentation)
end
parse_kv_dict_item(indentation, line) click to toggle source
# File lib/nestedtext/parser.rb, line 142
def parse_kv_dict_item(indentation, line)
  key = line.attribs['key']
  value = line.attribs['value']
  if value.nil?
    value = ''
    if !@line_scanner.peek.nil? && @line_scanner.peek.indentation > indentation
      value = parse_any(@line_scanner.peek.indentation)
    end
  end
  [key, value]
end
parse_kv_key_item(indentation, line) click to toggle source
# File lib/nestedtext/parser.rb, line 179
def parse_kv_key_item(indentation, line)
  key = parse_key_item_key(indentation, line)
  value = parse_key_item_value(indentation, line)
  [key, value]
end
parse_list_item(indentation) click to toggle source
# File lib/nestedtext/parser.rb, line 118
def parse_list_item(indentation)
  result = []
  while !@line_scanner.peek.nil? && @line_scanner.peek.indentation >= indentation
    line = @line_scanner.read_next
    assert_list_line(line, indentation)
    result << parse_list_item_value(indentation, line.attribs['value'])
  end
  result
end
parse_list_item_value(indentation, value) click to toggle source
# File lib/nestedtext/parser.rb, line 102
def parse_list_item_value(indentation, value)
  return value unless value.nil?

  if !@line_scanner.peek.nil? && @line_scanner.peek.indentation > indentation
    parse_any(@line_scanner.peek.indentation)
  elsif @line_scanner.peek.nil? || @line_scanner.peek.tag == :list_item
    ''
  end
end
parse_string_item(indentation) click to toggle source
# File lib/nestedtext/parser.rb, line 220
def parse_string_item(indentation)
  result = []
  while !@line_scanner.peek.nil? && @line_scanner.peek.indentation >= indentation
    line = @line_scanner.read_next
    assert_string_line(line, indentation)

    value = line.attribs['value']
    result << value
  end
  result.join("\n")
end
return_array(result) click to toggle source
# File lib/nestedtext/parser.rb, line 61
def return_array(result)
  result = [] if result.nil?
  raise Errors::TopLevelTypeMismatchParsedTypeError.new(@top_class, result) unless result.instance_of?(Array)

  result
end
return_hash(result) click to toggle source
# File lib/nestedtext/parser.rb, line 54
def return_hash(result)
  result = {} if result.nil?
  raise Errors::TopLevelTypeMismatchParsedTypeError.new(@top_class, result) unless result.instance_of?(Hash)

  result
end
return_object(result) click to toggle source
# File lib/nestedtext/parser.rb, line 47
def return_object(result)
  raise Errors::AssertionError, 'Parsed result is of unexpected type.' if \
    !result.nil? && ![Hash, Array, String].include?(result.class) && @strict

  result
end
return_string(result) click to toggle source
# File lib/nestedtext/parser.rb, line 68
def return_string(result)
  result = '' if result.nil?
  raise Errors::TopLevelTypeMismatchParsedTypeError.new(@top_class, result) unless result.instance_of?(String)

  result
end