function exec(match, params, matchers) {
	/** @type {Record<string, string>} */
	const result = {};

	const values = match.slice(1);
	const values_needing_match = values.filter((value) => value !== undefined);

	let buffered = 0;

	for (let i = 0; i < params.length; i += 1) {
		const param = params[i];
		let value = values[i - buffered];

		// in the `[[a=b]]/.../[...rest]` case, if one or more optional parameters
		// weren't matched, roll the skipped values into the rest
		if (param.chained && param.rest && buffered) {
			value = values
				.slice(i - buffered, i + 1)
				.filter((s) => s)
				.join('/');

			buffered = 0;
		}

		// if `value` is undefined, it means this is an optional or rest parameter
		if (value === undefined) {
			if (param.rest) result[param.name] = '';
			continue;
		}

		if (!param.matcher || matchers[param.matcher](value)) {
			result[param.name] = value;

			// Now that the params match, reset the buffer if the next param isn't the [...rest]
			// and the next value is defined, otherwise the buffer will cause us to skip values
			const next_param = params[i + 1];
			const next_value = values[i + 1];
			if (next_param && !next_param.rest && next_param.optional && next_value && param.chained) {
				buffered = 0;
			}

			// There are no more params and no more values, but all non-empty values have been matched
			if (
				!next_param &&
				!next_value &&
				Object.keys(result).length === values_needing_match.length
			) {
				buffered = 0;
			}
			continue;
		}

		// in the `/[[a=b]]/...` case, if the value didn't satisfy the matcher,
		// keep track of the number of skipped optional parameters and continue
		if (param.optional && param.chained) {
			buffered++;
			continue;
		}

		// otherwise, if the matcher returns `false`, the route did not match
		return;
	}

	if (buffered) return;
	return result;
}
@param {RegExpMatchArray} match
@param {import('types').RouteParam[]} params
@param {Record<string, import('@sveltejs/kit').ParamMatcher>} matchers