-- A class for working with index ranges in arrays. local Range = {} -- Create a new range based on the start/end indices, the type of the end index -- (inclusive/exclusive), the size of the array that contains the range, and an -- optional nondecreasing map-back function from indices in the array to -- indices in an original array and the size of the original array. -- -- Since empty ranges are usually a mistake, they are not allowed. -- For example, `Range:new(index, index, "exclusive", #array)` will cause an -- assertion error. The exception to this rule are empty arrays, which permit -- the empty range `Range:new(0, 0, "inclusive", 0)`. function Range.new(cls, range_start, range_end, end_type, transformed_array_size, map_back, original_array_size) -- Instantiate the class. local new_object = {} setmetatable(new_object, cls) cls.__index = cls -- Check pre-conditions. if transformed_array_size == 0 then assert(range_start == 0) else assert(range_start >= 1) assert(range_start <= transformed_array_size) end assert(end_type == "inclusive" or end_type == "exclusive") if end_type == "exclusive" then -- Convert exclusive range end to inclusive. range_end = range_end - 1 end if transformed_array_size == 0 then assert(range_end == 0) else assert(range_end >= range_start) assert(range_end <= transformed_array_size) end local mapped_range_start, mapped_range_end if map_back ~= nil then -- Apply the map-back function to the endpoints of the range. assert(original_array_size ~= nil) mapped_range_start = map_back(range_start) if original_array_size == 0 then assert(mapped_range_start == 0) else assert(mapped_range_start >= 1) assert(mapped_range_start <= original_array_size) end mapped_range_end = map_back(range_end) if original_array_size == 0 then assert(mapped_range_end == 0) else assert(mapped_range_end >= mapped_range_start) assert(mapped_range_end <= original_array_size) end else mapped_range_start = range_start mapped_range_end = range_end end -- Initialize the class. new_object.range_start = mapped_range_start new_object.range_end = mapped_range_end return new_object end -- Get the inclusive start of the range, optionally mapped back to the original array. function Range:start() return self.range_start end -- Get the inclusive end of the range, optionally mapped back to the original array. function Range:stop() return self.range_end end return function(...) return Range:new(...) end