function read_attribute_value(parser) {
	const quote_mark = parser.eat("'") ? "'" : parser.eat('"') ? '"' : null;
	if (quote_mark && parser.eat(quote_mark)) {
		return [
			{
				start: parser.index - 1,
				end: parser.index - 1,
				type: 'Text',
				raw: '',
				data: ''
			}
		];
	}

	/** @type {Array<AST.ExpressionTag | AST.Text>} */
	let value;
	try {
		value = read_sequence(
			parser,
			() => {
				// handle common case of quote marks existing outside of regex for performance reasons
				if (quote_mark) return parser.match(quote_mark);
				return !!parser.match_regex(regex_invalid_unquoted_attribute_value);
			},
			'in attribute value'
		);
	} catch (/** @type {any} */ error) {
		if (error.code === 'js_parse_error') {
			// if the attribute value didn't close + self-closing tag
			// eg: `<Component test={{a:1} />`
			// acorn may throw a `Unterminated regular expression` because of `/>`
			const pos = error.position?.[0];
			if (pos !== undefined && parser.template.slice(pos - 1, pos + 1) === '/>') {
				parser.index = pos;
				e.expected_token(pos, quote_mark || '}');
			}
		}
		throw error;
	}

	if (value.length === 0 && !quote_mark) {
		e.expected_attribute_value(parser.index);
	}

	if (quote_mark) parser.index += 1;

	if (quote_mark || value.length > 1 || value[0].type === 'Text') {
		return value;
	} else {
		return value[0];
	}
}
@param {Parser} parser
@return {AST.ExpressionTag | Array<AST.ExpressionTag | AST.Text>}