diff --git a/develop/fuzzing/execute_fuzz.lua b/develop/fuzzing/execute_fuzz.lua index 11bf6c0..6894bc0 100644 --- a/develop/fuzzing/execute_fuzz.lua +++ b/develop/fuzzing/execute_fuzz.lua @@ -28,6 +28,20 @@ end ---@param query evolved.query local function generate_query(query) + local either_set = {} + local either_list = {} + local either_count = 0 + + for _ = 1, math.random(0, #all_fragment_list) do + local either = all_fragment_list[math.random(1, #all_fragment_list)] + + if not either_set[either] then + either_count = either_count + 1 + either_set[either] = either_count + either_list[either_count] = either + end + end + local include_set = {} local include_list = {} local include_count = 0 @@ -56,6 +70,10 @@ local function generate_query(query) end end + if either_count > 0 then + evo.set(query, evo.EITHERS, either_list) + end + if include_count > 0 then evo.set(query, evo.INCLUDES, include_list) end @@ -171,9 +189,19 @@ local function execute_query(query) local query_chunk_set = {} local query_entity_set = {} + local query_either_list = evo.get(query, evo.EITHERS) or {} local query_include_list = evo.get(query, evo.INCLUDES) or {} local query_exclude_list = evo.get(query, evo.EXCLUDES) or {} + local query_either_count = #query_either_list + local query_include_count = #query_include_list + local query_exclude_count = #query_exclude_list + + local query_either_set = {} + for _, either in ipairs(query_either_list) do + query_either_set[either] = true + end + local query_include_set = {} for _, include in ipairs(query_include_list) do query_include_set[include] = true @@ -189,19 +217,29 @@ local function execute_query(query) query_entity_set[entity] = true end - assert(chunk:has_all(__table_unpack(query_include_list))) - assert(not chunk:has_any(__table_unpack(query_exclude_list))) + if query_either_count > 0 then + assert(chunk:has_any(__table_unpack(query_either_list))) + end + + if query_include_count > 0 then + assert(chunk:has_all(__table_unpack(query_include_list))) + end + + if query_exclude_count > 0 then + assert(not chunk:has_any(__table_unpack(query_exclude_list))) + end end for i = 1, all_entity_count do local entity = all_entity_list[i] local is_entity_matched = - evo.has_all(entity, __table_unpack(query_include_list)) - and not evo.has_any(entity, __table_unpack(query_exclude_list)) + (query_either_count == 0 or evo.has_any(entity, __table_unpack(query_either_list))) and + (query_include_count == 0 or evo.has_all(entity, __table_unpack(query_include_list))) and + (query_exclude_count == 0 or not evo.has_any(entity, __table_unpack(query_exclude_list))) for fragment in evo.each(entity) do - if evo.has(fragment, evo.EXPLICIT) and not query_include_set[fragment] then + if evo.has(fragment, evo.EXPLICIT) and not query_either_set[fragment] and not query_include_set[fragment] then is_entity_matched = false end end @@ -236,7 +274,10 @@ for _ = 1, math.random(1, 5) do if math.random(1, 2) == 1 then generate_query(query) else - if math.random(1, 2) == 1 then + local r = math.random(1, 3) + if r == 1 then + evo.remove(query, evo.EITHERS) + elseif r == 2 then evo.remove(query, evo.INCLUDES) else evo.remove(query, evo.EXCLUDES) diff --git a/evolved.lua b/evolved.lua index da4c23e..d2c3e1f 100644 --- a/evolved.lua +++ b/evolved.lua @@ -81,7 +81,9 @@ local evolved = { ---@field package [1] integer structural_changes ---@field package [2] evolved.chunk[] chunk_stack ---@field package [3] integer chunk_stack_size ----@field package [4] table? exclude_set +---@field package [4] table? either_set +---@field package [5] table? include_set +---@field package [6] table? exclude_set ---@alias evolved.each_iterator fun( --- state: evolved.each_state?): @@ -1108,6 +1110,9 @@ local __trace_minor_chunks local __cache_query_chunks local __reset_query_chunks +local __query_major_matches +local __query_minor_matches + local __update_major_chunks local __update_major_chunks_trace @@ -1117,7 +1122,6 @@ local __chunk_without_fragment local __chunk_without_fragments local __chunk_without_unique_fragments -local __chunk_matches local __chunk_requires local __chunk_fragments local __chunk_components @@ -1399,7 +1403,7 @@ function __update_chunk_queries(chunk) local major_query_chunks = __query_chunks[major_query] if major_query_chunks then - if __chunk_matches(chunk, major_query) then + if __query_major_matches(chunk, major_query) then __assoc_list_insert(major_query_chunks, chunk) else __assoc_list_remove(major_query_chunks, chunk) @@ -1572,20 +1576,37 @@ end function __cache_query_chunks(query) __reset_query_chunks(query) + local query_eithers = __sorted_eithers[query] + local query_either_list = query_eithers and query_eithers.__item_list + local query_either_count = query_eithers and query_eithers.__item_count or 0 + local query_includes = __sorted_includes[query] local query_include_list = query_includes and query_includes.__item_list local query_include_count = query_includes and query_includes.__item_count or 0 - if query_include_count == 0 then - __error_fmt('the query (%s) has no include fragments and cannot be cached', - __id_name(query)) - end - ---@type evolved.assoc_list local query_chunks = __assoc_list_new(4) __query_chunks[query] = query_chunks - do + for query_either_index = 1, query_either_count do + local query_either = query_either_list[query_either_index] + + if query_include_count == 0 or query_either > query_include_list[query_include_count] then + local major_chunks = __major_chunks[query_either] + local major_chunk_list = major_chunks and major_chunks.__item_list + local major_chunk_count = major_chunks and major_chunks.__item_count or 0 + + for major_chunk_index = 1, major_chunk_count do + local major_chunk = major_chunk_list[major_chunk_index] + + if __query_major_matches(major_chunk, query) then + __assoc_list_insert(query_chunks, major_chunk) + end + end + end + end + + if query_include_count > 0 then local query_major = query_include_list[query_include_count] local major_chunks = __major_chunks[query_major] @@ -1595,7 +1616,7 @@ function __cache_query_chunks(query) for major_chunk_index = 1, major_chunk_count do local major_chunk = major_chunk_list[major_chunk_index] - if __chunk_matches(major_chunk, query) then + if __query_major_matches(major_chunk, query) then __assoc_list_insert(query_chunks, major_chunk) end end @@ -1609,6 +1630,87 @@ function __reset_query_chunks(query) __query_chunks[query] = nil end +---@param chunk evolved.chunk +---@param query evolved.query +---@return boolean +---@nodiscard +function __query_major_matches(chunk, query) + local query_eithers = __sorted_eithers[query] + local query_either_set = query_eithers and query_eithers.__item_set + local query_either_list = query_eithers and query_eithers.__item_list + local query_either_count = query_eithers and query_eithers.__item_count or 0 + + local query_includes = __sorted_includes[query] + local query_include_set = query_includes and query_includes.__item_set + local query_include_count = query_includes and query_includes.__item_count or 0 + + local query_either_index = query_either_count > 0 and query_either_set[chunk.__fragment] or nil + local query_include_index = query_include_count > 0 and query_include_set[chunk.__fragment] or nil + + return ( + (query_include_index ~= nil and query_include_index == query_include_count) or + (query_either_index ~= nil and not __chunk_has_any_fragment_list(chunk, query_either_list, query_either_index - 1)) + ) and __query_minor_matches(chunk, query) +end + +---@param chunk evolved.chunk +---@param query evolved.query +---@return boolean +---@nodiscard +function __query_minor_matches(chunk, query) + local query_eithers = __sorted_eithers[query] + local query_either_set = query_eithers and query_eithers.__item_set + local query_either_list = query_eithers and query_eithers.__item_list + local query_either_count = query_eithers and query_eithers.__item_count or 0 + + if query_either_count > 0 then + if not __chunk_has_any_fragment_list(chunk, query_either_list, query_either_count) then + return false + end + end + + local query_includes = __sorted_includes[query] + local query_include_set = query_includes and query_includes.__item_set + local query_include_list = query_includes and query_includes.__item_list + local query_include_count = query_includes and query_includes.__item_count or 0 + + if query_include_count > 0 then + if not __chunk_has_all_fragment_list(chunk, query_include_list, query_include_count) then + return false + end + end + + local query_excludes = __sorted_excludes[query] + local query_exclude_list = query_excludes and query_excludes.__item_list + local query_exclude_count = query_excludes and query_excludes.__item_count or 0 + + if query_exclude_count > 0 then + if __chunk_has_any_fragment_list(chunk, query_exclude_list, query_exclude_count) then + return false + end + end + + if chunk.__has_explicit_fragments then + local chunk_fragment_list = chunk.__fragment_list + local chunk_fragment_count = chunk.__fragment_count + + for chunk_fragment_index = 1, chunk_fragment_count do + local chunk_fragment = chunk_fragment_list[chunk_fragment_index] + + local is_chunk_fragment_matched = + (not __evolved_has(chunk_fragment, __EXPLICIT)) or + (query_either_count > 0 and query_either_set[chunk_fragment]) or + (query_include_count > 0 and query_include_set[chunk_fragment]) + + if not is_chunk_fragment_matched then + return false + end + end + end + + return true +end + ---@param major evolved.fragment function __update_major_chunks(major) if __defer_depth > 0 then @@ -1789,50 +1891,6 @@ function __chunk_without_unique_fragments(chunk) return sib_chunk end ----@param chunk evolved.chunk ----@param query evolved.query ----@return boolean ----@nodiscard -function __chunk_matches(chunk, query) - local query_includes = __sorted_includes[query] - local query_include_set = query_includes and query_includes.__item_set - local query_include_list = query_includes and query_includes.__item_list - local query_include_count = query_includes and query_includes.__item_count or 0 - - if query_include_count > 0 then - if not __chunk_has_all_fragment_list(chunk, query_include_list, query_include_count) then - return false - end - elseif chunk.__has_explicit_fragments then - return false - end - - local query_excludes = __sorted_excludes[query] - local query_exclude_list = query_excludes and query_excludes.__item_list - local query_exclude_count = query_excludes and query_excludes.__item_count or 0 - - if query_exclude_count > 0 then - if __chunk_has_any_fragment_list(chunk, query_exclude_list, query_exclude_count) then - return false - end - end - - if chunk.__has_explicit_fragments then - local chunk_fragment_list = chunk.__fragment_list - local chunk_fragment_count = chunk.__fragment_count - - for chunk_fragment_index = 1, chunk_fragment_count do - local chunk_fragment = chunk_fragment_list[chunk_fragment_index] - - if not query_include_set[chunk_fragment] and __evolved_has(chunk_fragment, __EXPLICIT) then - return false - end - end - end - - return true -end - ---@param chunk evolved.chunk ---@return evolved.chunk ---@nodiscard @@ -3864,7 +3922,9 @@ function __iterator_fns.__execute_iterator(execute_state) local structural_changes = execute_state[1] local chunk_stack = execute_state[2] local chunk_stack_size = execute_state[3] - local exclude_set = execute_state[4] + local either_set = execute_state[4] + local include_set = execute_state[5] + local exclude_set = execute_state[6] if structural_changes ~= __structural_changes then __error_fmt('structural changes are prohibited during iteration') @@ -3884,7 +3944,9 @@ function __iterator_fns.__execute_iterator(execute_state) local chunk_child_fragment = chunk_child.__fragment local is_chunk_child_matched = - (not chunk_child.__has_explicit_major) and + (not chunk_child.__has_explicit_major or ( + (either_set and either_set[chunk_child_fragment]) or + (include_set and include_set[chunk_child_fragment]))) and (not exclude_set or not exclude_set[chunk_child_fragment]) if is_chunk_child_matched then @@ -5233,14 +5295,19 @@ function __evolved_execute(query) local chunk_stack = __acquire_table(__table_pool_tag.chunk_list) local chunk_stack_size = 0 + local query_eithers = __sorted_eithers[query] + local query_either_set = query_eithers and query_eithers.__item_set + local query_either_count = query_eithers and query_eithers.__item_count or 0 + local query_includes = __sorted_includes[query] + local query_include_set = query_includes and query_includes.__item_set local query_include_count = query_includes and query_includes.__item_count or 0 local query_excludes = __sorted_excludes[query] local query_exclude_set = query_excludes and query_excludes.__item_set local query_exclude_count = query_excludes and query_excludes.__item_count or 0 - if query_include_count > 0 then + if query_either_count > 0 or query_include_count > 0 then local query_chunks = __query_chunks[query] or __cache_query_chunks(query) local query_chunk_list = query_chunks and query_chunks.__item_list local query_chunk_count = query_chunks and query_chunks.__item_count or 0 @@ -5281,7 +5348,9 @@ function __evolved_execute(query) execute_state[1] = __structural_changes execute_state[2] = chunk_stack execute_state[3] = chunk_stack_size - execute_state[4] = query_exclude_set + execute_state[4] = query_either_set + execute_state[5] = query_include_set + execute_state[6] = query_exclude_set return __iterator_fns.__execute_iterator, execute_state end @@ -6325,15 +6394,36 @@ __evolved_set(__ON_REMOVE, __UNIQUE) ---@param query evolved.query local function __insert_query(query) + local query_eithers = __sorted_eithers[query] + local query_either_list = query_eithers and query_eithers.__item_list + local query_either_count = query_eithers and query_eithers.__item_count or 0 + local query_includes = __sorted_includes[query] local query_include_list = query_includes and query_includes.__item_list local query_include_count = query_includes and query_includes.__item_count or 0 + for query_either_index = 1, query_either_count do + local query_either = query_either_list[query_either_index] + + if query_include_count == 0 or query_either > query_include_list[query_include_count] then + local major_queries = __major_queries[query_either] + + if not major_queries then + ---@type evolved.assoc_list + major_queries = __assoc_list_new(4) + __major_queries[query_either] = major_queries + end + + __assoc_list_insert(major_queries, query) + end + end + if query_include_count > 0 then local query_major = query_include_list[query_include_count] local major_queries = __major_queries[query_major] if not major_queries then + ---@type evolved.assoc_list major_queries = __assoc_list_new(4) __major_queries[query_major] = major_queries end @@ -6344,10 +6434,26 @@ end ---@param query evolved.query local function __remove_query(query) + local query_eithers = __sorted_eithers[query] + local query_either_list = query_eithers and query_eithers.__item_list + local query_either_count = query_eithers and query_eithers.__item_count or 0 + local query_includes = __sorted_includes[query] local query_include_list = query_includes and query_includes.__item_list local query_include_count = query_includes and query_includes.__item_count or 0 + for query_either_index = 1, query_either_count do + local query_either = query_either_list[query_either_index] + + if query_include_count == 0 or query_either > query_include_list[query_include_count] then + local major_queries = __major_queries[query_either] + + if major_queries and __assoc_list_remove(major_queries, query) == 0 then + __major_queries[query_either] = nil + end + end + end + if query_include_count > 0 then local query_major = query_include_list[query_include_count] local major_queries = __major_queries[query_major]