lib/tomlrb/handler.rb in tomlrb-1.3.0 vs lib/tomlrb/handler.rb in tomlrb-2.0.0
- old
+ new
@@ -5,14 +5,18 @@
def initialize(**options)
@output = {}
@current = @output
@stack = []
@array_names = []
+ @current_table = []
+ @keys = Keys.new
@symbolize_keys = options[:symbolize_keys]
end
def set_context(identifiers, is_array_of_tables: false)
+ @current_table = identifiers.dup
+ @keys.add_table_key identifiers, is_array_of_tables
@current = @output
deal_with_array_of_tables(identifiers, is_array_of_tables) do |identifierz|
identifierz.each do |k|
k = k.to_sym if @symbolize_keys
@@ -41,18 +45,23 @@
yield(identifiers)
if is_array_of_tables
last_identifier = last_identifier.to_sym if @symbolize_keys
@current[last_identifier] ||= []
+ raise ParseError, "Cannot use key #{last_identifier} for both table and array at once" unless @current[last_identifier].respond_to?(:<<)
@current[last_identifier] << {}
@current = @current[last_identifier].last
end
end
def assign(k)
- k = k.to_sym if @symbolize_keys
- @current[k] = @stack.pop
+ @keys.add_pair_key k, @current_table
+ current = @current
+ while key = k.shift
+ key = key.to_sym if @symbolize_keys
+ current = assign_key_path(current, key, k.empty?)
+ end
end
def push(o)
@stack << o
end
@@ -66,8 +75,152 @@
while (value = @stack.pop) != [type]
raise ParseError, 'Unclosed table' if value.nil?
array.unshift(value)
end
array
+ end
+
+ private
+
+ def assign_key_path(current, key, key_emptied)
+ if key_emptied
+ raise ParseError, "Cannot overwrite value with key #{key}" unless current.kind_of?(Hash)
+ current[key] = @stack.pop
+ return current
+ end
+ current[key] ||= {}
+ current = current[key]
+ current
+ end
+ end
+
+ class Keys
+ def initialize
+ @keys = {}
+ end
+
+ def add_table_key(keys, is_array_of_tables = false)
+ self << [keys, [], is_array_of_tables]
+ end
+
+ def add_pair_key(keys, context)
+ self << [context, keys, false]
+ end
+
+ def <<(keys)
+ table_keys, pair_keys, is_array_of_tables = keys
+ current = @keys
+ current = append_table_keys(current, table_keys, pair_keys.empty?, is_array_of_tables)
+ append_pair_keys(current, pair_keys, table_keys.empty?, is_array_of_tables)
+ end
+
+ private
+
+ def append_table_keys(current, table_keys, pair_keys_empty, is_array_of_tables)
+ table_keys.each_with_index do |key, index|
+ declared = (index == table_keys.length - 1) && pair_keys_empty
+ if index == 0
+ current = find_or_create_first_table_key(current, key, declared, is_array_of_tables)
+ else
+ current = current << [key, :table, declared, is_array_of_tables]
+ end
+ end
+
+ current
+ end
+
+ def find_or_create_first_table_key(current, key, declared, is_array_of_tables)
+ existed = current[key]
+ if existed && existed.type == :pair
+ raise Key::KeyConflict, "Key #{key} is already used as #{existed.type} key"
+ end
+ if existed && existed.declared? && declared && ! is_array_of_tables
+ raise Key::KeyConflict, "Key #{key} is already used"
+ end
+ k = existed || Key.new(key, :table, declared)
+ current[key] = k
+ k
+ end
+
+ def append_pair_keys(current, pair_keys, table_keys_empty, is_array_of_tables)
+ pair_keys.each_with_index do |key, index|
+ declared = index == pair_keys.length - 1
+ if index == 0 && table_keys_empty
+ current = find_or_create_first_pair_key(current, key, declared, table_keys_empty)
+ else
+ key = current << [key, :pair, declared, is_array_of_tables]
+ current = key
+ end
+ end
+ end
+
+ def find_or_create_first_pair_key(current, key, declared, table_keys_empty)
+ existed = current[key]
+ if existed && existed.declared? && (existed.type == :pair) && declared && table_keys_empty
+ raise Key::KeyConflict, "Key #{key} is already used"
+ end
+ k = Key.new(key, :pair, declared)
+ current[key] = k
+ k
+ end
+ end
+
+ class Key
+ class KeyConflict < ParseError; end
+
+ attr_reader :key, :type
+
+ def initialize(key, type, declared = false)
+ @key = key
+ @type = type
+ @declared = declared
+ @children = {}
+ end
+
+ def declared?
+ @declared
+ end
+
+ def <<(key_type_declared)
+ key, type, declared, is_array_of_tables = key_type_declared
+ existed = @children[key]
+ validate_already_declared_as_different_key(type, declared, existed)
+ validate_already_declared_as_non_array_table(type, is_array_of_tables, declared, existed)
+ validate_path_already_created_as_different_type(type, declared, existed)
+ validate_path_already_declared_as_different_type(type, declared, existed)
+ validate_already_declared_as_same_key(declared, existed)
+ @children[key] = existed || self.class.new(key, type, declared)
+ end
+
+ private
+
+ def validate_already_declared_as_different_key(type, declared, existed)
+ if declared && existed && existed.declared? && existed.type != type
+ raise KeyConflict, "Key #{existed.key} is already used as #{existed.type} key"
+ end
+ end
+
+ def validate_already_declared_as_non_array_table(type, is_array_of_tables, declared, existed)
+ if declared && type == :table && existed && existed.declared? && ! is_array_of_tables
+ raise KeyConflict, "Key #{existed.key} is already used"
+ end
+ end
+
+ def validate_path_already_created_as_different_type(type, declared, existed)
+ if declared && (type == :table) && existed && (existed.type == :pair) && (! existed.declared?)
+ raise KeyConflict, "Key #{existed.key} is already used as #{existed.type} key"
+ end
+ end
+
+ def validate_path_already_declared_as_different_type(type, declared, existed)
+ if ! declared && (type == :pair) && existed && (existed.type == :pair) && existed.declared?
+ raise KeyConflict, "Key #{key} is already used as #{type} key"
+ end
+ end
+
+ def validate_already_declared_as_same_key(declared, existed)
+ if existed && ! existed.declared? && declared
+ raise KeyConflict, "Key #{existed.key} is already used as #{existed.type} key"
+ end
end
end
end